✔︎ 오늘의 정리
- 사용자가 라이트 / 다크 모드를 바꿀 때 대응하기
- 스토리보드 코드로 화면 전환하기
- 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 |