- This topic has 2개 답변, 2명 참여, and was last updated 4 years, 7 months 전에 by nobleidea.
-
글쓴이글
-
-
nobleidea참가자
- 글작성 : 5
- 답글작성 : 8
안녕하세요 고귀한 생각을 가진 개발자 nobleidea입니다.
오늘은 클로저 캡처 리스트에 대해 공부해보았습니다.먼저 클로저 캡처에 대해서 알아볼까요?
클로저 내부에서 외부에 있는 값에 접근하면 값에 대한 참조를 획득합니다.
라고 정의해보았습니다.var value = 0 let closure = { print("capture value: \(value)" }
위와 같은 코드를 보시면 capture value를 출력하는 코드를 클로저로 작성하고 상수에 저장했습니다.
이 때, value값이 클로저 외부에 있는 값이므로 클로저에서 접근 시 값에 대한 참조를 획득합니다.(값을 캡처합니다.)
캡처에는 두 가지 종류가 있습니다.
Object-C에서는 복사본을 캡처하는 방식을 사용하고 있고
Swift에서는 참조를 캡처하는 방식을 사용합니다.따라서 위와 같은 코드에서는 swift에서 복사본이 아닌 참조를 캡처합니다.
참조를 캡처한다라는 말은 곧 클로저 내부에서 캡처한 값을 변경 시 원래 값도 함께 바뀌게 됩니다.위 코드에 있는 상수를 통해서 클로저를 호출해보겠습니다.
closure()
코드를 실행해보면 캡처한 값이 0이라는 결과값이 나옵니다.
var value = 0 let closure = { value += 10 print("capture value: \(value)" } print(value)
클로저 내부에서 캡처한 값을 변경시키면 원본값도 변경이되어
“capture value: 10”
10
이라는 콘솔로그창을 보실 수 있습니다.그러나 클로저에서 값을 캡처할때는 메모리 관리를 하지 않는다면 참조사이클 문제가 발생합니다.
클로저가 인스턴스를 캡처하고 인스턴스가 클로저를 강한참조로 저장하고 있다면
인스턴스가 정상적으로 해제되지 않습니다.예를 들어보겠습니다.
class Car { var speed = 100.0 var distance = 100.0 lazy var time: () -> Double = { return self.distance / self.speed } deinit { print("car deinit") } } var car: Car? = Car() //car.time() car = nil
위 코드를 실행하면 정상적으로 인스턴스가 메모리에서 해제되어
deinit가 호출됩니다.
그러나 주석으로 처리된 car.time()을 주석해제를 하고
코드를 실행한다면 deinit가 호출되지 않습니다.
그 이유는 time이라는 클로저 내부에서 self라는 값을 캡처하고 있기때문에
인스턴스의 reference count가 1증가되어 강한참조사이클이 발생합니다.(ARC 선행학습하시면 도움이 됩니다.)이 부분은 2가지 방법으로 해결할 수 있습니다.
weak 키워드와 unowned키워드 입니다.class Car { var speed = 100.0 var distance = 100.0 lazy var time: () -> Double = { [weak self] in guard let strongSelf = self else { return 0.0 } return strongSelf.distance / strongSelf.speed } deinit { print("car deinit") } } var car: Car? = Car() car.time() car = nil
weak키워드는 약함참조를 의미합니다.
reference counting을 하지 않기때문에 강한참조사이클을 방지할 수 있습니다.
다만 weak키워드는 옵셔널형식입니다.
그래서 캡처대상을 사용할때는 언랩핑(옵셔널바인딩) 또는 옵셔널체이닝으로 접근해야합니다.
클로저의 실행이 완료되지 않은 시점에 캡처대상이 해제될 수 있다면 약한참조를 사용합니다.
만약 위 코드에서 car 인스턴스가 해제되고 클로저가 호출되었다면 약함참조를 사용하고 있기때문에
self는 nil이 전달되어 옵셔널바인딩에 실패하고 코드가 종료됩니다.강한참조사이클을 해결할 수 있는 다른방법으로는 unowned 키워드(비소유 참조)가 있습니다.
class Car { var speed = 100.0 var distance = 100.0 lazy var time: () -> Double = { [unowned self] in return self.distance / self.speed } deinit { print("car deinit") } } var car: Car? = Car() car.time() car = nil
비소유 참조는 옵셔널 형식이 아닙니다.
비소유 참조로 캡처한 대상은 클로저 실행이 종료되기전에 해제될 수 있습니다.
만약 그렇게 된다면 해제 된 대상에 접근하게 되어 런타림오류가 발생합니다.
위험한 접근이기때문에 비소유 참조는 캡처대상의 생명주기가 클로저와 같거나 더 긴경우에 사용합니다.
즉 캡처대상이 메모리에서 해제되지 않았다고 알고있을 때 비소유 참조를 사용한다고 생각하시면 될 것같습니다.틀린부분이나.. 개선사항은 피드백 미리 감사드립니다!
다음에는 더 치열히… 정리해보도록 하겠습니다.2020-03-23 오후 10:39 #3728 -
야곰키 마스터
- 글작성 : 37
- 답글작성 : 579
멋진글입니다.
- Object-C (x) -> Objective-C (o)
-
미소유획득(Unowned Capture) 상수도 옵셔널 타입입니다. 다만, 일반 옵셔널이 아닌 암시적 추출 옵셔널(implicitly unwrapped optional) 타입입니다. 선언시에 타입 뒤에 느낌표 붙여주는 그 타입입니다. 그래서 (
nil
체크 등이) 필요하다면 저 상수도 옵셔널 바인딩을 해줄 수 있습니다.
혹시 제가 틀린점이 있다면 말씀주시면 감사하겠습니다 🙂
좋은글은 추천👍
2020-03-24 오전 3:19 #3961
-
-
글쓴이글
- 답변은 로그인 후 가능합니다.