관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 메모리 할당 위치 (struct, class, protocol, generic, closure) 본문

iOS 기본 (swift)

[iOS - swift] 메모리 할당 위치 (struct, class, protocol, generic, closure)

jake-kim 2022. 6. 4. 19:17

value type, reference type

  • swift에는 2가지 타입이 존재
    • value type: struct, enum, collection, 기본타입(Int Double, Bool ...)
    • reference type: class, function, closure
  • 두 타입의 차이 - copying 
    • value tpye - 카피할때 데이터의 복사본을 생성
    • reference type - shared instance를 생성하여 같은 인스턴스를 바라보는 참조값을 생성
  • cf) value type을 사용하면, thread간 의도하지 않은 공유로부터 안전한 프로그래밍이 되어, 로버트 C. 마틴의 클린 코드에서 얘기하는 functional programming 핵심은 데이터의 불변성에 도움

메모리 공간 할당

https://www.vadimbulavin.com/value-types-and-reference-types-in-swift/

  • 일반적으로 value type은 stack영역, reference type은 heap영역에 저장 하지만, struct타입이 heap에 할당되는 경우가 상당히 많이 존재
  • value type의 크기가 컴파일 시간 동안 결정될 수 없는 경우 -> heap에 할당

메모리 할당

  • struct가 프로토콜을 confirm 하는 경우,
    • heap도 같이 저장 (3 machine words를 초과하게 되면 stack에서 오버헤드가 발생하여, 힙에 할당)
    • 여기서 힙에 할당된 것들의 life cycle은 Value Witness Table에서 관리
    • value type의 크기가 컴파일 시간 동안 결정될 수 없는 경우 -> heap에 할당
protocol Bar {}
struct Baz: Bar {}
  • value type과 reference 타입이 섞여있는 경우,
    • struct B와 struct C 모두 Heap에 할당
// Class inside a struct
class A {}
struct B { 
  let a = A() 
}

// Struct inside a class
struct C {}
class D {
    let c = C()
}
  • Generic 타입인 경우,
    • value type의 크기가 컴파일 시간 동안 결정될 수 없는 경우 -> heap에 할당
struct Bas<T> {
    var x: T

    init(xx: T) {
        x = xx
    }
}
  • Escaping closure captures인 경우,
    • 대부분의 지역 변수가 reference로 캡쳐되지만, 일부는 stack영역에 할당
class SomeClass {
  var handler: () -> Void = {}
  
  func myFunc(completion: @escaping () -> Void) {
    self.handler = completion // capture !
  }
  
  func callHandler() {
    self.handler()
  }
}

Reference count - value type과 reference type이 혼합된 경우

  • 데이터 준비
class Ref {}

// Struct with references
struct MyStruct {
    let ref1 = Ref()
    let ref2 = Ref()
}

// Class with references
class MyClass {
    let ref1 = Ref()
    let ref2 = Ref()
}
  • value type 내부에 reference type이 존재하는 경우,
    • value와 reference가 혼합되어 있으니 모두 heap 영역에 저장
    • value 타입의 count는 1이고, 내부 reference 타입은 2 
    • 즉 value type으로 reference type을 감싸면은 reference type이 별도로 count가 하나 더 증가되어 있음
let a = MyStruct()
let anotherA = a

CFGetRetainCount(a as CFTypeRef) // 1
CFGetRetainCount(a.ref1) // 2
CFGetRetainCount(a.ref2) // 2
  • 외부, 내부 모두 reference type인 경우,
    • 외부 reference type은 2, 내부 reference type은 1
    • 메모리 관리 관점에서 당연히, 외부 reference type만 갖고 있으면 내부 reference type을 2개 가지고 있지 않아도 되므로 내부는 1만 차지
let b = MyClass()
let anotherB = b

CFGetRetainCount(b) // 2
CFGetRetainCount(b.ref1) // 1
CFGetRetainCount(b.ref2) // 1
  • 외부는 reference type, 내부는 value type인 경우
    • 외부, 내부 모두 refernece type인 경우와 동일
struct Val {}

class MyClass2 {
    let val1 = Val()
    let val2 = Val()
}

let c = MyClass2()
CFGetRetainCount(c) // 2
CFGetRetainCount(c.val1 as CFTypeRef) // 1
CFGetRetainCount(c.val2 as CFTypeRef) // 1

복사 비용

  • value type
    • 기본 타입인 Int, Double과 같은 타입은 CPU register에 저장되고 복사할 때 RAM 메모리에 접근할 필요가 없기 때문에 속도가 빠른 장점이 존재
    • struct와 같은 value type은 복사할때 스택에 할당되고, 일정의 시간이 지연
    • String, array, set, dictionary와 같은 대부분의 extension이 가능한 타입은 write시에 값이 복사
  • reference 타입
  • 데이터를 직접 저장하지 않고 복사할 때 reference counting 비용이 발생
  • 스레드 간 공유 자원에 관한 관리를 할때 비용도 발생

* 참고

https://www.vadimbulavin.com/value-types-and-reference-types-in-swift/

Comments