MVC -> MVVM 변경하는 과정에서 클로저와 ARC관계에 대해 공부하고자 정리.
- 내용: MVC, MVVM, closure, ARC, unowned, waek
Model = Model // 정확하게 말하면 DataClass를 말한다.
View = Controller + View // MVC에서 VC를 같이 사용.
ViewModel // View를 위한(데이터)모델, 로직을 가짐.
⭐️ViewModel
사용자의 인풋(View에서 이벤트가 발생하면)을 받으면 Model의 데이터를 가지고 View에 전달해 주어 View(화면)이 변경된다.
ViewModel은 일반적으로 class로 작성한다
why?
class는 참조타입이고 struct는 값타입(데이터의 공유와 변경감지가 class보다 복잡할 수 있다.)이다.
class로 만들면 메모리 관리, 데이터 공유 등이 용이하다.
그럼 ViewModel은?
View에서 어떠한 동작(사용자가 터치를 했을 경우) 그 액션을 ViewModel에 전달해 준다.
그럼, ViewModel은 서버와 통신해 model(DataClass)에서 데이터를 생성해 데이터 모델타입 변수에 담는다.
통신이 성공하고 데이터 모델 타입 변수에 데이터를 담을때 비동기 처리를 위해 Closule를 사용한다.
⭐️Network 요청을 수행하고 결과를 closule를 통해서 받는 과정 중..
class ViewModel {
...
func tapped {
fetchMusic { [unowned self] result in
switch result {
case .success(let music):
self.music = music
case .failure(let error):
...
}
}
}
...
}
"Closure 값 캡처방식"
Closure는 내부 함수와 내부 함수에 영향을 미치는 주변 환경을 모두 포함한 객체
즉, 클로저 내부에서 변수가 사용되면 변수의 값이 캡쳐되었다고함.
func doSomething() {
var message = "message"
var num = 10 //클로저 범위 시작
let closure = { print(num) } //클로저 범위 끝
/*
num의 값을 클로저 내부적으로 저장하고있는데,
이것을 클로저가 num의 값을 캡쳐되었다 라고 표현한다.
*/
print(message)
}
"Closure 캡처 방식"
value/ Reference Type 관계없이 Reference Type로 캡처함.
// 보통
var numr: Int = 0
/*
num은 Int타입의 구조체 형식이므로 값(Value) 타입이기 때문에
값을 복사해서 저장함.(스택영역에 실제 데이터를 저장함)
*/
하지만 colsure는 캡처하는 값들을 참조하기 때문에 Reference Capture라고 함(힙에 저장됨)
"클로저의 캡처 리스트"
let closure = { [ num] in } 이렇게 사용하면 클로저에서도 Value Type로 사용가능하다. 이를 Value Capture라고 함.
이때, 클로저에 있는 [ num ]은 상수이다. 그리고 외부의 num을 참조하고 있지 않는다. (말 그대로 캡처를함.)
closure 내부에서 Value Caaptire 된 값을 변경할 수 없다.
why?
상수니까.
즉, 클로저는 기본적으로 Value Type의 값도 Reference Capture를 하지만,
클로저 캡처 리스트를 이용하면 Const Value Type으로 캡처가 가능하다.
근데... Reference Capture 즉 인스턴스의 캡처는 ARC의 개념이 필요하다.
class ViewModel {
var music: Music? // 데이터 모델
...
func tapped {
fetchMusic { [unowned self] result in
switch result {
case .success(let music):
self.music = music
case .failure(let error):
...
}
}
}
...
}
위 소스의 클로저는 self.music를 참조하고 있다.
이 말은 ViewModel의 인스턴스를 참조하고 있다는 말이다.
즉, Reference Type다.
"Reference Type의 값을 복사해서 캡처(Capture)하는 경우"
Reference Type의 값도 Capture Lists에 작성하면, Value Capture가 될까?
답은 안된다.
why?
클로저 캡처를 하면 Const Value Type을 캡처하면, Const Value Type으로 Capture가 되고,
Reference Type 하면 Reference Capture가 된다.
그럼 Reference Type을 하려면 클로저 캡처를 사용하지 않고 하면 되겠네?
라고 할 수 있겠지만, 여기서 클로저와 ARC의 관계의 이해가 필요하다.
⭐️ARC, 인스턴스의 Referrence Count를 자동으로 계산하며 메모리를 관리해 줌
클로저는 Reference Type을 캡처하면, 참조타입(인스턴스) 자체를 캡처한다.따라서 내부, 외부를 변경하면 동일한 변경이 이루어진다.
이걸 강한 참조 (strong reference)라 한다.
즉, 클로저는 참조 타입이므로 Hepp에 존재한다.
클로저 안의 self.music = music을 보면 self(인스턴스 자신)을 참조하고 있기 때문에 Reference Count가 증가한다.
그럼 이 강한 순한 참조는 어떻게 해결할까?
unowned or weak로 해결할 수 있다.
⭐️⭐️⭐️unowned & weak + Capture Lists
...
func tapped {
//[weak self]
fetchMusic { [unowned self] result in
switch result {
case .success(let music):
self.music = music
case .failure(let error):
...
}
}
}
...
unowned : 소유하지 않음이라는 뜻이다.
- unowned 참조는 클로저가 실행될 때 해당 인스턴스가 이미 메모리에서 해제되지 않았다는 것을 가정한다. 만약 해제가 됐다면 런타임 오류가 발생할 수 있다.
why?
이유는 항상 값을 가지는 것으로 간주되기 때문이다.
- weak는 옵셔널 참조이며, 참조하는 객체가 해제되면 nil이 됨
여기서의 참조는 closure에서 'self'(클래스 또는 구조체의 인스턴스)를 말한다.
즉, ViewModel의 인스턴스 더 자세히 말해 ViewModel.tapped를 가진 클래스나 구조체를 의미한다.
fetchMusic {...} 클로저는 통신의 성공여부에 따라 결과를 반환.
즉, 캡처리스트를 사용하는 이유는
⭐️ 강한 참조 순환 방지 & 인스턴스의 생명주기 관리
- unowned나 weak를 사용하여 약한 순한 참조를 해서 메모리 누수가 발생할 위험을 줄인다.
무튼, 위 과정을 거처 fetchMusic {...} 의 결과가 success 가되면 self.music에 데이터가 잘 담길거다.
즉, var music: Music? // Music Type 변수에 데이터가 잘 담긴다는 소리.
그럼 MVVM(2)에서 다음 이야기를...
⭐️ ⭐️ ⭐️ Closule Capture Lists, ARC, ViewModel
'iOS > TIL' 카테고리의 다른 글
[TIL] Combine (1) (1) | 2024.01.30 |
---|---|
[TIL] MVVM(3) (0) | 2023.12.05 |
[TIL] MVVM(2) (0) | 2023.11.28 |
[TIL] NaverMapAPI (0) | 2023.03.16 |