Closure - Capturting Value
우선, referunce type과 value type에 대해서 알아보자,
referunce type class, function, closure 등이있고, 대표적으로는 class를 많이 이야기한다. class의 인스턴스를 생성할 때, 인스턴스를 가리키는 포인터가 생성되는데 이 포인터는 생성된 인스턴스여 주소를 참조하게 된다.
(클로저에서 class 인스턴스에 대한 참조를 캡처하면 클로저의 내부의 인스턴스에 대한 모든 변경 사항이 클로저 외부의 원래 인스턴스에도 반영된다.)
이와달리 value type은 해당 값의 복사본을 생성하여 사용하는 데이터타입을 말한다. 대표적으로는 struct, enum, String, Array, Dictionary 등이 있다.
(swift Type에 대해 자세히 알고 싶다면 여기서 보시면 될거같습니다. 굉장히 잘 정리되어 있어요!!)
Referunce type or Value type
referunce type와 value type의 차이점음 copying 동작에 차이가 있다.
value type 은 copying 시 data의 unique(유일한) 한 복사를 한다. 하지만,
referunce type은 shared instance(공유 인스턴스)를 사용하기 때문에, 다른 변수에 할당하고, instance의 값을 변경하면 기존 값도 같이 변경된다.
value Type
var str1: String? = "Value"
var str2 = str
var str3 = str2
/*
str1, str2, str3는 Stack 영역에서 각각 메모리를 차지하고있다.
*/
referunce Type
class TestClass {
var a = 11
func testFunc() {
print(a)
}
lazy var myClosure = {
print(self.a)
/* closure 안에서 사용하는 프로퍼티는 closure 내부에서
class를 참조 하고있기때문에 명시적으로 self. 을 사용하여 가르켜 줘야한다.
즉, myClosure는 TestClass 자체를 참조 하고있는것이다.
*/
}
}
var myClass1: TestClass? = TestClass()
var myClass2 = myClass1
var myClass3 = myClass2
var myClass4 = myClass3
/*
myClass1, myClass2, myClass3, myClass4는 TestClass의 주소값을 참조하고 있다.
*/
myClass1?.a = 10
위 코드에서 myClass1 ~ myClass4는 같은 주소(TestClass의 주소를 참조하고 있음)를 가지고 있기 때문에, TestClass의 변수 a를 호출하여 10을 대입하고, 출력하면 myClass1.a ~ myClass4.a 를 호출하면 모두 10으로 변경된 걸 볼 수 있다.
print(myClass1?.a)
print(myClass2?.a)
print(myClass3?.a)
print(myClass4?.a)
/*결과
Optional(10)
Optional(10)
Optional(10)
Optional(10)
*/
참조를 끊으려면?
nil 사용해서 참조 끊기
가장 쉬운 방법은 nil을 대입하는 것이다.
class TestClass {
var a = 10
func testFunc() {
print(a)
}
lazy var myClosure = {
print(self.a)
}
deinit {
print("TestClass deinit")
}
}
var myClass1: TestClass? = TestClass()
var myClass2 = myClass1
var myClass3 = myClass2
var myClass4 = myClass3
myClass1?.a = 10
myClass1 = nil
myClass2 = nil
myClass3 = nil
myClass4 = nil
/*결과
TestClass deinit
*/
하지만 myClosure를 생성하면
class TestClass {
var a = 10
// ...
lazy var myClosure = {
print(self.a)
}
deinit {
print("TestClass deinit")
}
}
// ...
myClass1.myClosure
myClass1 = nil
myClass2 = nil
myClass3 = nil
myClass4 = nil
/*결과
*/
myClass1.myClosure를 생성하였기 때문에 메모리 해제가 되지 않는다. 왜냐하면 클로저가 생성될 때 클로저 내무에서 프로퍼티 a 에 대한 주소값을 참조하고 있기 때문이다.
즉, myClass1.myClosure = nil을 할당해 줘야 비로소 메모리 해제가 된다.
그런데! nil을 저장할 수 있는 자료형은 오로지 Optional로 선언된 자료형만 이기 때문에 myClosure의 자료형을 변경해 줘야 한다.
//...
lazy var myClosure: (() -> Void)? = {
//...
}
myClass1?.myClosure = nil
캡처한다는 의미는 클로저 내부에서 외부에 있는 프로퍼티나 다릇것?을 참조(referunce)할 때를 말한다.
nil 사용하지 않고 참조 끊기!
class TestClass {
var a = 10
// ...
// capturing value
lazy var myClosure1: (() -> Void)? = {
print(self.a)
}
// 값을 복사해 사용해 따로 메모리를 해제하지 않아도 됨.
lazy var myClosure2 = { [a] in
print(a)
}
// self 자체를 캡처하면 강한참조가돼어 따로 메모리 해제를 해 줘야함
/*
myClass1: TestClass? = TestClass()
myClass2 = myClass1
처럼 class 자체를 참조하고있다.
*/
lazy var myClosure3: (() -> Void)? = { [self] in
print(a)
}
// capturing list
// 따로 메모리 해제를 하지 않아도 됨.
lazy var myClosure4 = { [weak self] in
print(self?.a ?? 0)
}
deinit {
print("TestClass deinit")
}
}
myClass1: TestClass? = TestClass()
var myClass2 = myClass1
var myClass3 = myClass2
var myClass4 = myClass3
myClass1?.myClosure1 = nil
myClass1?.myClosure2
myClass1?.myClosure3 = nil
myClass1?.myClosure4
myClass1 = nil
myClass2 = nil
myClass3 = nil
myClass4 = nil
/*결과
TestClass deinit
*/
위 소스를 보면 myClass1?. myClosure2, myClass1?. myClosure3은 nil을 할당하지 않아도 메모리 해제가 된 걸 볼 수 있다.
왜냐하면 값을 복사해 오기 때문이다. [ ] 안에 value type를 복사해 와 사용하기 때문에 self도 사용 안 해도 되는 것이다.
하. 지. 만... myClass1?. myClosure1, myClass1?. myClosure4는 class의 참조(referunce)를 가리키고 있기 때문에 메모리 해제를 해줘야 하는 것이다.
음.. 메모리 개념이 이해 안 가신다면 ARC 개념에대헤서 공부하시면 될 거 같아요! 여기
'iOS > Swift' 카테고리의 다른 글
[Swift Network] URLSession (0) | 2023.04.12 |
---|---|
[Swift Network] URLSession - 사전지식 (0) | 2023.04.12 |
[Swift] Closure - 축약 (0) | 2023.03.23 |
[Swift] Closure - escaping (0) | 2023.03.23 |
[Swift] Closure 기본 개념 (2) | 2023.03.20 |