본문 바로가기
RxSwift

[RxSwift] 곰튀김님의 RxSwift 4시간 끝내기 정리 - 4교시

by beansBin 2021. 11. 8.

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을 할당해서 메모리 참조가 끊긴 것 처럼 보이지만

실제로는 여전히 존재하고 있는 메모리 누수가 발생한다.

여전히 메모리에 존재하는 Class1, Class2의 인스턴스들..

 

이러한 메모리 누수는 약한 참조를 사용함으로써 해결할 수 있다.

변수 앞에 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 관계, 의존성이 없어 독립성을 띄므로 유지보수 및 관리에 용이한 장점

댓글