1교시, 2교시는 곰튀김님이 분류하신것이 아닌 제가 임의로 끊어들은 것을 기준으로 함을 알립니다,,🚨
곰튀김님의 RxSwift 정리 마지막 회차!
사실 강의는 저번 주에 다 들었지만 이번에는 시즌2에는 있지만 시즌 1에는 설명하지 않으신 내용을 보충 정리하려고 한다.!
순환참조와 메모리 관리 / Hot Observable, Cold Observable / MVVM이 메인 주제이다.
1. 순환참조와 메모리 관리
순환 참조는 RxSwift보다는 스위프트 문법에 해당하는 내용이다.
개념적인 내용과 RxSwift에서 어떻게 적용되는지를 한 번 알아보려고 한다.
- 스위프트는 메모리 관리를 위해 기본적으로 ARC(Automatic Reference Counting)를 사용한다.
- 인스턴스가 생성되면 메모리를 차지한다. 그런데 더 이상 사용하지 않는 인스턴스를 계속 해제하지 않는다면 메모리 누수(Memory Leak)가 발생한다. 그럼 앱이 죽게된다✨
- ARC는 레퍼런스 카운트를 이용해 메모리를 관리한다. 레퍼런스 카운트는 '객체의 주소를 참조하는 개수'이다. 객체는 주소에 의한 참조(Reference by address)이기 때문.
코드 예제를 통해 레퍼런스 카운트의 증감을 살펴보자.
// 예제를 위한 Person 클래스
class Person {
init() {
print("Person 클래스 생성")
}
deinit {
print("Person 클래스 해제")
}
}
var person1: Person! = Person() // Person 타입의 person1 객체 생성, RC++ --> RC == 1
person1 = nil // nil로 참조 해제 -> RC-- --> RC == 0
person1 객체를 생성한 시점에 해당 인스턴스가 메모리를 차지하면서 레퍼런스 카운트가 0에서 1로 증가한다. 해당 인스턴스의 주소를 가리키는 person1 변수가 하나 생겼기 때문.
그런데 아래에서 person1 변수에 nil 을 할당해주면 -> 해당 변수는 더 이상 이전 인스턴스의 메모리를 참조하지 않게 되어 레퍼런스 카운트가 감소되어 도로 0이 된다. 즉, 메모리에서 해당 인스턴스가 해제된 것.
하지만 해제 이전에 다른 변수(예를 들어 person2)에 person1을 할당하면 레퍼런스 카운트가 2가 되므로 -> person1 의 참조를 끊어도 메모리에서 해제되지 않는다!
이런 일반적인 객체의 할당과 소멸되는 참조 관계를 강한 참조라고 한다.!
그런데 이제 문제가 되는 것은 순환 참조(강한 참조 순환)에 의한 메모리 누수이다.
이는 약한 참조를 통해 해결할 수 있다.
- 순환 참조는 두 개 이상의 클래스가 서로를 참조할 때 발생한다.
- 순환 참조가 발생하면 서로가 서로를 참조하고 있으므로 임의로 해제하지 않는 한 레퍼런스 카운트가 0이 되지 않으므로 메모리 누수가 발생한다.
// Class1, 2 클래스 정의
class Class1 {
var secondClass: Class1!
}
class Class2 {
var firstClass: Class2!
}
// Class 1, 2 타입의 객체 생성
var first: Class1? = Class1()
var second: Class2? = Class2()
// 순환 참조 생성
first?.secondClass = second
second02?.firstClass = first
first = nil
second = nil
각 클래스 안에 상대 클래스를 참조하는 변수를 가지고 있는 클래스이다.
각 클래스를 할당하면 first 변수는 Class1의 인스턴스 메모리 주소를,
second 변수는 Class2의 인스턴스 메모리 주소를 가리킨다.
그런데 각 클래스의 멤버 변수에 상대 클래스를 할당하게 되면
해당 클래스가 상대 클래스를 참조하고 있는 모양새가 되어 각 클래스의 레퍼런스 카운트는 2가 된다.
따라서 자기를 참조하고 있는 변수의 참조를 끊어도 여전히 서로의 레퍼런스 카운트는 1이 된다!
따라서 명시적으로 코드에서는 각 변수에 nil을 할당해서 메모리 참조가 끊긴 것 처럼 보이지만
실제로는 여전히 존재하고 있는 메모리 누수가 발생한다.
이러한 메모리 누수는 약한 참조를 사용함으로써 해결할 수 있다.
변수 앞에 weak 키워드를 사용하게 되면 참조되는 객체가 nil이 되면 자기 자신인 변수도 nil처리를 해버리는 것.
class Class1 {
weak var secondClass: Class1!
}
RxSwift에서는 다음과 같은 상황에서 적용해볼 수 있다.
- stream의 subscribeOn의 onNext 파라미터 클로져 내부에서 클로져 바깥의 멤버 변수인 countLabel에 접근하고 있다.
- 해당 코드는 뷰 컨트롤러 내부에 쓰여져 있다.
- 소속된 뷰 컨트롤러는 네비게이션 컨트롤러 스택에 쌓인다.
이런 상황일 때, 해당 클로져는 RxSwift에 의해 비동기적으로 수행하는 코드이므로 소속된 viewController의 레퍼런스 카운트를 갖는다.
그리고 subscribeOn이 실행될 때 레퍼런스 카운트가 1 증가되고, 해당 레퍼런스 카운트는 completed될 때 초기화된다.
그런데 해당 코드가 실행되기 이전 레퍼런스 카운트가 1인 상태에서 이전 화면으로 돌아가면(네비게이션 pop)
여전히 레퍼런스 카운트가 1이 되어 메모리 누수가 발생하는 것.
...
.subscribeOn(onNext: { i in
self.countLabel.text = "\(i)"
})
...
클로져 구문은 다음과 같이 weak self 구문을 사용하여 약한 참조를 사용해주면 해결된다.
...
.subscribeOn(onNext: { [weak self] i in
self?.countLabel.text = "\(i)"
})
...
곰튀김님의 블로그 클로져와 메모리 실험 참고 링크 : https://iamchiwon.github.io/2018/08/13/closure-mem/
2. Hot Observable과 Cold Observable
: susbscribe되었을 때 이벤트들을 어떤 기준으로 방출할 것인지를 정하는 것.
1) Hot Observable : subscribe되었을 때부터 이벤트를 방출하는 방식. 이전에 발생한 이벤트에 대해서는 observer들에게 전달하지 않는다.
ex) 실시간 방송처럼 이전 방송분은 볼 수 없는 것
2) Cold Observable : subcribe에 상관 없이 이벤트를 방출, observer는 subscribe 되기 전 이벤트도 받아볼 수 있음.
ex) 일반 동영상처럼 이전으로 돌아가서 다시 볼 수 있는 것
3교시에 배웠던 subject들의 종류를 생각해보면 hot observable과 cold observable의 차이를 좀 더 명확하게 이해할 수 있을 것 같다.
Publish Subject는 Hot Observable, Replay Subject는 Cold Observable인 것..
3. MVC, MVP, MVVM의 차이점
- MVC(모델-뷰-컨트롤러) : 보편적으로 많이 사용되는 소프트웨어 디자인 패턴
- Model : 데이터와 명령을 담당
- View : 화면과 사용자 인터페이스를 담당
- Controller : 모델과 뷰 사이의 명령을 라우팅을 담당
* MVC의 동작 : View에서 사용자가 입력 -> Controller가 입력을 받아 처리, Model을 변경하고 View가 나타냄
* Controller는 여러 개의 뷰를 가질 수 있음.
=> 컨트롤러가 많은 부담을 가지고 있는 패턴, 그리고 View와 Model의 높은 의존성(View가 직접 Model을 보여주므르) - MVP(모델-뷰-프레젠터) : MVC처럼 모델과 뷰는 동일하나 controller 대신 presenter를 사용하는 패턴
- Presenter: 뷰에서 요청한 정보로 Model을 가공하여 뷰에게 전달
=> UIViewController가 View로 포함 : 입력은 view가 받되 처리는 presenter에게 알려주는 것.
=> View와 Model의 의존성을 줄여주므로 소프트웨어가 커졌을 때 유지보수가 용이해진다.
=> View - Presenter는 1:1 관계이다. 대신 View-Presenter 사이의 의존성이 생긴다는 단점
- MVVM(모델-뷰-뷰 모델) : MVC처럼 모델과 뷰는 동일하나 view model을 사용하는 패턴
- view model : view를 표현하기 위해 만든 View를 위한 Model. View를 나타내기 위한 Model이자 View를 나타내기 위한 데이터 처리를 하는 부분
=> 화면을 그리는 것은 완전히 view에게 할당하는 것.
=> view로부터 입력 action을 받아 model에게 데이터를 요청 및 받은 데이터를 처리, view는 view model과 binding하여 데이터를 보여준다.
=> view - view model은 1:1 관계, 의존성이 없어 독립성을 띄므로 유지보수 및 관리에 용이한 장점
'RxSwift' 카테고리의 다른 글
[RxSwift] 알엑스로 마리모 다이어리 v1.1 리팩토링하기 - 1 (0) | 2021.12.20 |
---|---|
[RxSwift] 곰튀김님의 RxSwift 4시간 끝내기 정리 - 3교시 (0) | 2021.11.01 |
[RxSwift] 곰튀김님의 RxSwift 4시간 끝내기 정리 - 2교시 (0) | 2021.10.26 |
[RxSwift] 곰튀김님의 RxSwift 4시간 끝내기 정리 - 1교시 (0) | 2021.10.12 |
댓글