RxSwift(35).

RxCocoa에서 제공하는 기능 중 Driver가 가장 중요합니다.

드라이버는 UI를 바인딩하는 직관적이고 효율적인 방법을 제공합니다.

드라이버는 특별한 관찰 가능 항목이며 UI 처리와 관련된 몇 가지 속성이 있습니다.

1. 오류 메시지를 제공하지 마십시오.

오류로 인해 UI 처리가 중지되는 상황이 발생하지 않습니다.

2. 강제 스케줄러 변경의 경우를 제외하고는 메인 스케줄러가 항상 켜져 있습니다.

이벤트는 항상 기본 스케줄러에 의해 발송되며 후속 작업도 기본 스케줄러에 의해 실행됩니다.

3. 운전자는 부작용을 공유합니다.

Observable 1에서 Share 연산자를 호출하고 .whileConnect 연산자를 호출하는 것과 같습니다.

모든 구독자는 시퀀스를 공유하며 새 구독이 시작되면 전송된 마지막 이벤트가 즉시 전송됩니다.


import UIKit
import RxSwift
import RxCocoa

enum ValidationError: Error {
    case notANumber
}

class DriverViewController: UIViewController {
    
    let bag = DisposeBag()
    
    @IBOutlet weak var inputField: UITextField!
@IBOutlet weak var resultLabel: UILabel!
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() { super.viewDidLoad() //1. 텍스트속성에서 flatMapLatest를 호출하고 클로저에서 validateText를 리턴한다 let result = inputField.rx.text .flatMapLatest { validateText($0) } //3. 옵저버블이 전달하는 Bool을 ok 또는 Error로 바꾸고 레이블에 바인딩 result .map { $0 ? "Ok" : "Error" } .bind(to: resultLabel.rx.text) .disposed(by: bag) //Bool값을 빨, 파로 바구고 레이블 백그라운드와 바인드 result .map { $0 ? UIColor.blue : UIColor.red } .bind(to: resultLabel.rx.backgroundColor) .disposed(by: bag) //Bool을 버튼의 활성화상태와 바인드 result .bind(to: sendButton.rx.isEnabled) .disposed(by: bag) } } //2. 이 함수는 String?을 파라미터로 받아서 새로운 옵저버블을 리턴한다.

옵저버블이 NE를 통해 방출하는건 Bool func validateText(_ value: String?) -> Observable<Bool> { return Observable<Bool>.create { observer in //시퀀스 실행시점을 파악하기위한 로그 print("== \(value ?? "") Sequence Start ==") defer { print("== \(value ?? "") Sequence End ==") } //문자열을 Double로 컨버전하고 실패한경우 에러이벤트를 전달 guard let str = value, let _ = Double(str) else { observer.onError(ValidationError.notANumber) return Disposables.create() } //성공했다면 Next이멘트로 true를 전달하고 observer.onNext(true) //completed를 전달 observer.onCompleted() return Disposables.create() } } /* == 1234 Sequence Start == == 1234 Sequence End == == 1234 Sequence Start == == 1234 Sequence End == == 1234 Sequence Start == == 1234 Sequence End == */

화면 진입 후 로그를 보면 3개의 시퀀스가 ​​시작됩니다.

결과에 저장된 Observable에 새로운 구독자가 추가될 때 새로운 시퀀스가 ​​시작됩니다.

그래서 동점이 있는 횟수만큼 새로운 시퀀스가 ​​시작됩니다.

이 상태에서 숫자를 입력하면 3개의 순서가 다시 시작됩니다.

만약에 유효성 검사 대신 네트워크 요청을 하는 함수를 호출하면 동일한 요청이 세 번 실행됩니다.

이렇게 되면 너무 많은 시스템 리소스를 소비하므로 수정해야 합니다.

숫자 이외의 문자가 전달되면 Observable은 오류 이벤트를 전달합니다.

코드에서 바인딩에 사용되는 text, backgroundcolor 및 isenable 속성은 모두 바인딩 유형에서 선언됩니다.

그리고 바인더는 오류 이벤트를 수신하지 않습니다.

오류 이벤트 바인딩 안 함

디버깅 환경에서 충돌이 발생하고 런타임 환경에서 오류 메시지가 표시됩니다.

어떤 환경에서도 사용자 인터페이스가 더 이상 업데이트되지 않습니다.

수정이 필요합니다.

1. 먼저 시퀀스가 ​​필요 이상으로 시작되는 문제를 수정합니다.

let result = inputField.rx.text
            .flatMapLatest { validateText($0).catchErrorJustReturn(false) }
            .share()

공유 연산자를 추가하면 모든 구독자가 시퀀스를 공유합니다.

오류 이벤트를 false로 변경하려면 catchErrorJustReturn 연산자를 사용하십시오.

2. InnerObservable이 백그라운드 환경에서 결과를 반환하는 경우 UI 바인딩이 잘못된 스레드에서 실행 중일 수 있습니다.

        let result = inputField.rx.text
            .flatMapLatest {
                validateText($0)
                    .observe(on: MainScheduler.instance)
                    .catchErrorJustReturn(false) }
            .share()

잠재적인 문제를 방지하려면 기본 스케줄러를 직접 지정해야 합니다.

결과

import UIKit
import RxSwift
import RxCocoa

enum ValidationError: Error {
    case notANumber
}

class DriverViewController: UIViewController {
    
    let bag = DisposeBag()
    
    @IBOutlet weak var inputField: UITextField!
@IBOutlet weak var resultLabel: UILabel!
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() { super.viewDidLoad() //1. 텍스트속성에서 flatMapLatest를 호출하고 클로저에서 validateText를 리턴한다 let result = inputField.rx.text .flatMapLatest { validateText($0) .observe(on: MainScheduler.instance) .catchErrorJustReturn(false) } .share() //3. 옵저버블이 전달하는 Bool을 ok 또는 Error로 바꾸고 레이블에 바인딩 result .map { $0 ? "Ok" : "Error" } .bind(to: resultLabel.rx.text) .disposed(by: bag) //Bool값을 빨, 파로 바구고 레이블 백그라운드와 바인드 result .map { $0 ? UIColor.blue : UIColor.red } .bind(to: resultLabel.rx.backgroundColor) .disposed(by: bag) //Bool을 버튼의 활성화상태와 바인드 result .bind(to: sendButton.rx.isEnabled) .disposed(by: bag) } } //2. 이 함수는 String?을 파라미터로 받아서 새로운 옵저버블을 리턴한다.

옵저버블이 NE를 통해 방출하는건 Bool func validateText(_ value: String?) -> Observable<Bool> { return Observable<Bool>.create { observer in //시퀀스 실행시점을 파악하기위한 로그 print("== \(value ?? "") Sequence Start ==") defer { print("== \(value ?? "") Sequence End ==") } //문자열을 Double로 컨버전하고 실패한경우 에러이벤트를 전달 guard let str = value, let _ = Double(str) else { observer.onError(ValidationError.notANumber) return Disposables.create() } //성공했다면 Next이멘트로 true를 전달하고 observer.onNext(true) //completed를 전달 observer.onCompleted() return Disposables.create() } }

이전과 달리 새로운 번호를 입력하면 순서가 하나씩 시작됩니다.

문자를 입력해도 구독자에게 잘못 전달되어 구현된 코드에 따라 UI가 업데이트 됩니다.

오류 이벤트로 인해 충돌이 발생하거나 UI 업데이트가 중지되는 문제를 수정했습니다.

이제부터는 이 코드를 개선하기 위해 드라이버를 사용할 것입니다.

드라이버는 우리가 만들지 않습니다.

asDriver 메서드를 사용하여 일반적인 observable을 드라이버로 변환합니다.

let result = inputField.rx.text.asDriver()
            .flatMapLatest {
                validateText($0)
             }
             
             
//Driver로 대체
//.observe(on: MainScheduler.instance)
//.catchErrorJustReturn(false)

드라이버는 시퀀스를 공유하므로 공유 연산자가 필요하지 않습니다.

두 연산자 모두 드라이버로 대체됩니다.

asDriver 메서드를 보면 오류 이벤트가 전달될 때 사용할 기본 값 또는 observable 복구를 전달할 수 있습니다.

여기서는 false를 기본값으로 전달합니다.

이 경우 아래 바인딩 연산자에서 오류가 발생합니다.

경우에 따라 지도 연산자에 오류가 나타날 수 있습니다.

이 오류는 bind(to:) 메서드 때문에 발생합니다.

드라이버를 사용하는 경우 bind(to:) 대신 드라이브 방법을 사용하십시오.하다.

실행 결과는 이전과 동일합니다.

1. 드라이버는 모든 작업이 기본 스레드에서 실행되도록 보장하므로 스케줄러를 지정할 필요가 없습니다.

2. 시퀀스를 공유하기 때문에 불필요한 자원 낭비를 피할 수 있는 장점이 있습니다.

3. 동시에 오류 처리를 쉽게 구현할 수 있습니다.

향후 UI 바인딩 코드 작성 시 드라이버 적극 활용

import UIKit
import RxSwift
import RxCocoa

enum ValidationError: Error {
    case notANumber
}

class DriverViewController: UIViewController {
    
    let bag = DisposeBag()
    
    @IBOutlet weak var inputField: UITextField!
@IBOutlet weak var resultLabel: UILabel!
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() { super.viewDidLoad() let result = inputField.rx.text.asDriver() .flatMapLatest { validateText($0) .asDriver(onErrorJustReturn: false) } result .map { $0 ? "Ok" : "Error" } .drive(resultLabel.rx.text) .disposed(by: bag) result .map { $0 ? UIColor.blue : UIColor.red } .drive(resultLabel.rx.backgroundColor) .disposed(by: bag) result .drive(sendButton.rx.isEnabled) .disposed(by: bag) } } func validateText(_ value: String?) -> Observable<Bool> { return Observable<Bool>.create { observer in print("== \(value ?? "") Sequence Start ==") defer { print("== \(value ?? "") Sequence End ==") } guard let str = value, let _ = Double(str) else { observer.onError(ValidationError.notANumber) return Disposables.create() } observer.onNext(true) observer.onCompleted() return Disposables.create() } }