apple/RxSwift, ReactorKit

[week1] Hello RxSwift 🖐

lgvv 2021. 7. 7. 18:03

✅ 이번 시간에는 RxSwift 기초부터 차근히 알아보고 지나갈 예정이야.

이전 포스팅에서 Rx에 대해서 공부한 자료가 있는데, 왜 Hello RxSwift라고 반문할 수 있겠지?

이전 시간에는 방대한 자료를 4시간만에 공부하다 보니까, 실제로 적용하는데, 어려움이 많았어.

그래서 다시 처음부터 돌아볼 필요성도 느꼈고, 하나하나 더 자세히 알고 있으면 어떠한 상황에서도 코드를 자유롭게 사용할 수 있으니까, 정석으로 공부해 보도록 하자!

 

시작하기에 앞서, 이 공부는 SOPT의 커리큘럼을 바탕으로 ReactiveX.io 공식 문서를 따르고 있어.

그리고 내가 보기에 필요한 부분만 축약해서 사용할 예정!! 

⭐️⭐️⭐️⭐️⭐️그리고 코드리뷰 형식으로 진행할꺼니까 하나하나 천천히 봐주길 바랄게!

 

⭐️🔸 공부 방법

SOPT 문서를 기반으로 이론 공부를 한 뒤, 코드를 작성해보면서 하나하나 직접 적용해보기로 한다.

 

http://reactivex.io/ 

 

ReactiveX

CROSS-PLATFORM Available for idiomatic Java, Scala, C#, C++, Clojure, JavaScript, Python, Groovy, JRuby, and others

reactivex.io

https://github.com/5anniversary/RxSwiftStudy

 

5anniversary/RxSwiftStudy

RxSwift를 공부하는 Repository입니다.🐍. Contribute to 5anniversary/RxSwiftStudy development by creating an account on GitHub.

github.com

 

(목차)

1. lazy 문법에 대해서 알아보자

2. 우리 코드에서의 lazy는 어떻게 쓰였을까?

3. Observer 및 operator 부분을 보자

4. 코드로 레이아웃을 잡는법.

5. 이론에서 주목할만한 점.

6. rx로 실행하면 어떤 결과가 나타날까?

 

 

✅ 11. lazy 문법에 대해서 알아보자

문법에 대해서 공부했었지만 다시금 짚고 넘어가자. lazy를 적극적으로 사용하지는 않아서 그냥 사용할 때 메모리에 잡히는 프로퍼티 정도로만 간단하게 생각하고 넘어갔었는데, week1 starter 파일을 열어보니, 이게 딱 있네?

그래서 공부할게 늘어서 넘 잘되었다는 생각으로 이번에 짚고 넘어가보려고!

 

"A lazy stored property is a property whose initial value is not calculated until the first time it is used"

 

애플 공식문서에 따르면 lazy문법이란 변수가 처음 사용되기 전까지 계산되지 않는다는 말이야

 

그럼 lazy를 왜 사용하는걸까? 사실 잘 이해가 되지 않잖아 그치?

lazy를 사용하는 이유에 대한 예시

위와 같은 이유로 사용된다고 해.

 

그렇다면 lazy를 사용하기 위해서 걸리는 몇가지 제약사항을 알아볼까?

1️⃣ lazy는 반드시 var와 함께 사용되어야 합니다.
 - lazy 는 반드시 var와 사용되어야 합니다. lazy는 우리가 처음 사용한 시점에 값이 생성되는데, let의 경우에는 프로그램이 실행될 때 초기에 동시에 되어야 함으로 lazy와 let은 공존할 수 없어!

2️⃣ lazy는 기본적으로 struct, class에서만 사용할 수 있다

 - lazy는 struct와 class에서만 사용할 수 있어.

3️⃣ lazy와 Computed Property

 - Computed Property에는 lazy를 사용할 수 없다. 생각해보면 쉬운데, lazy의 경우에는 사용될 때 값이 생성되는데, Computed Property는 사용할 때 마다 값을 연산해서 사용하잖아, 그러면 초기에 lazy로 선언하면 메모리에 올라오지 않은 상태에서 무언가 계산해서 사용해야한다? 말이 안돼!! 그래서 Computed Property에는 사용한 수 없다

4️⃣ lazy와 Closure

 - lazy에 연산을 통해 값을 넣어주는 방법도 존재하는데, 이는 closure를 활용하여 작성한다.

class나 struct의 다른 프로퍼티 값을 lazy 변수에서 사용하기 위해서 closure 내의 self를 통해 접근이 가능하다. 기본적으로 일반 변수들은 클래스가 생성된 후에 접근이 가능하기 때문에 클래스 내의 다른 영역(메소드, 일반 프로퍼티) 에서는 self통해 접근할 수 없지만, lazy라는 키워드가 붙으면 생성 후 추후에 접근할 것이라는 의미라서 closure내에서 self로 접근이 가능해!

 

아래 예시를 한번 보도록 하자!

class Person {
    var name:String
    
    lazy var greeting: ()->String = { [weak self] in
        return "Hello my name is \(((self?.name))!)"
    }
    init(name:String){
        self.name = name
    }
}

var me = Person(name:"John")

print(me.greeting()) // Hello my name is John

me.name = "James"

print(me.greeting()) // Hello my name is James

만일 변수가 lazy var greeting:String이 아닌 lazy var greeting:()->String으로 클로저 실행의 결과를 담는 것이 아닌 클로저 자체를 담고 있는 변수라면 반드시 [weak self]를 통해 메모리 누수를 방지해주어야 합니다. 

🔸이 부분을 유의깊게 봐야하는데 우리가 rx에서 메모리 누수에 대해서 보았었는데, 클로저에는 이스케이핑과 논이스케이핑이 있다. 근데 클로저는 escaping이 디폴트 임으로 우리가 메소드가 끝나도 별도의 공간에 존재하게 되는데, 메소드가 종료되더라도 클로저에서 메모리를 차지하고 있다는 말이다. 근데 이게 중간에 죽었으니 클로저는 어디로 갈까? 반환이나 등등은 하지 않는데, 붙잡혀 있으니 메모리 누수가 발생하는 것이다. 그래서 weak self로 메모리 누수를 막아야 한다.

 

또한 값이 아닌 클로저 자체가 메모리에 올라가 있고 self 는 내부에서 계속해서 클래스를 참조하기 때문에 계속 John이 출력이 되는 것이 아닌 James가 출력이 되는 것입니다.

🔸이것 또한 init을 통해 참조하는 것이니 생각해둘것!

 

✅ 2. 우리 코드에서의 lazy는 어떻게 쓰였을까?

우리 코드에서 lazy를 사용한 방법

이번에는 이미지로 가져왔다!

주목할만한 점은 lazy var로 선언하면서 그 아래에 같은 이름으로 let을 사용한 것을 볼 수 있을 것이다. 이거 안하면 실행시 error나서 앱이 다운되는 현상이 발생할 수도 있다.

❗️할 수도 있다이지 발생한다가 아님을 주의!

왜냐하면 실습코드에서는 ViewDidLoad()에서 코드로 레이아웃을 잡아 주었는데, 이때 스위치, 버튼, 텍스트 필드에 대해서 참조를 통해 처리해주고 있다. 

그러니까, 사용하진 않더라도 메모리에 올라간 시점에, 버튼, 스위치, 텍스트 필드에 대한 참조가 이루어 지는데, lazy 키워드가 viewDidLoad보다 나중에 작동하므로 에러가 발생한다는 것이다.

✅ (실험)

1. viewDidLoad에서 layout() 부분을 주석처리하고, let으로 선언된 부분 또한 주석처리 하였다. -> 앱이 정상적으로 실행 된다.

2. let으로 선언된 부분만 주석처리 하였다 -> 앱이 실행중에 다운된다.

 

근데 여기서 보면 주목할게 [weak self] in 이 없지? 분명히 클로저를 사용하면 메모리 누수를 막기 위해 있어야한다고 했는데 말이야. 그 이유는 rx에서 제공해주는 disposeBag을 이용해 다른 곳에서 메모리를 헤제하고 있기 때문이다.

 

✅ 3. Observe 및 operator 부분을 보자

override func viewDidLoad() {
        super.viewDidLoad()
        
        layout()

        // Observer
        button.rx.tap.subscribe(onNext: { next in
            // tap시 textField를 초기화시켜준다
            self.textField.text = ""
        }).disposed(by: disposeBag)

        // Operator
        textField.rx.text
            .filter { text in
                if text == "appear" {
                    self.button.isHidden = false
                    return false
                } else if text == "disappear" {
                    self.button.isHidden = true
                    return false
                } else {
                    //print("filter")
                    return true
                }
            }.subscribe(onNext: { text in
                print(text ?? "")
            })
        
        toggleSwitch.rx.isOn
            .subscribe( onNext : { enabled in
                print( enabled ? "It's ON" : "It's OFF")
            }).disposed(by: disposeBag)
    }

위의 코드들은 viewDidLoad 쪽에 작성되어 있다.

viewDidLoad 쪽에서 layout() 코드를 제외하고 바깥으로 빼고려고 했으나, 코드로 버튼을 배치하고 작성하는게 처음이라, 우선 안에서 작성해서 작동시키기로 했다.

저기서 볼것이 (변수명.rx.액션) 이 부분에서 rx라는 표현인데 RxCocoa에서 제공하는 것이며, UI관련 처리를 간편하게 도와주는 도구이다. 그리고 크게 볼건 없고 .subscribe와 onNext를 통해 코드를 작성하고, 마지막에는 디스포즈드를 활용해 메모리 누수를 막는 것을 볼 수 있다.

 

또한 텍스트필드 쪽에서 주목할만한 스킬은 map이 아닌 filter를 사용했다는 것인데, map으로도 같은 동작을 처리할 수 있지만, map의 경우에는 조건에 맞는지 계속해서 맞춰보서 filter안에 있는 구문을 지속적으로 실행한다는 점이다. 하지만 filter의 경우는 조건이 맞지 않으면 아예 들어오지 않아서 훨씬 더 효율적인 방법이라고 생각할 수 있다.

 

✅ 4. 코드로 레이아웃을 잡는법.

func layout() {
        view.addSubview(textField)
        view.addSubview(button)
        view.addSubview(toggleSwitch)
        
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        textField.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        textField.widthAnchor.constraint(equalToConstant: 300).isActive = true
        textField.heightAnchor.constraint(equalToConstant: 40).isActive = true
        
        textField.backgroundColor = UIColor.blue.withAlphaComponent(0.1)
        textField.layer.cornerRadius = 5
        
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 100).isActive = true
        button.widthAnchor.constraint(equalToConstant: 300).isActive = true
        button.heightAnchor.constraint(equalToConstant: 40).isActive = true
        
        button.backgroundColor = UIColor.black.withAlphaComponent(0.7)
        
        toggleSwitch.translatesAutoresizingMaskIntoConstraints = false
        toggleSwitch.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        toggleSwitch.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 200).isActive = true
        
        //toggleSwitch.backgroundColor = UIColor.red.withAlphaComponent(0.7)
        //toggleSwitch.layer.cornerRadius = 16
        toggleSwitch.onTintColor = UIColor.blue.withAlphaComponent(0.7)
    }

코드로 레이아웃을 잡는다니,, 사실 애니메이션 할때도 레이아웃은 스토리보드에서 잡아서 간단하게 해봤는데 이건 정말 신세계였어.

요즘 개발 공부가 진짜 말도 안되게 재미있는데, 또 새로운 것을 공부한다니까 더욱 ㅎ-ㅎ

버튼, 토클, 텍스트 필드 이렇게 세개인데, 세개가 비슷하니까 텍스트 필드를 기준을 코드를 설명해 보겠어

일단 뷰에 서브뷰로 넣어서 아이템들을 추가해주고,

 - translatesAutoresizingMaskIntoConstraints 코드의 경우에는 아래 참고한 부분을 찾아봐.

쉽게 말해서 코드로 레이아웃을 작성할 때, 충돌을 피하고 싶으면 이걸 넣어주면 돼.

 - centerXAnchor 이 코드 같은 경우에는 뷰의 수평축 중심을 나타내주는 코드야

 - equalToConstant 이 코드는 너비와 높이를 지정해주는 코드야.

 - withAlphaComponent 는 투명도를 주는 코드야

 - 그리고 대망의 isActive 이건 레이아웃을 자동으로 해주는게 아니라 설정했으면 레이아웃이 활성화되라고 알려주는 코드야

 

✅ 5. 이론에서 주목할만한 점.

1️⃣ finite observable sequences 🆚 Infinite observable sequences

 - Finite observable sequences (서버 통신과 같이 한정되어 있는 경우)

설명!

 - Infinite observable sequences (UIcomponent와 같이 끝이 없이 이어지는 경우)

 

설명2

그러니까 Infinite쪽은 무한히 캡쳐링이 가능해서, error이나 onComplete가 절대 발생하지 않을 것이기 때문에 생략할 수 있다.

 

✅ 6. rx로 실행하면 어떤 결과가 나타날까?

실시간으로 반응을 하네?!

이렇게 실시간으로 글자 하나하나마다 처리할 수 있다는 장점이 존재한다...!

이걸 다른 프로젝트에서 Rx없이 구현했다니... 아무튼 rx 짱

 

 

 

그럼 RxSwift 첫 시간은 여기서 마치도록 할게!

 

 

 

(참고)

https://baked-corn.tistory.com/45

 

[Swift] lazy Variables

Lazy variables 이전의 글들을 보셨거나 스위프트 문법 공부를 해보신 분들이라면 스위프트에서 메모리는 굉장히 예민한 주제인 것을 알 수 있습니다. 저 역시 그렇게 느꼈고, 그런 예민함이 보다

baked-corn.tistory.com

https://zeddios.tistory.com/474

 

iOS ) translatesAutoresizingMaskIntoConstraints

안녕하세요 :) Zedd입니다. 오늘은...! translatesAutoresizingMaskIntoConstraints..!을 정확히 알아보는 공부~.~ translatesAutoresizingMaskIntoConstraints 사실 엄청 많이 보셨을 코드에요. self.view.trans..

zeddios.tistory.com

 

'apple > RxSwift, ReactorKit' 카테고리의 다른 글

[week4] Filtering Observables  (0) 2021.07.10
[week3] Subjects  (0) 2021.07.10
🐉 RxSwift(Operators) Creating Observables  (0) 2021.07.09
[week2] 👀 Observserbles  (0) 2021.07.08
RxSwift 4시간 만에 끝내기  (0) 2021.07.06