Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] RxSwiftExt로 syntactic sugar 코딩하기 본문

iOS 응용 (swift)

[iOS - swift] RxSwiftExt로 syntactic sugar 코딩하기

jake-kim 2023. 1. 14. 23:25

* syntactic sugar: 기능은 동일하지만 장황하지 않게 코딩하는것

RxSwiftExt

  • RxSwiftExt는 RxSwift에 연산자를 extension하여 여러가지 유용한 연산자를 제공
  • 보통 RxSwift에서 extension으로 직접 custom하여 연산자를 정의하여 사용해도 되지만, 오픈소스를 사용하면 처음 오는 개발자도 해당 오픈 소스를 알고 있다면 쉽게 바로 사용할 수 있는 장점이 존재
  • RxSwiftExt를 알고 있으면 장황한 코드들을 더욱 단순하게 처리할 수 있는 점이 존재

RxSwift 연산자를 선언적으로 사용하기

  • '무엇'이 아닌 '어떻게'에 대한 내용을 선언적으로 표현하는게 핵심
  • 피해야하는 코드
    • keypath를 사용할 수 있지만, 클로저를 사용하는 코드 
    • 쓸모없는 map { } 중괄호를 사용하는 코드
    • 한 줄로 표현할 수 있는 것들을 두 줄 이상으로 표현하는 코드

RxSwiftExt의 유용한 연산자

  • framework 준비
target 'ExRxSwiftExt' do
  use_frameworks!
  
  pod 'RxSwift'
  pod 'RxSwiftExt'
end
  • unwrap()
    • compactMap과 클로저를 사용하지 않고 unwrap()으로 바로 사용
private func unwrap() {
    let observable = Observable.of(Int?(1),2,nil,4,5)
    
    // before
    observable
        .compactMap { $0 }
        .debug()
        .subscribe()
        .disposed(by: disposeBag)
    
    // after
    observable
        .unwrap()
        .debug()
        .subscribe()
        .disposed(by: disposeBag)
}
  • retry()
private func retry() {
    let observable = Observable<Int>.error(NSError(domain: "error", code: 1))
    
    // .immediate: 즉시 반복
    observable
        .retry(.immediate(maxCount: 2))
        .debug()
        .subscribe()
        .disposed(by: disposeBag)
    
    // .delayed: n초 후 반복
    observable
        .retry(.delayed(maxCount: 2, time: 3)) // 3초 후 한번 더 시도
        .debug()
        .subscribe()
        .disposed(by: disposeBag)
    
    // .customTimerDelayed: retry 시간 커스텀 + 클로저
    observable
        .retry(.customTimerDelayed(maxCount: 2, delayCalculator: { count in
            return .seconds(3)
        }))
        .debug()
        .subscribe()
        .disposed(by: disposeBag)
    
    // .exponentialDelayed: 지수 증가 (시간 - initial^mutiplier)
    observable
        .retry(.exponentialDelayed(maxCount: 2, initial: 3, multiplier: 2)) // 9초 후 한번 더 시도
        .debug()
        .subscribe()
        .disposed(by: disposeBag)
}
  • pairwise()
    • 이전에 방출된 값을 알 수 있기 때문에 매우 유용
private func pairwise() {
    let observable =  Observable.of(true, false)
    
    // before x
    
    // after
    observable
        .pairwise()
        .map { $0 == $1 }
        .subscribe()
        .disposed(by: disposeBag)
}
  • ignore()
private func ignore() {
    let observable = Observable.of(-1,1,2,3,4,5)
    
    // before
    observable
        .filter { $0 != -1 && $0 != 1 }
        .debug()
        .subscribe()
        .disposed(by: disposeBag)

    // after
    observable
        .ignore(-1, 1)
        .debug()
        .subscribe()
        .disposed(by: disposeBag)
}
  • mapTo()
private func mapToVoid() {
    let observable = Observable.just(1)
        
    // before
    observable
        .map { _ in () }
        .subscribe()
        .disposed(by: disposeBag)
    
    // after
    observable
        .mapTo(())
        .subscribe()
        .disposed(by: disposeBag)
}
  • count { condition }
private func count() {
    let observable = Observable.of(1,2,3,4,5)
    
    // before
    var count = 0
    observable
        .filter { $0 % 2 == 0 }
        .do(onNext: { _ in count += 1 })
        .subscribe()
        .disposed(by: disposeBag)
    
    // after
    observable
        .count { $0 % 2 == 0 }
        .subscribe()
        .disposed(by: disposeBag)
}

RxSwiftExt의 기타 연산자

  • not()
private func toggle() {
    let observable = Observable.just(false)
    
    // before
    observable
        .map { !$0 }
        .subscribe()
        .disposed(by: disposeBag)
    
    // after
    observable
        .not()
        .subscribe()
        .disposed(by: disposeBag)
}
  • and()
private func and() {
    let observable =  Observable.of(true, false)
    
    // before
    observable
        .pairwise()
        .map { $0 == $1 }
        .subscribe()
        .disposed(by: disposeBag)
    
    // after
    observable
        .and()
        .debug()
        .subscribe()
        .disposed(by: disposeBag)
}
  • catchErrorJustComplete
private func catchErrorJustComplete() {
    let observable = Observable<Int>.error(NSError(domain: "error", code: 1))
    
    // before
    observable
        .catch { error in
            .empty() // empty를 방출하면 onComplete도 방출
        }
        .subscribe()
        .disposed(by: disposeBag)
    
    // after
    observable
        .catchErrorJustComplete()
        .subscribe()
        .disposed(by: disposeBag)
}
  • filterMap { }
private func filterMap() {
    let observable = Observable.of(1,2,3,4,5,6)
    
    // before
    observable
        .filter { $0 % 2 == 0 }
        .map { $0 + 10 }
        .subscribe()
        .disposed(by: disposeBag)
    
    // after
    observable
        .filterMap { $0 % 2 == 0 ? .map($0 + 10) : .ignore }
        .subscribe()
        .disposed(by: disposeBag)
}
  • fromAsync
    • closure 메소드를 observable로 표현하는 방법
    • 인수는 Observable에서 받고, 클로저는 subscribe 블록에서 처리
private func fromAsync() {
    func someAsynchronousService(arg1: String, arg2: Int, completionHandler: (String) -> Void) {
    }
    
    // 1. 함수 이름을 fromAsync안에 삽입
    // 2. 파라미터는 뒤에 괄호로 추가
    // 3. 함수의 클로저는 subscribe안에서 처리
    Observable
        .fromAsync(someAsynchronousService)("jake", 0)
        .subscribe(onNext: { string in
            print(string)
        })
        .disposed(by: disposeBag)
}
  • partition
private func partition() {
    let observable = Observable.of(1,2,3,4,5)
    
    let (evens, odds) = observable.partition { $0 % 2 == 0}
    
    evens
        .subscribe()
        .disposed(by: disposeBag)
    
    odds
        .subscribe()
        .disposed(by: disposeBag)
}


* 전체 코드: https://github.com/JK0369/ExRxSwiftExt


* 참고

https://github.com/RxSwiftCommunity/RxSwiftExt

Comments