[TIL] RxSwift 4시간에 끝내기

승아·2021년 5월 10일
0

RxSwift 4시간에 끝내기를 참고하였습니다.

✅⠀RxSwift란?

RxSwift를 활용하지 않고 서버에서 이미지 가져오기

  • 동기
guard let url = URL(string: imageUrl) else { return nil }
guard let data = try? Data(contentsOf: url) else { return nil }
let image = UIImage(data: data)
imageView.image = image
  • 비동기
DispatchQueue.global().async{
    guard let url = URL(string: imageUrl) else { return nil }
    guard let data = try? Data(contentsOf: url) else { return nil }
    let image = UIImage(data: data)
    
    DispatchQueue.main.async {
        self.imageView.image = image
    }
}

RxSwift를 활용하여 서버에서 이미지 가져오기

지금은 이미지만 가져오는 간단한 작업이라 코드가 간결해보이지 않지만 복잡한 작업을 할 때 유용하다고 한다.

rxswiftLoadImage(from: LARGER_IMAGE_URL)
    .observeOn(MainScheduler.instance)
    .subscribe({ result in
        switch result {
        case let .next(image):
            self.imageView.image = image

        case let .error(err):
            print(err.localizedDescription)

        case .completed:
            break
        }
    }).disposed(by: disposeBag)
    
    
func rxswiftLoadImage(from imageUrl: String) -> Observable<UIImage?> {
    return Observable.create { seal in
        asyncLoadImage(from: imageUrl) { image in
            seal.onNext(image)
            seal.onCompleted()
        }
        return Disposables.create()
    }
}

subscribe

작업의 시작을 알림. subscribe은 Disposable을 반환한다.

func subscribe(_ on: @escaping (Event<UIImage?>) -> Void) -> Disposable

Dispose

  • Disposable을 직접 취소
var disposable: Disposable?

let disposable = rxswiftLoadImage(from: LARGER_IMAGE_URL)
    .observeOn(MainScheduler.instance)
    .subscribe({ result in
        switch result {
        case let .next(image):
            self.imageView.image = image

        case let .error(err):
            print(err.localizedDescription)

        case .completed:
            break
        }
    })
    
disposable?.dispose() // cancel
  • DisposeBag을 활용한 취소
var disposable: Disposable?
var disposeBag: DisposeBag = DisposeBag()

// DisposeBag에 넣을 수 있는 방법
// 1. insert를 사용해 넣어준다.
let disposable = rxswiftLoadImage(from: LARGER_IMAGE_URL)
    .observeOn(MainScheduler.instance)
    .subscribe({ result in
        switch result {
        case let .next(image):
            self.imageView.image = image

        case let .error(err):
            print(err.localizedDescription)

        case .completed:
            break
        }
    })
    
disposeBag.insert(disposable) // 해당 disposable을 넣어줌

// 2. disposed(by: )를 사용하여 1번보다 더 간편하게 넣어준다.
rxswiftLoadImage(from: LARGER_IMAGE_URL)
    .observeOn(MainScheduler.instance)
    .subscribe({ result in
        switch result {
        case let .next(image):
            self.imageView.image = image

        case let .error(err):
            print(err.localizedDescription)

        case .completed:
            break
        }
    }).disposed(by: disposeBag) // <---
    
disposeBag = DisposeBag() //disposeBag을 새로 만들어주면 기존에 disposeBag에 있는 작업들이 다 dispose 된다.

✅⠀Operators

Creating Observables

Just

  • create an Observable that emits a particular item
  • 통으로 나온다.
Observable.just("Hello World")
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)
// Hello World 출력

Observable.just(["Hello", "World"])
    .subscribe(onNext: { arr in
        print(arr)
    })
    .disposed(by: disposeBag)
// ["Hello", "World"] 출력

From

  • convert various other objects and data types into Observables
  • Just와 달리 하나하나 나온다.
Observable.from(["Hello", "World"])
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)

// ** 출력 **
// Hello
// World


Observable.from(["Hello", "World"])
    .map { $0.count }
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)
    
// ** 출력 **
// 5
// 5

Transforming Observables

Map

Observable.from([1, 2, 3])
    .map { $0 * 10 }
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)
    
// ** 출력 **
// 10
// 20
// 30

Filtering Observables

Filter

Observable.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    .filter { $0 % 2 == 0 }
    .subscribe(onNext: { n in
        print(n)
    })
    .disposed(by: disposeBag)
    
// ** 출력 **
// 2
// 4
// 6
// 8
// 10

First

Observable.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    .first()
    .subscribe(onNext: { n in
        print(n)
    })
    .disposed(by: disposeBag)
    
// ** 출력 **
// 1

Single

Observable.from([1])
    .single()
    .subscribe(onNext: { n in
        print(n)
    })
    .disposed(by: disposeBag)
    
// ** 출력 **
// 1

Combining Observables

CombineLatest

Observable.combineLatest( idField.rx.text.orEmpty
                            .map(checkEmailValid),
                          pwField.rx.text.orEmpty
                                .map(checkPasswordValid),
                          resultSelector: {s1, s2 in s1 && s2})
    .subscribe(onNext: { b in
        self.loginButton.isEnabled = b
    }).disposed(by: disposeBag)

✅⠀Scheduler

어느 Scheduler에서 실행시킬지 결정

observeOn

순서에 영향을 받는다. 즉, 중간중간에 스케쥴러를 바꿔도 됨.

Observable.just("800x600")
    .observeOn(ConcurrentDispatchQueueScheduler.init(qos: .default)) // ** 여기서부터 ConcurrentDispatchQueueScheduler에서 처리하겠다.
    .map { $0.replacingOccurrences(of: "x", with: "/") }
    .map { "https://picsum.photos/\($0)/?random" }
    .map { URL(string: $0) }
    .filter { $0 != nil }
    .map { $0! }
    .map { try Data(contentsOf: $0) }
    .map { UIImage(data: $0) }
    .observeOn(MainScheduler.instance) // ** UI관련 작업은 메인쓰레드에서 처리
    .subscribe(onNext: { image in
        self.imageView.image = image
    })
    .disposed(by: disposeBag)코드를 입력하세요

// 만약 observeOn에 순서를 바꾼다면?

Observable.just("800x600")
    .map { $0.replacingOccurrences(of: "x", with: "/") }
    .map { "https://picsum.photos/\($0)/?random" }
    .map { URL(string: $0) }
    .filter { $0 != nil }
    .map { $0! }
    .map { try Data(contentsOf: $0) }
    // ** Data를 가져오는 부분보다 뒤에서 비동기처리를 하니 데이터를 가져오는 것 까진 메인쓰레드에서 동기 처리. 
    // ** 즉, 데이터를 받아올 때 까지 아무것도 못한다.
    .observeOn(ConcurrentDispatchQueueScheduler.init(qos: .default)) 
    .map { UIImage(data: $0) }
    .observeOn(MainScheduler.instance) // ** UI관련 작업은 메인쓰레드에서 처리
    .subscribe(onNext: { image in
        self.imageView.image = image
    })
    .disposed(by: disposeBag)

subscribeOn

observeOn과 달리 순서에 영향을 받지 않음. subscribe 쓰는 순간 부터 처리함.

Observable.just("800x600")
    .map { $0.replacingOccurrences(of: "x", with: "/") }
    .map { "https://picsum.photos/\($0)/?random" }
    .map { URL(string: $0) }
    .filter { $0 != nil }
    .map { $0! }
    .map { try Data(contentsOf: $0) }
    .map { UIImage(data: $0) }
    .subscribeOn(ConcurrentDispatchQueueScheduler.init(qos: .default))
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { image in
        self.imageView.image = image
    })
    .disposed(by: disposeBag)

✅⠀subscribe 후 이벤트 처리

  • subscribe(_ on: (Event) -> Void )
Observable.just(["Hello", "World"])
    .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)

// ** 출력 **
// next : Hello
// next : World
// completed

Observable.just(["Hello", "World"])
    .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)
    
// ** 출력 **
// next : Hello
// error: The operation couldn't be completd. 
// single은 하나만 들어와야되는데 여러개가 들어오니 에러
  • .subscribe(onNext: , onError: , onCompleted: , onDisposed: )
Observable.just(["Hello", "World"])
    .subscribe(onNext: { s in
        print(s)
    }, onError: { err in
        print(err.localizedDescription)
    }, onCompleted: {
        print("completd")
    }, onDisposed: {
        print("disposed")
    }).disposed(by: disposeBag)

// ** 출력 **
// next : Hello
// next : World
// completd
// disposed

✅⠀Side effect

말 그대로 외부에 영향을 주는 부분을 담당. subscribe와 do에서만 처리하자.

  • subscribe
.subscribe(onNext: { image in 
                self.imageView.image = image // 외부에 있는 imageView의 image를 바꿀 때            
  })
  • do
.do(onNext: { image in
                print(image?.size)
            })

✅⠀RxCocoa

textField의 입력될 때마다 호출되는 부분을 Delegate를 사용하지 않고 RxCocoa를 활용하여 구현.

@IBOutlet var idField: UITextField!
// .rx : 비동기로 받겠다.
// orEmpty : .filter{ $0 != nil}.map{$0!} nil값을 체크
idField.rx.text.orEmpty
    .map(checkEmailValid)
    .subscribe(onNext: { b in
        self.idValidView.isHidden = b
    })
    .disposed(by: disposeBag)

// 따로 분리해서 쓸 수도 있음
let idInputOb : Observable<String> = idField.rx.text.orEmpty.asObservable()
let idValidOb = idInputOb.map(checkEmailValid)

idValidOb.subscribe(onNext: { b in
    self.idValid.onNext(b)
    self.idValidView.isHidden = b
})
.disposed(by: disposeBag)

✏️⠀오늘은...

말로만 듣던 Rx 오늘 처음 공부해봤는데 생각보다 굉장히 유용한것 같다. gesture 부분을 rx로 간편하게 구현할 수 있는거 보고 감탄의 감탄을 👏🏻⠀전세계 사람들 다 코드 길게 치기 귀찮아서 오픈소스 열심히 만드나 보다. 감사합니다 ㅎㅎ 내일은 곰튀김님의 RxSwift 시즌2를 들어야겄다..

0개의 댓글