Swift/개념 & 응용

[Swift] 값 타입에서 참조 타입 사용 시 주의할 점(feat. isKnownUniquelyReferenced)

유정주 2022. 12. 27. 17:17
반응형

서론

이 주제는 옛날에도 다루긴 했지만 따로 한 번 더 다루는 것도 좋다고 생각해서 포스팅을 남깁니다.

 

Collection -> Copy-On-Write -> isKnownUniquelyReferenced -> 이번 주제 라는 의식의 흐름 공부를 하고 있었고,,,,

사실 곧 면접이 있어서 포스팅하는 게 시간적으로 굉장히 큰 부담이지만,,,

그래도 기록해두면 저도 좋고 방문자님들도 좋고 좋은게 좋은거니.. ㅎㅎ

 

아무튼, 이번 포스팅에서는 구조체에서 참조 타입 사용 시 주의할 점에 대해 알아보겠습니다.

(전체 코드는 최하단에 있습니다. 코드가 이미지라고 너무 노여워하지 마세요.)

 

Value Semantics와 Reference Semantics

Value Semantics

구조체는 값 타입이기 때문에 하나의 객체가 유니크합니다.

Value Semantics라는 단어가 익숙하실텐데요.

하나의 인스턴스에서 변화가 있어도 다른 인스턴스에 영향을 주지 않는다는 의미입니다.

위 예시에서 testStruct1을 testStruct2에 할당하고

testStruct2의 numbere를 2로 변경했을 때,

testStruct1의 number는 바뀌지 않고 그대로입니다.

 

Reference Semantics

하지만 참조 타입은 다릅니다.

하나의 객체를 여러 인스턴스에서 참조할 수 있기 때문에

특정 인스턴스에서 값을 바꾸면 다른 인스턴스에도 영향을 줍니다.

testClass1을 testClass2에 할당하고 

testClass2의 number를 수정했을 때,

testClass1의 number도 2로 변경되었습니다.

 

구조체에서 참조 타입 사용 시 주의할 점

오늘의 주제는 구조체에서 참조 타입 사용 시 주의할 점입니다.

 

TestStruct 안에 TestClass 프로퍼티를 생성했을 때를 생각해 봅시다.

testClass 프로퍼티는 Value Semantics로 동작할까요, Reference Semantics로 동작할까요?

testStruct1을 testStruct2에 할당하고,

testStruct2의 testClass의 number를 수정합시다.

만약 Value Semantics로 동작한다면 testStruct1.testClass.number는 1로 출력 돼야 할거고,

Reference Semantics로 동작한다면 2로 출력 돼야 합니다.

 

확인해보면,

Reference Semantics로 동작을 한다는 것을 알 수 있습니다.

 

즉,

값 타입일지라도 내부 프로퍼티가 참조 타입이라면 그 프로퍼티는 Reference Semantics를 가진다는 것을 알 수 있습니다.

 

해결 방법

값 타입 안의 참조 타입도 Value Semantics로 동작하게 하려면 어떻게 해야할까요?

(해결 방법이라고 했지만 이게 오류나 에러는 아니긴 하죠.)

 

방법 1

가장 먼저 떠오르는 건 매번 객체 생성을 해주는 방법입니다.

이는 NSCopying 프로토콜을 이용해 구현할 수 있습니다.

TestClass는 copy 메서드를 이용해 새로운 객체를 생성할 수 있게 되었습니다.

TestStruct에서는 _testClass와 testClass 프로퍼티를 나눠서 관리됩니다.

호출부에서는 testClass에 접근하는데 매번 _testClass 프로퍼티를 복사해서 반환합니다.

 

이 방법의 문제는 뭘까요?

바로 프로퍼티에 접근만 해도 객체를 생성한다는 점입니다.

위 사진처럼 Read 동작만 하려고 해도 새로운 객체가 생성이 되서 매우 비효율적입니다.

 

방법 2. isKnownUniquelyReferenced 추가

이 문제를 해결하기 위해서는

단순히 프로퍼티 접근만 한건지, 새로운 객체에 할당이 된건지 알아야 합니다.

 

포스팅 제목에서 언급한 isKnownUniquelyReferenced 함수를 이용해 구현이 가능합니다.

isKnownUniquelyReferenced 함수는 객체가 하나의 강한 참조만 가지고 있는지 Bool 값을 반환합니다.

강한 참조를 하나만 가지고 있으면 true, 여러 개 가지고 있으면 false 입니다.

  • 주의할 점 1. 강한 참조만을 따지기 때문에 weak나 unowned는 판단하지 않습니다.)
  • 주의할 점 2. 해당 함수는 Swift 객체에 맞춰서 구현되어 있습니다. Objective-C 객체를 해당 함수로 테스트하지 마세요.

 

이제 isKnownUniquelyReferenced를 이용해 최적화를 해봅시다.

TestStruct만 수정을 했고, TestClass는 동일합니다.

TestStruct의 testClass의 getter에 isKnownUniquelyReferenced가 추가되었습니다.

_testClass가 하나의 강한 참조만 가지고 있다면 바로 반환합니다.

_testClass가 여러 개의 강한 참조를 가지고 있다면 새로운 객체를 생성해서 반환합니다.

 

결과 확인

먼저 Value Semantics로 동작이 잘 되는지 확인해 봅시다.

testStruct2의 변화가 testStruct1에 영향을 주지 않습니다.

굿굿

 

이제 Read 동작을 할 때 불필요한 복사를 하지 않는지 봅시다.

객체가 단 한 번만 생성이 되고, 이후에는 같은 객체를 참조하여 출력합니다!

 

감사합니다.

 

전체 코드

import UIKit

struct TestStruct {
    private var _testClass: TestClass
    
    var testClass: TestClass {
        mutating get {
            if !isKnownUniquelyReferenced(&_testClass) {
                print("Create New Object")
                _testClass = _testClass.copy() as! TestClass
            }
            
            return _testClass
        }
        
        set {
            _testClass = newValue
        }
    }
    
    init(testClass: TestClass) {
        self._testClass = testClass
    }
}

class TestClass: NSCopying {
    var number: Int
    
    init(number: Int) {
        self.number = number
    }
    
    func copy(with zone: NSZone? = nil) -> Any {
        return TestClass(number: self.number)
    }
}

var testClass: TestClass = TestClass(number: 1)
var testStruct1: TestStruct = TestStruct(testClass: testClass)
var testStruct2 = testStruct1

testStruct2.testClass.number = 2

print("testStruct1.testClass.number: \(testStruct1.testClass.number)") //1
print("testStruct2.testClass.number: \(testStruct2.testClass.number)") //2

 

참고

https://developer.apple.com/documentation/swift/isknownuniquelyreferenced(_:)-98zpp 

https://www.hackingwithswift.com/example-code/language/how-to-safely-use-reference-types-inside-value-types-with-isknownuniquelyreferenced

 

 


아직은 초보 개발자입니다.

더 효율적인 코드 훈수 환영합니다!

공감 댓글 부탁드립니다.

 

 

반응형