안녕하세요
closure
에서 왜 대체 self
를 써야하는가에 대한 고민을 마치고 돌아온 킴스캐슬입니다
공부를 하다가 그냥 자연스럽게 넘어가던 코드에서 거슬리는 부분이 있었습니다...바로 저 self
라는 녀석인데요
그냥 정말 갑자기 거슬려서 머릿속에서 생각을 해보니
굳이 self라는 viewcontroller라는 instance내부에 있는 클로저인데 굳이 self라고 명시하지 않아도 되는거아니야?
라는 생각이 머릿속을 스쳐갔습니다
사실 우리가 코딩하다가 self.count라고 안하고 count라고 해도 코드가 돌아가는데 아무런 문제가 없잖아요?
그래서 지웠더니 이게 왠걸...
오류가 뙇!하고 발생을 해버리네요
그래도 친절한 우리 엑코가 self를 붙이라고 알려주네요
근데 오류가발생한다는 말은 없어도그만 있어도 그만이라는 이야기가 아니잖아요?
분명히 어떤 이유에 의해 self를 붙인다는건데 그 이유가 궁금해졌습니다
사실은 이거에대해서 예전에 똑같은 주제로 TIL을 올린적이있었어서 그 글을 찾아서 읽었는데
과거의 저는 정말 글을 조리있게 쓰지 못했더군요...
https://velog.io/@kimscastle/TIL230411
그리고 다시 공부를 하니까 제가 이때 적었던 논리에도 약간의 문제가 있다는걸 알게되어서 다시 정리해보는 글이라고 생각해주시면 됩니다
Swift로 해야할지 iOS로 해야할지 고민을 하다가 클로저에서 self를 써야하는 경우는 우리가 application을 만들때 발생하는 케이스이기때문에 iOS로 분류를 했습니다
그럼 함께 알아보시죠
우선 self를 왜 쓰는가?
라는 결론에 도달하기 위해서는 알고있어야만하는 개념들이 몇개 존재합니다
중간보스라고 해두죠 ㅎㅎ
두둥등장
혹시 클로저는 어디에 저장이 되는지 알고계신가요?
클로저는 힙영역에 존재합니다
물론 한번쓰고 말 클로저는 스택에서 사용되고 사라지지만 클로저의 경우엔 변수에 할당된다거나 비동기처리를 한다거나 하는경우엔 좀 오래 저장이되야할 경우가 존재하고 그럴 때는 힙영역에 저장되게 됩니다
그런데 한번 생각을 해보면
클로저 내부에서 외부에 있는 변수를 사용할 때 그 변수를 어떻게 가져와야할까요에 대한게 캡처현상입니다
조금더 간단히 설명해보면 클로저 내부
에서 클로저 외부에 있는 변수
를 사용해야할때 변수를 가져오기 위해 사용되는것이 캡처현상이다라고 생각해주시면 됩니다
근데 swift에는 어떤 변수를 가져올때 두가지 방법이있죠
reference type VS value type
값자체를 복사해서 들고오는 방식 =
value type
주소를 복사해서 가져오는 방식 =reference type
캡처도 결국은 어떤 변수를 사용하기 위해 가져오는 방식이기때문에 값을 복사해서 가져오는지 주소를 가져오는지가 중요한 포인트입니다
결국 캡처현상이라는건 변수를 가져오는 방식인데 A일땐 a방식으로 가져오고 B일땐 b방식으로 가져오는 rule이 있어야겠죠?
그래서 특정상황에서 캡쳐리스트를 통해서 변수를 가져오는 방식은 특정한 방식을 가진다 정도로만 이해하고 넘어가시면 됩니다
이게 무슨말인지는 당연히 이해가 안되시는게 맞아요 지금부터 이게 무슨말인지를 천천히 풀어보도록 하겠습니다
아주 아주 쉬운 예제를 하나 들고 와 봤습니다
16번줄의 코드를 실행하면 어떤일이 일어날까요? 아마 1이라는 값이 print될겁니다
자 근데 클로저 내부인 13번째줄에서 클로저 외부변수인 number이라는 값을 사용하려하고있죠?
그러면 이때 캡쳐현상이 발생합니다
근데 이 예제는 캡처리스트를 사용한예제일까요?
아닙니다 왜냐면 코드 어디에도 리스트모양의 코드가 단 1도 없기때문에죠!(진담임🥺)
캡처리스트는 말그대로 특정방식으로 캡처할 녀석들을 list형태로 담아넣는 방식입니다
클로저를 하나 더 만들어봤습니다 closure2를보시면 변수가 list안에 들어있죠?
클로저 내부에서 외부변수를 사용하려하는데 그 변수가 list안에 들어있다?
캡쳐현상이 발생하는데 캡처리스트를 이용해 캡쳐해오겠다는말과 동일하다고 보시면됩니다
자그러면 closure2를 실행하면 어떻게될까요
캡쳐리스트가 어떻게 수행되는지 몰라도 1이 출력되지 않을까하는 생각을 할수있을겁니다
그리고 실제로도 1이 출력됩니다
단순히 이런경우에는 closure1이나 closure2나 즉, 캡쳐리스트를 사용하나안하나 결과는 동일합니다
근데 이런경우를 생각해볼까요
number가 3으로 바뀌었습니다
이때 closure1의 실행결과와 closure2의 실행결과는 어떻게될까요?
실제로 해보시면 closure1의 실행결과는 3이 나오고 closure2의 실행결과는 1이나옵니다
대체 이런 결과가 왜나온걸까를 생각해보면 값타입에서의 캡처리스트 작동방식
에 대해 알수있게됩니다
closure1의 입장에서 볼게요
16번째줄에서의 출력결과는 1이었는데 26번째줄에서의 출력결과는 3이나왔다는건
클로저의 number은 number의 주소를 바라보고 있다는 이야기가됩니다
바라보고있다는 말이 조금 애매모호할수있는데 reference를 참조하게된다고 이야기하면 좀 나을거같네요
값타입의 경우에 캡처를 하게되면 기본적으로 값의 주소값을 캡처하게됩니다
즉, number가 저장되어있는 메모리주소를 가리키게 되어서 number의 data 바뀌어도 메모리주소는 그대로이기때문에 실제로 변경된 값이 출력되게 되는것이죠
그러면 반대로 closure2의 입장에서 한번볼게요
1을 출력했다가 number를 바꿨는데도 그대로 1이 출력된다는건 closure2을 선언하는 순간에 number값의 data 자체를 그냥 가져왔다는 이야기가 됩니다
정리를 해보면 이렇습니다
값타입
에서의캡처
는 기본적으로 값의주소
를 가져오지만캡처리스트
를 사용하면값자체
를 가져올수있게됩니다
참조타입의 경우에도 예제 한가지를 보면서 시작해보겠습니다
이번에는 value type이 아니라 reference type인 클래스를 캡쳐해야하는 상황을 가져왔습니다
closure1의 경우엔 단순 캡처를 한 상황이고
closure2의 경우엔 캡처리스트를 통한 캡처를 한 상황이네요
우선 저렇게 21번째와 22번째 코드가 실행되면 당연히 0이 출력될겁니다
그러면 우리 value type에서의 예시처럼 num을 한번 바꿔봅시다
그리고 26번째줄과 27번째줄을 실행하면 어떤결과가 나올까요?
value type때처럼 결과가 다르면 내부 로직을 이해하기가 쉬울텐데 아쉽게도 똑같이 5라는 결과가 출력됩니다
그러면 참조타입을 캡처할때는 차이가 없으니까 아무거나 써도 상관없는건가요??
라고 물어보실수도 있지만 이 경우엔 우리가 다른 관점에서 결과를 분석해볼 필요가 있습니다
testClass의 reference Count를 한번 출력해볼게요
closure1을 실행하면(참조타입의 캡쳐현상의 경우) myClass의 reference count는 1이됩니다
Q : 제가 실행해보니 2가 나오는데요????
A : CFGetRetainCount라는 메서드를 실행시키는 과정에서 count가 발생하기때문에 1개를 빼주는게 맞습니당
그러면 closure2의 경우는 어떨까요
위의 코드를 실행해보면 myClass의 reference count는 2가 나옵니다
결론적으로 출력되는 결과는 같을수있지만 엄밀히 말하자면 myClass라는 클래스객체의 reference count가 다르다고 말할 수 있습니다
reference count가 많아지면 그 객체에 의존성이 강해지는 거니까 안좋은거 아닌가요?
그럴수도 있다고 생각합니다
⭐️closure2의 경우에 rc가 1더 증가했다는건 클로저의 캡쳐리스트를통해 객체 자체를 바라보고 있고
⭐️cloure1의 경우엔 클로저자체의 주소가 아니라 클로저를 담고있는 변수의 주소를 캡처하게됩니다
물론 객체자체에 의존하지 않는게 좋을때도 있지만 오히려 closure1의 경우에 변수가 다른 클로저를 바라보게 바뀌면 내가 원하는 결과를 전혀 출력하지 못하게 될수도 있기때문에 문제가 발생할 수도 있습니다
반대로 closure2의 경우엔 강하게 참조하고있기때문에 순환참조가 발생할수도있는데 이부분을 weak로 해주면 강한순환참조도 예방할 수 있습니다
지금까지 값타입에서의 캡처와 캡처리스트 참조타입에서의 캡처와 캡처리스트에 대해 알아봤는데요
이제 본격적으로 우리가 iOS application개발에서 사용하는 경우에대해 알아보겠습니다
드디어 이번 포스팅의 메인 주제로 왔습니다
제가 처음에 보여줬던 예시와 비슷한 예제를 가져왔습니다
지금까지 저희가 두가지 상황에 대한 캡처현상과 캡처리스트를 봤는데요
지금 이 코드는 어떤 상황과 같다고 할 수 있을까요?
A는 이렇게 말할 수 있습니다
결국 클로저안에서 값타입인 userName에 접근하는거니까 Value type에서의 캡처현상이네
그리고 또다른 B는 이렇게 말할수 있죠
self라는 참조타입객체에 접근을 하는거니까 Reference type에서의 캡처현상이라고 봐야지
A의 이야기를 듣고 코드를 봤는데 그렇다고 하기에는 인스턴스(self)내부에 있는 userName에 접근한거같아서 value type에서의 캡처현상이라고 보기에는 조금 어려울거같은데 B의 이야기가 맞다고 생각하려던 찰나에 위에서 reference type의 캡처현상은 클로저가 객체 외부에있었던 모양이어서 그게 맞는 말이되려면 TestViewController밖에 클로저가 있어야할거가고 TestViewController의 객체를 저장하는 변수도 있어야할거같습니다
맞습니다
지금 이 상황, TestViewController안에서 클로저를 사용하고 그 클로저안에서 객체의 속성을 사용하는 경우는 완전다른경우
라고 생각하셔야합니다
그래서 이전 value type의 캡처/캡처리스트나 reference type의 캡처/캡처리스트에서 부터 정답을 찾기 보다는 해당 case에서 어떻게 작동하는지를 알아야합니다
객체안에서 클로저를 사용하는 경우는 클로저가 클로저외부에 존재하는 참조타입의 참조(메모리주소)를 캡처합니다
이게 무슨말이냐면 이 예시에서 클로저 외부에있는 TestViewController라는 타입의 주소를 캡처하고 있다는겁니다
즉, reference type의 캡처리스트형태로 캡처하는 방식(객체의 주소자체를 캡처)으로 작동하게됩니다
결론적으로
객체안에서 클로저를 사용하는 경우 클로저가 외부의 객체의 주소를 캡처하고있기때문에 객체에 접근해서 특정 프로퍼티에 접근을 할 수 있다는겁니다
클로저가 TestViewController의 주소에가서야 비로소 userName 값을 가져올수있기 때문에 self.userName으로 코드를 작성해야한다고 이해하시면 됩니다
self가 없다면 클로저입장에서는 클로저 내부에 userName이라는 변수가 없는데 그 값을 알려달라고 하니까 황당한겁니다 그 값은 클로저가 바라보고 있는 객체(TestViewController)가 가지고 있으니까요!
MVVM을 공부하다가 문득 의문이 들어서 예전에 정리했던 글을봤는데
제가 봐도 뭐라 쓴지 전혀 모르겠어서 다시 공부하고 정리를 해봤습니다
아직은 아무도 안보는 블로그지만
저와 같은 의문을 가지고있는 개발자분들중에 한명이라도 만족하실수 있는 답변이 되었으면 좋겠습니다ㅎㅎ
저는 공부하던 MVVM을 공부하러 가보겠습니다!
그럼 20000!