iOS 응용 (swift)

[iOS - swift] Swift에서 Collection 타입이 value 타입으로 설계한 이유 (#class 타입을 struct로 감싸는 것의 의미, #out-of-line mutable storage, 동시성, data race condition)

jake-kim 2024. 7. 5. 01:18

Swift에서 Collection 타입은 모두 value 타입

  • reference type은 다중 스레드 환경에서 동기화 문제와 Data race 문제를 발생시키므로 value타입으로 관리하는것이 더욱 용이하기 때문에, Collection 타입은 value type으로 설계
  • 하지만 아래 코드처럼 reference 타입을 value 타입으로 감싼 것 뿐이며, struct 내부도 모두 value type이 아님
class SomeClass {}

struct SomeStruct {
    let c = SomeClass()
}
  • 내부도 모두 value type으로 구성하면 비용이 발생하는데, 아래에서 계속 설명

Large structs의 복사 방식 특징 2가지

large structs

  • 아래처럼 person을 복사할 때 내부적으로 비용이 많이 발생
let person = Person()
let personB = person
  • name, birthday, address, relationships을 복사할때, stored property에 대한 자체 저장소가 별도로 필요함
    • 따라서 이 값을 많이 복사할 것으로 예상될땐 stored property만큼 별도의 저장소가 생기므로 훨씬 더 많은 메모리 사용이 될 수 있음
  • 이런 것을 해결하기 위해서는 stored property 만큼 매번 복사하지 않도록 class타입과 같은 reference type을 사용하면 해결이 가능
    • 하지만 reference type은 다중 스레드 환경에서 동기화 문제와 Data race 문제를 발생시키므로 value타입으로 관리하는것이 더욱 용이
  • 두 가지 문제를 해결할 수 있는 방법?
    • 변경을 가하려는 참조에서만 객체를 복사되도록 하면 동기화 문제와 많은 메모리 사용 방지가 가능
    • 이 방법은 class 유형들을 struct로 감싸면 해결이 가능

class 유형들을 struct로 감싸는 것의 이점

  • 아래처럼 구현하면 ValueType 인스턴스 a는 변경을 가하려는 참조에서만 객체를 복사되어서 참조 타입만 쓸때의 동기화 문제와 value type만 쓸때의 오버헤드 문제를 모두 해결 가능
class ReferenceType {
    var value: Int
    init(value: Int) {
        self.value = value
    }
}

// Copy-on-Write 구조체
struct ValueType {
    private var reference: ReferenceType

    init(value: Int) {
        self.reference = ReferenceType(value: value)
    }

    var value: Int {
        get { return reference.value }
        set {
            if !isKnownUniquelyReferenced(&reference) {
                reference = ReferenceType(value: newValue)
            } else {
                reference.value = newValue
            }
        }
    }
}

var a = ValueType(value: 42)
var b = a
b.value = 100

print(a.value) // 42
print(b.value) // 100

* 참고

- https://developer.apple.com/videos/play/wwdc2024/10217