TIL(230411)

Youth·2023년 4월 10일
0

왜 클로저에 self.를 붙어야하는걸까? [self]는 대체 왜 쓰는걸까?

우선 이글을 정리하게 된 이유는 캡처리스트를 공부하려는데 아니 대체왜 클로저내에서 프로퍼티에 접근할때 self.를 붙여야하는지 알고싶었고 그걸 알아야 캡쳐리스트를 왜 사용하는지가 이해가될거같았다.

우선 캡쳐리스트에 대해 알기전에 클로저내에서 왜 self.를 써야하는지에 대해 알아보자

클로저는 함수다, 그렇기때문에 클로저의 실행방식이나 함수의 실행방식이나 크게 다르지 않다!

함수의 실행을 메모리구조측면에서보면 컴파일 시점에서 코드영역에 알아서 함수들의 명령이 쓰여진다. 함수를 실행하면 스택영역에서 함수의 시작점인 10번째줄에가서 함수를 순서대로 실행하라고 명령을 한다

사실 클로저도 똑같다 보통의 클로저 그냥 클로저를 실행시키는경우에는 클로저 자체는 함수의 시작점을 가리키고있고 스택영역에서 함수를 실행하게 된다.

애초에 클로저가 함수처럼 스택영역에만 존재하고 사라지고 존재하고 사라지고 했으면 캡처니 캡처리스트니하는 개념자체가 필요가 없어진다.

그런데 문제는 클로저는 함수처럼 동작하지만(위의 그림처럼) 특별한 상황에서는 한번 실행되고 사라지는 것이 아니라 오래동안 저장되고 사용되어야하는 경우가 생긴다. 그렇기때문에 클로저는 함수와 다르게 어떤 특정한 상황에서는 쓰고 없어지는것이아닌 저장되야하는 순간이 발생한다는 것이다.

예를들면 어떤경우냐면
어떤 변수에다가 클로저를 대입하는 경우이다
var testClosure: (Int)->Void = { print($0) }이런것처럼 testClosure이라는 변수에 클로저를 담는 경우엔 클로저의 실행(함수의실행)이 끝나도 없어지는것이 아니라 변수안에 계속 존재해야한다 이런경우에는 클로저라는 함수가 저장되어야하는 대표적인 경우에 해당된다.

두번째는 비동기적으로 실행되는경우다 뭐 예를들어 디스패치큐에 담아야하는 클로저의 경우가 이에 해당할거같다. 우리가 비동기처리를 하는이유는 여러가지가있겠지만 이해하기 쉽게 생각을 해보면 어떤 task가 좀 오래걸려서 이걸 하나의 thread에서 다 처리하려니 이 오래걸리는 일때문에 다른일들을 못하게되서 문제가 발생을 하는건데, 이를 해결하려면 아얘 오래걸리는 작업들은 다른 thread로 보내야한다. 즉 오래걸리는작업들을 클로저에 묶어서 다른 thread로 보내는데 결국 이 오래걸리는 작업동안에는 closure가 유지가 되어야하기때문에 이또한 클로저를 저장해야하는 상황에 해당한다

그리고 escaping키워드의 클로저도 비슷한 의미라고 생각하면될거같다

어쨋든 우리는 클로저를 사용할때 어쩔수 없이 클로저를 힙이라는 영역에 보관해야할일이 생긴다. 그리고 이런상황에서 사용해야하는 것이 캡처현상과 캡처리스트다

간단히 정리를 하면 우선 애초에 실행되고 없어질 클로저면 캡처니 캡처리스트니 weak self니 이런걸 고려할 필요자체가 없다. 그래서 우리가 map함수나 filter함수에 있는 closure에 [self]이런걸 안쓰는 이유가 고차함수를 자세히보면 뭐 비동기처리도, escaping도 그 클로저를 다른 변수에 넣는 코드가없다. 즉 그냥 실행되고 없어질 클로저라는거다.

서론이 길었다 이제 앞으로 우리는 힙영역에 저장되는 클로저만 가지고 이야기를 할거다.

우선 힙에저장되는 클로저의 경우에 클로저 내부에서 외부변수를 사용하려면 캡처를 해서 사용해야한다.
그리고 이때 뭐 value타입의 캡처와 캡처리스트도 분명히 있지만 우리는 앱을만들때 사용할 개념을 공부할거니까 그냥 오늘은 간단하게 reference(참조) 타입의 캡처와 캡처리스트에 대해서만 이야기를 해보려한다. reference 타입은 이후로 참조타입이라고 말하곘다

총 두가지가 있다
첫번째는 참조타입의 캡처현상이다.

우선 이렇게 변수의 주소값을 캡쳐하는 경우는 적어도 우리가 uikit을 사용할때는 극히 드문 상황을 제외하고는 볼일이 없기때문에 겁먹을 필요없다 그냥 그러려니하고 넘어가면된다. 그래도 알기는 알아야하니 우선 설명을 해보자면 이렇게 우리는 class라는 참조타입에 접근해서 num이라는 변수에 접근을 하고싶은거다. 우선 ref라는 변수에 클로저를 넣었으니 클로저가 힙영역에 저장되어야한다. 그리고 이때 힙영역에 저장된 클로저가 외부 변수를 가져다 쓰고싶을때 캡처나 캡처리스트를 통해서 가져와야하는데 이때는 class에 프로퍼티에 접근을 해야하니까 참조타입의 캡처현상을 하던 참조타입의 캡처리스트를 하면된다.

우선 이 경우는 참조타입의 캡쳐현상이다. 애초에 힙영역에 클로저를 저장할때 클로저의 함수메모리주소와 x를 사용해야하니 x의 값을 캡처해와야하는데 이경우에는 외부에서 class 인스턴스를 저장해놓은 변수의 주소값을 캡처해와서 그 변수의 주소로 가서 그 변수가 가지고 있는 클래스의 주소로가서 인스턴스에 접근을 하게된다.

하지만 우리가 이걸사용할일이 없다. 왜냐면 이건 클래스 외부에서 변수에 클로저를 저장한 경우인데 보통 우리가 ViewController 밖에 변수를 만들고 거기에 클로저를 담고 심지어 ViewController의 객체를 담은 변수도 선언을해야한다…? 적어도 나는 앱을만들면서 그런코딩을 해본적이 없기때문에 그냥 이런게 있구나 하고 넘어가면된다.

두번째 경우는 캡처리스트를 사용한경우다 이 경우엔 그냥 애초에 클래스의 주소자체를 캡쳐해와서 사용하는 경우다 아마 앱을만드는경우엔 99.99퍼센트 이런경우만 사용을 할것이기 때문에 우리가 일반적으로 uikit내에서 클로저를 사용할때는 캡처리스트만 사용하는구나라고 생각하면된다

하지만 우리는 앱을만들때 위의 상황도 사용해본적이 없을거다. 사실 이경우에는 ViewController밖에다가 변수를 만들고 클로저만 선언해주면되기때문에 할라면할수는있다.근데 우리가 매번 사용하는경우는

객체 즉 클래스내에서(ViewController) 클로저를 사용하는 경우다. 이경우에는 기본적으로 힙에 저장되는 클로저의 경우에는 외부 변수를 사용할때 캡처현상이나 캡처리스트를 사용해야하는데 캡쳐현상의 경우에는 객체가 담겨져있는 변수의 주소값을 캡쳐해야하기때문에 사용할수가 없는 방식이다(애초에) 그러면 우리가 사용할수 있는 방식은 ViewController안에 프로퍼티에 접근을 해야하니까 참조타입에 접근해서 캡쳐리스트를 해오면된다.

그래서 우리가 매번 뷰컨안에서 클로저를사용할때(힙에저장되는 클로저의경우) self.~~~를 해준거다. 그리고 self.을 하기싫으면 in 앞에다가 [self]를 넣어줬던것이다 이 두가지는 동일한 방법이다. 그냥 클래스의 주소값자체를 캡처리스트한다는 뜻이다.

이제 왜 맨날 ViewController안에서 self.나 [self]를 썼는지 아주 조금은 이해를 했다. 이제 이걸 이해했으니 왜 [weak self]를 쓰는지를 공부해보면 될거같다

profile
AppleDeveloperAcademy@POSTECH 1기 수료, SOPT 32기 iOS파트 수료

0개의 댓글