[SeSAC] November 1, 2023

✔︎ 오늘의 정리

  • 사용자가 라이트 / 다크 모드를 바꿀 때 대응하기
  • 스토리보드 코드로 화면 전환하기
  • RxSwift
    • Observable / Observer
    • subject
    • bind
    • withUnretained, subscribe(onNext:...) / subscribe(with:...) / subscribe(onNext:...)

 


사용자가 라이트 / 다크 모드를 바꿀 때 대응하기

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
            loadData(date: selectedDate)
        }
    }

지난번에 쇼핑 프로젝트를 진행할 때 다크 모드 대응에서 사용했던 친구인데 다시 사용했다

colorSet으로 각 다크모드 / 라이트모드마다 대응해놓기는 했는데 layer로 가장 마지막 셀의 구분선을 가려놓은 게 가린 상태에서 사용자가 모드를 바꾸게 될 경우에는 적용된 레이어 색깔이 바뀌지 않아 그 부분을 대응해 줬다.

 

근데 이 친구가 익숙하지 않아서 ㄱ- traitCollection이 뭔데??? 하고 찾아보니...

 

 

공식 문서를 보자면 다음과 같다... ㅇ.ㅇ

 

나는 이 친구가 다크모드 라이트모드 바뀔 때 사용하는거구나~ 햇는데 그 관련된 부분이 단순히 color뿐만 아니라 다른 게 많은 것 같다 horizontal, vertical size부터 device size까지 여기서 관리하는듯?! 

 

자세한 설명은 아래에... ^___^

 

 

 

https://hcn1519.github.io/articles/2020-03/ios_darkmode

 

iOS 다크모드 알아보기

iOS에서 다크모드를 적용하기 위해 알아야 하는 내용을 정리하였습니다.

hcn1519.github.io

 

https://velog.io/@wansook0316/UITraitCollection

 

UITraitCollection

회전과 관련된 코드를 보다보니, compact, regular와 같은 용어들이 보였다. Dark Mode와도 관련이 있다던데, UITraitCollection은 무엇일까?

velog.io

 

 

 

 

 

 

스토리보드 코드로 화면 전환하기

분명 익숙하게 썼던 코드인데 올만에 쓰려구 하니까 기억이 안 나는지 ㅜ,ㅜ

코드베이스 ,,,,,, 영 레이아웃 잡는 속도가 느린 거 같길래 연습겸 스토리보드로 안 하고 코드베이스로 작성하고 있었는데 간단한 UI ,, 그냥 스토리보드가 빠르지 않을까? 싶어서 스토리보드로 만들고 연결하려고 하니 화면전환 코드를 까묵엇더라

레존드

 

 

 

 

RxSwift

ㅜㅜ 정리하던 거 다 날아갔다......

설명 열심히 써놨는데 걍 다 생략하고 기록용으로 적어놔야겠음

 

Observable / Observer

Observable: event를 emit(방출)함

Observer: Observable에서 emit한 event를 받아서 처리함

 

보통 Observable과 Observer를 통해 Stream이라고 부르는 데이터의 흐름을 통제할 수 있고, Operator를 통해 Stream을 조작, 변경할 수 있다구 한다.

다만 Observable과 Observer가 각각 따로 존재한다면 아무 소용이 없고 Observable -> ( subscribe ) -> Observer를 해야 이벤트가 온전히 처리될 수 있다.

 

이 둘만 있으면 왠지 ,, 완벽할 것 같지만?

Observable과 Observer는 큰 문제가 있다.

바로 Observable은 event를 emit만 할 수 있지 처리는 하지 못하고, Observer는 event를 처리할 수 있지만 Observable처럼 다른 Observer에게 전달하지는 못한다. (emit 불가~~!!)

 

따라서, Observer + Observable 역할을 할 수 있는 Subject가 나오게 되는데... ...

 

=> 헐. 이래서? Subject 자리에 Observable을 쓰게 된다면 

 

이러한 오류가 나는 것이다.

onNext는 Observer가 이벤트를 처리하는 오퍼레이터인데 emit, 즉 방출만 하는 친구한테 이것 좀 처리해 달라고 하고 있으니까~~!

 

 

참고! Observer는 이벤트를 처리하는 상태를 세 가지로 나누는데...

  • onNext
    • 처리?
  • onComplete
    • 완료!
  • onError
    • 처리 못함!

dispose의 경우에는 메모리에 반환되었을 때 확인하려고 사용한다구 한당

상태는 총 세 가지!!!! 라고 함

(추후 추가된 부분이에용)

  • onDispose
    • 처리 후 메모리에서 완전히 삭제~

 

 

Subject

 

    // Subject: Observable + Observer (이벤트를 전달 + 처리 가능)
    let publish = PublishSubject<Int>() // 초깃값을 설정해 주지 않아도 ㄱㅊ
    let behavior = BehaviorSubject(value: 200) // 초깃값 설정해 주어야 함

 

    func practiceSubject() {
        
        // subscribe 안 해줘서 아~무 의미 없음
        publish.onNext(20)
        publish.onNext(21)
       	
        // subscribe 이후부터 값 받아욤
        publish
            .subscribe { value in
                print("publish - \(value)")
            } onError: { error in
                print("publish - \(error)")
            } onCompleted: {
                print("publish completed")
            } onDisposed: {
                print("publish disposed")
            }
            .disposed(by: disposeBag)
        
        // 여기부터 print!
        publish.onNext(1)
        publish.onNext(2)
        publish.onNext(3)
        // 여기까지 print!
        publish.onCompleted()
        
        // 반영 X
        publish.onNext(100)
        publish.onNext(200)


        behavior.onNext(201)
        behavior.onNext(202)
        // 202만 초깃값으로 반영
        
        behavior
            .subscribe { value in
                print("behavior - \(value)")
            } onError: { error in
                print("behavior - \(error)")
            } onCompleted: {
                print("behavior completed")
            } onDisposed: {
                print("behavior disposed")
            }
            .disposed(by: disposeBag)
        
        // 여기부터 print!
        behavior.onNext(403)
        behavior.onNext(404)
        behavior.onNext(500)
        // 여기까지 print!
        behavior.onCompleted()
        
        // 반영 X
        behavior.onNext(600)
    }

 

 

  • Publish
    • 초기값이 없는 빈 상태로 시작한다.
    • subscribe 이전에 emit한 event들은 처리하지 않는다!
    • subscribe 이후 emit 되는 event들만 처리한다.
  • Behavior
    • 초기값이 있는 상태로 시작한다.
      • 초깃값 생성이 필수!!! 이다. ^_^
    • subscribe 이전에 가장 마지막으로 emit한 event를 초깃값으로 갖는다.
    • 만약 subscribe 이전에 emit한 event가 없다면 초기화 시 지정해 준 초깃값을 기본으로 갖는다.
  • Replay
  • Async

 

 

 

 

 

 

 

bind

 

 

        button.rx.tap
            .observe(on: MainScheduler.instance) // UI
            .subscribe { _ in
                self.label.text = "안뇽하세요?"
            }
            .disposed(by: disposeBag)

        button.rx.tap
            .bind(with: self) { object, _ in
                object.label.text = "안뇽하세요?"
            }
        
        button.rx.tap
            .map({ "안뇽하세요?" }) // 데이터 전달인듯?!
            .bind(to: label.rx.text) // bind...
            .disposed(by: disposeBag)

 

위 세 코드는 모두 같은 동작을 한다.

.observe~ 코드는 dispatchQueue.main.async { }와 같은 역할을 한다구 보면 된당

mainThread에서 동작할 수 있게끔 넘겨주는 코드이다! 

 

근데 어차피 일케 쓸 수 있는데 뭔 차이냐?! 하고 묻는다믄.

subscribe의 경우에는 Observable에서 흔하게 사용하는 구독 Operator로 아까 말했듯 3가지의 상태를 가진다.

onNext / onComplete / onError 일케!

 

근데 꾸준히 사용자의 이벤트를 emit해야 하는 UI 객체들의 경우에는 onComplete될 일도 onError가 날 일도 없을 것이다.

따라서 subscribe의 onComplete / onError 없이 UI 요소만 간소화시킨 것이 bind이당 

 

 

 

 

흠. 긍까 어떤 식으루 사용할 수 있냐믄

 

nextButton을 눌렀을 때 가입 완료! 라는 alert을 띄워주고 싶다고 하자.

 

    func showAlert() {
        let alert = UIAlertController(
            title: "가입 완료!",
            message: "This is wonderful",
            preferredStyle: .alert
        )
        let defaultAction = UIAlertAction(title: "Ok",
                                          style: .default,
                                          handler: nil)
        alert.addAction(defaultAction)
        present(alert, animated: true, completion: nil)
    }

 

고럼 요 showAlert method를 불러주면 되겠군아.

 

 

일반적인 방식으로 한다면...

nextButton.addTarget(self, action: #selector(nextButtonClicked), for: .touchUpInside)
    @objc func nextButtonClicked() {
        print("가입완료")
    }

 

이렇게 할 수 있을 것이다.

 

 

만약에 RxSwift를 이용한다믄?

 

        nextButton.rx.tap
            .observe(on: MainScheduler.instance)
            .subscribe(with: self) { object, _ in
                object.showAlert()
            }
            .disposed(by: disposeBag)
        
        nextButton.rx.tap
            .bind(with: self) { Object, _ in
                Object.showAlert()
            }
            .disposed(by: disposeBag)

 

요렇게 표현해 줄 수 있다.

 

 

 

그러믄

 

일케 alert이 잘 뜨는 걸 볼 수 있다.

 

 

 

 

 

 

 

withUnretained, subscribe(onNext:...) / subscribe(with:...) / subscribe(onNext:...)

 

위 세 개는 무슨 차이일까?

 

결론부터 말하자면, 메모리 누수를 막기 위해서 withUnretained와 subscribe(with:)를 이용하는 것이다! 

 

근데 왜 메모리 누수가 나는데?

 

swift는 ARC를 이용해서 메모리를 관리하는데, 클로저에서 self keyword를 사용하게 된다면 메모리 누수가 날 수 있
다.

 

왜 self keyword가 메모리 누수를 나게 하는데? 클로저 안에서 self를 쓰면 서로의 변수를 초기화하여 참조를 끊어도 서로가 참조된 채로 남아 있는 순환 참조가 발생할 수 있기 때문이다. 

 

참고로 ARC와 순환 참조의 자세한 설명은 요기루~~!

https://blog.naver.com/01unknown/223147022924

 

[Swift] ARC(Automatic Reference Counting) (1/2)

면접을 봤는데, 분명!!!! ㅠㅠ 알고 있는 지식이라고 생각했는데 긴장해서 어버버~ 하느라 대답을 잘 못한 ...

blog.naver.com

 

 

 

암튼. Rx의 예시를 보자.

        button.rx.tap
            .observe(on: MainScheduler.instance)
            .subscribe { _ in
                self.label.text = "안뇽하세요?" // 얼레 이러면 순환참조가?
            }
            .disposed(by: disposeBag)
        
        button.rx.tap
            .withUnretained(self) // 순환참조 방지!
            .observe(on: MainScheduler.instance) // UI
            .subscribe(onNext: { owner, _ in // self 대신 owner(object)가 생겼다.
                owner.label.text = "안뇽하세요?" // 만약 여기서 정의한 변수 말고 self 쓰면 말짱도루묵~~!
            })
            .disposed(by: disposeBag)
        
        button.rx.tap
            .observe(on: MainScheduler.instance) // UI
            .subscribe { [weak self] _ in // weak self로 순환 참조를 방지할 수도 있다. (당연함)
                self?.label.text = "안뇽하세요?"
            }
            .disposed(by: disposeBag)
        
        button.rx.tap
            .observe(on: MainScheduler.instance) // UI
            .subscribe { [weak self] _ in
                guard let self else { return } // 물음표가 싫다면 일케 미리 guard let으로 풀어줘도 굿
                self.label.text = "안뇽하세요?"
            }
            .disposed(by: disposeBag)
        
        button.rx.tap
            .observe(on: MainScheduler.instance) // UI
            .subscribe(with: self, onNext: { owner, _ in // with를 써준다면 역시 순환참조 방지!
                self.label.text = "안뇽하세요?"
            })
            .disposed(by: disposeBag)
        
        button.rx.tap
            .bind(with: self) { object, _ in // bind도 마찬가지이당 
                object.label.text = "안뇽하세요?"
            }
        
        button.rx.tap
            .map({ "안뇽하세요?" })
            .bind(to: label.rx.text) // 이런 식으로 데이터를 전달해줘도 순환 참조를 막을 수 있다고?
            .disposed(by: disposeBag)

 

 

 

 

 

 

 

 

 

과제 끝낸 것,,

 

 

 

 

    func bind() {
        
        nickname
            .bind(to: nicknameTextField.rx.text)
            .disposed(by: disposeBag)
        
        isHidden
            .bind(to: nextButton.rx.isHidden)
            .disposed(by: disposeBag)
        
        nickname
            .map({ $0.count > 1 && $0.count < 6 })
            .bind(with: self, onNext: { object, value in
                object.isHidden.onNext(!value)
            })
//            .subscribe(with: self, onNext: { object, value in
//                object.isHidden.onNext(!value)
//            })
            .disposed(by: disposeBag)
        
        
        nicknameTextField.rx.text.orEmpty
            .bind(to: nickname)
            .disposed(by: disposeBag)
    }

 

 

초반에 계속 안 돼서 ㅜㅜ 왜 안 되지?!?!? 했는데 사유: 마지막 nicknameTextField를 nickname과 bind해주는 코드가 없었다. 

우아앙...

이... 이거 이렇게 넣음 대나? 하고 넣었더니 됐음

우와~

 

변수를 outlet과 바인딩하는 것 말고도 outlet과 변수를 bind해 주는 걸 까묵지 말아야 한다...

 

 

'TIL' 카테고리의 다른 글

[SeSAC] November 10, 2023  (0) 2023.11.10
[SeSAC] November 6, 2023  (0) 2023.11.06
[SeSAC] October 24, 2023  (5) 2023.10.24
[SeSAC] October 19, 2023  (3) 2023.10.19
[SeSAC] October 15, 2023  (2) 2023.10.16