2024. 10. 27. 03:42ㆍApple/RxSwift
덤벼라 RxSwift..!!(잘 흡수해서 적용하게 해주세요..ㅎ..)
RxSwift 종류도 많고, 쓰임도 복잡해보여서 몇년동안 회피했던 라이브러리 중 하나였다.
비동기 프로그래밍의 중요성은 아래 포스팅에서도 언급했었기에, 더는 미룰수가 없었다.
동시성(Concurrency)을 대하는 Swift 의 자세 1 - DispatchQueue
(다음엔 Actor 와 Async 등에 대해서도 글을 써야할 것만 같은 느낌이 든다..😅)
다시 곰튀김님의 유튜브 종합편 강의를 살포시 클릭하여 들었다. (감사합니다.🐻 곰튀김님 🥹)
사실 애플에서 제공하는 async, sync 개념도 있고 Combine 도 있는데, 왜 RxSwift 였을까?
처음엔 채용공고에 많이 보여서, 예전 팀플 때 팀원들이 많이 써서..? 그래서 Rx를 공부해야 하는구나 했다.
곰튀김님 강의를 다시 보면서 RxSwift 가 제공하는 클래스를 쓰면 길어지는 코드가
효율적으로 줄어드는 것을 알게 되었다. 또한, 비동기, 동기일때 이미지를 불러들이는 속도가 달라진다는 것을
어렴풋이 알 수 있었다.
기본적으로 내가 알아야할 큰 틀에 대해 잡고 RxSwift를 적용해봐야할 것 같았다.
RxSwift 는 어떤 라이브러리야?
RxSwift 는 ReactiveX 에서 제공되는 라이브러리 중 Swift 플랫폼에서 쓸 수 있다.
observable
stream 을 비동기적으로 쓰는 API 라고 정의가 되어 있다.
Docs 를 파보면 Observable 이라는 타입이 있다. 사전적 의미로는 관찰가능한 이라는 뜻이다. Observable
사용시 동시적인 연산이 가능해진다. Observable 은 비동기적으로 다른 Task 가 끝날 때까지 기다릴 필요 없이 데이터를 방출하는 스트림을 나타내는 타입이다. 그럼 여기서 스트림이란 무엇인가?!
스트림(stream)은 시간의 흐름에 따라 발생하는 데이터의 연속적인 흐름을 의미한다. 특정 시작점부터 끝점까지 순차적으로 흘러가는 데이터를 의미한다. 그리고 이러한 데이터는 특정 이벤트가 발생할 때마다 방출되기도 한다.
위의 이미지에서는 타임라인, 아이템 방출, 변환과정, 완료, 오류 처리 등에 대해 설명하고 잇다.
일직선은 completed(완료), 에러 발생시에는 x 로 표시된다. 위의 flip 을 거치고 나서 기존의 데이터가 뒤집어져서 나온 것을 볼 수 있다.
다양한 연산자를 통해 Observable 을 조작하고 그 과정을 나타내는 이미지다. ReactiveX 의 연산 특징은 . 을 찍고 연산자 체이닝을 쓰는 것이 특징이다.
Observable 의 Life cycle
데이터를 처리하고 메모리를 관리한다면, 생성하고 관리하고 삭제하거나 종료하는 Life cycle 이 그래서 존재하는 것이다.
그럼 Observable 의 생애주기는 어떻게 흐를까? 자연의 이치와 비슷하게 바라본다면 데이터를 생성하고 그걸 구독하고 데이터를 방출(내보낼때) 호출되는 이벤트 등이 있을 것이다. 구독자는 이런 이벤트를 통해 데이터를 받는다는 것!
그리고 정상적으로 작업이 완료되었을 때는 종료도 해주어야 한다.
기본적으로 이미지를 업로드 한다는 가정하에 Observable 을 create(생성) 하고 Observable<UIImage>
타입으로 반환이 되는 함수가 있다고 가정한다.
🚽 Disposable 은 화장실에서 자주 봤던 단어인데, 사용 후 버리는 일회용의 사전적 의미로 쓰인다. 정확히는 ReactiveX 에서는 작업을 취소하거나 버릴 때 사용한다.
생성 후 쓰는 것은 just
, from
이 있는데 이 친구들의 차이라면 just
는 하나의 문자열을 쓸 때이며, from
은 여러 개의 배열을 생성할 때 인데, from
은 여러 타입의 배열이어도 상관 없다. 즉, Any
타입이여도 상관 없다는 뜻이다.
데이터의 변형은 mapping 되는 느낌으로 map
, flatMap
이 있고, 이는 swift 를 포함한 다른 프로그래밍 언어들의 고차함수에서도 자주 써봤던 것이다.
@IBAction func exFrom1() {
Observable.from(["왕보초", "RxSwift", 4, "Hours"])
.subscribe(onNext: { str in
print(str)
})
.disposed(by: disposeBag)
}
이런식으로 넣으면 개행되어 출력되고, just
는 배열의 형태나 문자열로 출력된다.
둘의 차이는 from은 타입이 하나로 한정되어 있지 않다는 것이다.
subscribe(on:)
은 쓰는 순간부터 동작이 된다고 한다.
스트림 → 오퍼레이터들(생성, 결합, 오류처리 등) →
최종적으로 얻은 그 데이터를 사용할 때 → subscribe
subscribe 를 사용하는 경우
1. 이벤트를 switch - case 로 처리할 때 받는 subscribe 형태 즉
⇒ 이벤트를 방출시에는 onNext, onError, onCompleted, onDisposed
등이 있다.
@IBAction func exFrom1() {
Observable.from(["RxSwift", "In", 4, "Hours"])
/*
.subscribe(onNext: { str in
print(str)
})
*/
/// 싱글은 하나만 들어와야하는데 여러개의 배열요소가 들어가기 때문에
/// 에러가 발생함.
//.single()
.subscribe { event in
switch event {
case .next(let str):
print("next: \(str)")
break
case .error(let err):
print("error: \(err.localizedDescription)")
break
case .completed:
print("completed")
break
}
}
.disposed(by: disposeBag)
}
2. 필요한 것만 구현
@IBAction func exFrom1() {
Observable.from(["RxSwift", "In", 4, "Hours"])
.subscribe(onNext: { s in
print(s)
}, onError: { err in
print(err.localizedDescription)
}, onCompleted: {
print("completed")
}, onDisposed: {
print("disposed")
})
.disposed(by: disposeBag)
}
3. 그냥 간단히 onNext 만 사용할 때
func output(_ s: Any) -> Void {
print(s)
}
@IBAction func exFrom1() {
Observable.from(["RxSwift", "In", 4, "Hours"])
.subscribe(onNext: output)
.disposed(by: disposeBag)
}
IBAction 함수라는 것은 메인스레드에서 맵, subscribe, filter, disposed 모든 것이 실행됨으로 이걸 concurrency thread 에서 실행하도록 한다.
이미지세팅은 다시 메인스레드에서 해야한다.
@IBAction func exMap3() {
Observable.just("800x600")
.observe(on: ConcurrentMainScheduler.instance)
.map { $0.replacingOccurrences(of: "x", with: "/") } // "800/600"
.map { "https://picsum.photos/\($0)/?random" } //"https://picsum.photos/800/600/?random"
.map { URL(string: $0) }
.filter { $0 != nil } // null 체크
.map { $0! } // URL!
.map { try Data(contentsOf: $0) }
.map { UIImage(data: $0) } // UIImage?
// 여기서부터는 다시 메인스레드, 그 이전은
// ConcurrentScheduler 에서 돌아간다.
.observe(on: MainScheduler.instance)
.subscribe(onNext: { image in
self.imageView.image = image
})
.disposed(by: disposeBag)
}
subscribe(on:)
subscribe 쓰는 순간 그때부터 동작되는 것
외부에서 작용하는 걸 허용하는 부분
.subscribe(on:)
과 .do(onNext:)
이런 구독된 데이터를 계속 지니고 있을 필요가 없을 때, 메모리 관리차원에서 DisposeBag
을 사용하는 것이고, 구독이 취소되는 경우에는 onDisposed
를 사용하게 된다.
다음에는 RxCocoa 와 활용하는 부분을 공부해야겠다. (이 강의의 흐름이 그렇기에.. 이 글에서 한번에 정리하기엔 긴 것 같았다.)
그 외에도 RxMarble 을 이용해 데이터의 흐름과 각각의 연산자들의 특징을 그림을 보면서 이해를 돋기에 큰 도움이 되었던 것 같다.
출처:
p.s. : 문제시 수정 및 삭제하겠습니다. 댓글 남겨주세요 :)
rxSwift 공부 소요시간: 2시간 가량
글쓰기: 2시간 가량
(남은 공부시간: 쭈욱..)
수정시간: 일단은 미정