본문 바로가기
IOS - Swift

[iOS/Swift] Closure의 캡쳐리스트와 ARC + AnyObjet

by 게게겍 2024. 4. 10.

Closure Capture list

 

이 코드를 실행시키면 어떻게 될까요?

  • Hello, World!가 출력됩니다

그러면 이 코드를 실행시키면 어떻게 될까요?

  • “next”가 출력됩니다.

이런 일이 벌어지는 이유가 뭘까요?

바로 capture list때문입니다.

Capture List가 뭔데요?

위 코드에서, 함수를 실행시키려면 Str 이라는 변수가 있어야 하는데

str을 (범위 바깥에서도) 지속적으로 사용하고 싶어서 잇는게 Capture 입니다.

Closure의 범위 외부에 있지만 Closure의 내부에서 사용해야해 라는 뜻이에요. 즉, 외부에 존재하는 변수를 사용하기위해 사용됩니다.

실제로는 변수를 캡쳐하는일은 잘 없고 보통 객체를 캡쳐해서 사용합니다

self.사용할 객체

이런식으로 사용합니다.

캡쳐 리스트의 사용

( 캡쳐 사용은 클로저의 시작인 { 의 바로 옆에  []를 이용해 캡쳐할 멤버를 in 키워드와 함께 나열하는것 )

//파라미터가 없는 경우
    
    { [캡처리스트] in
    
    }
    
//파라미터가 있는 경우
    
    { [캡처리스트] (파라미터) -> 리턴형 in
    
    }

왜 이런식으로 출력된거죠?

첫번째 코드에서는 str가 외부 변수를 capture해서 쓰기 때문에 해당 클로저를 실행시키면 “Hello, World!”가 출력 됩니다.

하지만 두번째 코드에서는 capture list가 없기 때문에 원래 closure대로 동작합니다.

Reference Capture

Closure는 값의 타입이 Value건 Reference건 모두 Reference Capture를 하게 됩니다.

Reference Capture가 뭐냐면

 

위 코드에서, str 이라는 외부 변수를 클로저 내부에서 사용하려고 str을 캡쳐했더니 외부변수인 str을 사용하는것을 보았습니다.

이 내용이 전부입니다. 이 내용이 Reference Capture 입니다

reference의 특성상, 값들의 불변값들을 복사본을 만들어 내게 되는데, 이 경우에 캡쳐리스트에 명시적으로 포함되어있더라도 참조된것 그 자체가 캡쳐되기 때문에, 동일한 객체를 참조할 수 있습니다.

 

 

예시를 들어서,

이 값을 찍어보았을때 각각의 active는 0 34 34 가 나옵니다.

이 값을 클로저 내부에서 변경해도

동일한 결과인 0,34,34 가 나옵니다.

즉, 참조타입의 객체가 변경되면 (참조하고 있는 값 자체가 변경되면) 클로저 내부에서도 그 변경된 상태를 반영하게 됩니다.

 

Value Capture

엥 나는 그러면 참조말고 값 자체를 캡쳐할래요

여기서 나오게 된 개념이 Capture Lists 입니다.

아까와는 다르게, 값을 캡쳐하게되면, 외부변수인 active의 값이 0, 34 ,0 이 나옵니다.( print는 1 3 2 로 순서가 달라요)

Capture Lists를 사용할 때, 값 타입(value type)은 클로저 생성 시점의 값으로 복사되어 캡처됩니다.

즉, Closure를 선언할 당시의 num의 값을 Const Value Type으로 캡쳐하게 되는것입니다.

값을 캡쳐해서 immutable한 copy를 만들어내는 것은 reference type이 아닌 value type에 적용됩니다. 캡처 목록을 사용하는 경우에도 reference type의 경우 reference semantics로 인해 모두 동일한 객체를 참조하게 됩니다.

→ value type은 stack에 저장되고, reference type은 stack에 reference가 저장되고 heap에 내용이 저장되기 때문에 !


Arc와 클로저의 캡쳐

 

ARC란?
  • 자동으로 메모리 관리하는것 (Auto Reference Count)
  • 객체에 대한 참조 카운트를 관리하고 0이 되면 자동으로 메모리 해제
  • run time에 계속 실행되는게 아니라 complie time(build 할 때)에 실행
  • 자동으로 관리해줌
  • 하지만 retain cycle에는 유의할것
  • 옛날 옛날 옛날 Objective-C에서는 MRC, 즉 수동으로 메모리 관리 했다.

 

 

기본적으로, Swift는 강한참조로 캡쳐를 합니다. 즉, 클로저가 클로저 내부의 값들에 대해 강한참조를 만든다는것입니다.

  1. Reference count가 1증가한다.(객체에 새로운 참조가 생길 때마다 해당 객체의 참조 카운트가 1 증가합니다)
  2. ARC는 객체를 메모리에서 deallocate시킬 때 Reference count가 0이냐 아니냐를 기준으로 deallocate합니다.(release)
  3. 0이라면 “해제각 떳다” 하고 deallocate시키는 반면에, 0보다 높다면 “사용중이구나”고 하고 메모리에 유지되어 있습니다.
NowSopt.SoptiOS = {result in
  self.iOS1 = result.0
  self.iOS2 = result.1
}
  • ViewController에서 NowSopt 객체가 있고 해당 객체에 클로저 프로퍼티가 있고 해당 코드는 ViewController에 있는 코드입니다.
  • 그리고 ViewController에서 일어나는 network 통신이라고 가정했을때
  • Network 통신은 보통 굉장히 오래걸리는 작업으로 여기고 비동기로 처리하죠.
  • 여기서 네트워크 통신 결과는 ViewController가 release된 후에 결과가 올 수도 있습니다.

 

 

??? ViewController가 release가 될수가 있나요 ???

위에서 언급한것처럼, Closure는 Closure내부에서 사용되는 값에 대해 capture를 합니다.

NowSopt는 클로저 타입의 프로퍼티인 SoptiOS를 강한 참조로 가지고 있고 SoptiOS closure는 내부에서 self 를 사용하기 때문에 이 객체를 capture합니다. (해당 코드에서 self는 ViewController)

따라서 ViewController 클래스의 reference count는 1증가합니다.

그리고 ViewController는 default로 NowSopt 를 강한 참조로 가지게 됩니다.

 

이 그림과 같은 상황이 되고, 이는 각 인스턴스가 서로에 대한 strong 참조로 이어지게 됩니다. (2 는 self를 통해서 참조중)

위와같은 상황을 Retain Cycle라고 합니다.

Retain Cycle

서로가 서로를 강한 참조로 붙잡고 있어 둘의 reference count는 1에서 절대 줄어들지 않습니다. (Retain Cycle)

 

ARC가 객체를 메모리에서 deallocate할 때 할당 해제해도 될지 안될지에 대한 기준이 reference count가 0인지 아닌지 여부라고 위에서 언급했습니다. 즉, 1에서 더이상 줄어들지 않는다는 것은 메모리에서 release될 수 없다는 것을 말합니다.

 

이와같은 상황을 막기 위해서, weak나 unowned키워드를 사용해서 retain cycle을 해제 할 수가 있습니다.

weak나 unowned 키워드를 사용하면 reference count를 증가시키지 않습니다.

NowSopt.SoptiOS = { [weak self] result in
    guard let self = self else {return}
     self.iOS1 = result.0
     self.iOS1 = result.1
}
  • 위와 같이 코드를 바꿔준다면 NowSopt의 클로저는 ViewController를 약한 참조로 가지고 있게 됩니다.

연약한 self 화살표

Break Retain Cycle

그럼 이제 위와 같은 그림의 형태가 되게됩니다.

weak는 reference count를 증가시키지 않기 때문에 ViewController의 refernce count는 ViewController의 참조가 끝나는 순간 0이 될테고, ViewController가 할당 해제된다면 자연스럽게 NowSopt도 해제되면서 메모리 누수가 없어지게 됩니다.

AnyObject

모든 클래스가 암묵적으로 준수하는 프로토콜입니다.

AnyObject는 클래스, 클래스 타입 또는 클래스 전용 프로토콜의 인스턴스에 대한 구체적인 타입으로 사용될 수 있습니다.

바로 본론으로 들어가면

버튼을 눌러서 뒤로 가면 버튼 누른 횟수가 뜨는 코드가 있을때

/// ViewController에서 SoptMemberViewController를 present하는 코드
let soptMemberViewController = SoptMemberViewController()
soptMemberViewController.delegate = self
self.present(soptMemberViewController, animated: true)

...

protocol SoptMemberViewControllerDelegate: AnyObject {
    func didTapBackButton(pressCount: Int)
}

final class SoptMemberViewController: UIViewController {

    private var pressCount = 0

    weak var delegate: SoptMemberViewControllerDelegate?

        ...
}

위에서 언급하였듯이, weak를 사용하여 retain cycle을 깨 부술수 있게 됩니다.

만약, 이 코드에서, AnyObject를 사용하지 않는다면,

"클래스에 바인딩되지 않은 임의의 SoptMemberViewControllerDelegate에는 weak를 적용할 수 없다"

는 에러 메시지가 뜨게 됩니다.

 

 

? AnyObject ? 님 뭐 됨 ?

 

프로토콜은 클래스와 구조체, 열거형에 사용할 수 있습니다.

하지만 지금 오류코드에서는, SoptMemberViewControllerDelegate 프로토콜이 어디에 사용될지 모르니 reference count 관리를 위해 사용되는 unowned나 weak 키워드 사용 불가 ( 즉, 값타입일지 참조타입일지 알수 없어서 오류를 뱉는중) 하게 됩니다.

 

protocol에 AnyObject를 상속받아서 사용하면, 클래스인것을 확신하기 때문에 weak 키워드 사용 가능합니다.

 

따봉 AnyObject야 고마워~

Ref

https://medium.com/swlh/using-capture-lists-in-swift-19f408f986d

 

Using Capture Lists in Swift

Are you closures capturing values?

medium.com

 

https://zeddios.tistory.com/213

 

Swift ) Any와 AnyObject의 차이

안녕하세요 :) Zedd입니다. 오늘은 Any와 AnyObject의 차이를 알아볼거에요!! 시작할게요!! Any와 AnyObject의 차이 The Swift Programming Language에서, Any와 AnyObject를 이렇게 말합니다. Swift는 특정하지 않은 타

zeddios.tistory.com

 

https://sujinnaljin.medium.com/ios-arc-뿌시기-9b3e5dc23814

 

[Swift] ARC 뿌시기

ARC.. 들어는 봤습니다만?ㅎ

sujinnaljin.medium.com

 

https://codingmon.tistory.com/43

 

[Swift] Closure Capture list, ARC, AnyObject까지 연결지어서 정리

키워드 공부 과제에서 Closure Capture list, ARC, AnyObject에 대해서 알아보는 것이였는데 이를 연결지어서 정리해보았습니다 Closure Capture list var str = "Hello, World!" var myClosure = { [str] in print (str) } str = "next

codingmon.tistory.com

 

https://jusung.github.io/classOnlyProtocol/

 

[Swift] 클래스만 사용 가능한 프로토콜의 선언

'weak' must not be applied to non-class-bound 'MyDelegate'; consider adding a protocol conformance that has a class bound

jusung.github.io

 

https://stackoverflow.com/questions/33471858/swift-protocol-error-weak-cannot-be-applied-to-non-class-type

 

Swift protocol error: 'weak' cannot be applied to non-class type

What's the difference between Protocols and class-bound Protocols, and which one we should use in Swift? protocol A : class { ... } protocol A { ... } We get an error when attempting to add a weak

stackoverflow.com