Skip to content

Latest commit

 

History

History
592 lines (427 loc) · 33.8 KB

File metadata and controls

592 lines (427 loc) · 33.8 KB

이글은 Getting Started With RxSwift and RxCocoa ( https://www.raywenderlich.com/138547/getting-started-with-rxswift-and-rxcocoa )에 대한 번역입니다. (번역 허락을 받은 것은 아니지만, 도움이 필요하신 분들을 위해서 올려봅니다. 문제가 될 경우 바로 삭제하겠습니다. ㅡㅜ)

RxSwift 와 RxCocoa 시작하기

코드가 여러분이 원하는 대로 정확히 동작하면(저의 고양이와는 달리) 매우 기쁩니다. 프로그램에서 어떤 것을 변경하고 업데이트 하도록 이야기하면 그대로 됩니다. 좋은 코드입니다.

객체지향 시대에 대부분의 프로그래밍은 명령형(imperative)이었습니다. 이런식이었죠. 코드가 프로그램에게 해야할 일을 말하고, 변경사항은 여러가지 방법으로 전달됩니다. 그러나 보통 뭔가 변경되면 시스템에 적극적으로 알려야 합니다.

그런대로 괜찮았습니다. 하지만, 만약 여러분이 앱에서 변경 사항이 발생할 때 자동으로 코드가 업데이트하도록 설정할 수 있었다면 그것보다 좋을 수 있을까요? 이것이 reactive programming 의 기본 사상입니다.: 앱이 직접적으로 어떻게 하라는 여러분의 지시 없이 데이터에 기반하여 변경사항에 반응할 수 있습니다. 이렇게 되면 특정 상태를 처리하는 것보다 바로 앞의 로직에 집중하기 더 쉬워집니다.

이런 것은 순수 Objective-C 에서는 보통 Key-Value Observation ( https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html ) 으로 할 수 있고, Swift에서는 didSet 혹은 setter를 override 해서 할 수 있습니다. 하지만 때때로 이런 메소드들은 제대로 다루기가 힘들 수 있습니다. Objective-C 와 Swift 에는 이런 문제를 피해 reactive programming 을 더 쉽게 사용할 수 있도록 하는 여러 framework들이 존재합니다.

주의: 좀 더 자세히 알고 싶다면, Rui Peres가 작성한 주요 framework 들간의 차이점을 설명한 좋은 문서( https://www.raywenderlich.com/126522/reactivecocoa-vs-rxswift )를 보세요. 댓글 영역에 열정적인 사람들이 어떤 framework이 더 좋다고 생각하는 지에 대한 재미있는 관점 또한 있습니다.

오늘 여러번은 초콜릿 구매 앱을 성가신 명령형(imperative)에서 멋진 반응형(reactive)으로 탈바꿈시키기 위해서 이런 framework들 중 하나를 사용하게 될 겁니다. RxSwift, 그리고 그의 동료 RxCocoa를 사용하게 될 겁니다.

RxSwift 와 RxCocoa가 뭔가요?

RxSwift 와 RxCocoa는 ReactiveX(보통 Rx라고 축약됩니다.) 언어 도구들 의 suite 의 일부입니다. ReactiveX는 여러 프로그래밍 언어와 플랫폼를 지원합니다. ReactiveX가 .NET/C# 생태계의 일부로서 시작하기는 했지만, 루비 개발자, 자바스크립트 개발자, 특히 자바와 안드로이드 개발자들 사이에서 폭발적인 인기를 얻었습니다.

RxSwift는 Swift 프로그래밍 언어와 작동하는 framework입니다. 반면에 RxCocoa는 iOS와 OSX에서 사용되는 Cocoa API를 reactive 테크닉들로 사용하기 쉽게 도움을 주는 framework입니다.

ReactiveX framework는 공통 용어를 제공함으로써 다양한 프로그래밍 언어에서 반복적으로 사용되는 특정 작업들에 유용하게 설계되어 있습니다. (이론적으로) 이것은 새로운 언어에 공통 작업을 어떻게 매핑할지 알아 내는데 시간을 낭비하기 보다는 언어 그자체의 문법에 더 집중하기 쉽게 만들어 줍니다.

Observables 와 Observers

이 튜터리얼을 위해 알아야 할 두가지 개념은 Observable 과 Observer 입니다.

  • Observable 는 변경 알림을 발생시키는(emit) 어떤 것입니다.
  • Observer는 변경 사항이 발생했을 때 알림을 받기 위해서 Observable에 대해서 구독(subscribe)하는 어떤 것입니다.

하나의 Observable에 여러 Observer들이 listen(subscribe)할 수 있습니다. 이것은 Observable 이 변경될 때 그것의 모든 Observer들에게 알림이 전달된다는 것을 의미합니다.

DisposeBag

RxSwift 와 RxCocoa는 ARC와 메모리 관리를 다룰 추가적인 툴을 가지고 있습니다. DisposeBag 입니다. 이것을 Observer 객체들에 대한 가상의 가방("bag") 입니다. DisposeBag에 있는 Observer들은 부모 객체가 deallocte될 때, 폐기됩니다.

DisposeBag을 property로 가지고 있는 객체에 대해서 deinit() 이 호출될 때, 그 가방(DisposeBag)은 비워집니다. 그리고 폐기되는 Observer는 자동으로 관찰하고(observe) 있던 것으로 부터 해지(unsubscribe)됩니다. 이것은 보통 그렇듯이 ARC가 메모리를 해제할 수 있게 해줍니다.(reference count가 줄어서 0이 되고 ARC가 해제할 수 있게 됩니다.)

DisposeBag이 없다면, 두가지 중 하나의 결과를 얻게 될 겁니다.:Observer가 retain cycle을 만들게 되고, 무한정 관찰하게 되거나, 여러분의 객체에서 deallocted 되서, crash를 유발하게 됩니다.

그래서 좋은 ARC 시민이 되기 위해서는 Observable 객체들을 생성할 때, DisposeBag에 추가하는 것을 기억하세요. 그래야 Observable 객체들이 깔끔하게 정리될 겁니다.

시작하기

초콜릿을 먹으러 갑시다! 이 튜터리얼을 위한 starter 앱인 Chocotasic 은 여기( https://koenig-media.raywenderlich.com/uploads/2016/10/Chocotastic-starter-s3-rxs-3b1.zip )에서 받으실 수 있습니다.
zip 파일을 다운로드 받고 Xcode에서 project를 여세요.

주의:이 프로젝트는 CocoaPods를 이용합니다. 그래서 Xcode에서 Chocotasic.xcworkspace 파일을 Xcode에서 열어야 합니다.

앱을 빌드하고 실행시키세요. 마침내 여러분은 다음의 화면을 보게 될 겁니다. 개별 가격과 함께 유럽에서 살 수 있는 몇가지 초콜릿 종류들이 리스트로 보여집니다.

초콜릿 에 탭을 하면 카트에 그 제품이 추가 될 겁니다.

우측 상단에 있는 카트를 탭하면 체크 아웃하거나 cart를 reset할 수 있는 페이지로 이동됩니다.

체크 아웃을 선택하면, 신용카드 입력 폼이 나타날 겁니다.

튜터리얼에서 나중에, 순수 reactive programming을 사용하도록 설정하기 위해서 다시 돌아 올겁니다. cart 요약 페이지로 돌아가려면 Cart 버튼을 탭하세요. 그런다음 빈 cart 를 가진 메인 메이지로 돌아 가려면 Reset 버튼을 탭하세요.

시작하기: Non-reactive

이제까지 여러분은 애플리케이션이 어떤지 봤습니다. 이제는 어떻게 동작하는지 조사해볼 시간입니다. ChocolatesOfTheWorldViewController.swift를 여세요. 여러분은 꽤 전형적인 UITableViewDelegate와 UITableViewDataSource를 extension들을 보게 될겁니다.

updateCartButton() 메소드가 또한 있습니다. 이 메소드는 cart button을 카드에 있는 초콜릿의 개수로 업데이트 합니다. 이 메소드는 두 곳에서 호출됩니다. view controller가 보여질 때 호출되는 viewWillAppear(:) 와 카트에 새로운 초콜릿이 추가된 후에 호출되는 tableView):didSelectRowAt:)에서 호출됩니다.

이것들은 둘다 명령형(imperative)인 방법입니다.:명시적으로 개수를 업데이트하는 메소드를 호출해야 합니다. 지금은 어디에서 값을 변경하는지 기억해둬야 하지만, 반응형(reactive) 기술을 사용해서 코드를 재작성할 겁니다. 그렇게 하면, 개수가 어디에서 어떻게 변경되던지 상관없이 버튼이 업데이트 될 겁니다.

RxSwift:Cart의 개수를 반응형(reactive)으로 만들기

카드의 item들을 참조하는 모든 메소들은 ShoppingCart.sharedCart 싱글톤(SingleTon)을 사용합니다. ShoppingCart.swift를 여세요. 그러면 인스턴스 값을 설정하는 꽤 전형적인 싱클턴(SingleTon)을 보게 될 겁니다.

var chocolates = [Chocolate]()

당장은, chocolates 변수의 내용에 대한 변경사항을 관찰(observe)할 수 없습니다. 정의에 didSet 를 추가할 수 있지만, 배열의 각 요소가 아닌 전체 배열이 변경되었을 때만 호출될 겁니다.

다행스럽게도, RxSwift는 해답을 가지고 있습니다. chocolates 변수를 생성하는 줄을 이렇게 변경하세요.

let chocolates: Variable<[Chocolate]> = Variable([])

주의: 이 변경은 사이드바에 상당한 오류들을 유발할 겁니다. 하지만 금방 수정할 수 있을 겁니다.

이 문법은 조금 어려워서 머리를 감싸게 할 수도 있습니다. 그래도 이면에서 어떤 것들이 이루어지는 것을 이해하는데 도움이 됩니다.

chocolates 를 Chocolate 객체들의 Swift 배열로 설정하기 보다는, Chocolate 객체들의 Swift 배열의 타입을 가지는 RxSwift Variable 로 정의했습니다.

Variable는 클래스이고, 그래서 reference 문법을 사용합니다. - chocolates 가 Variable 인스턴스를 참조한다는 것을 의미합니다.

Variable 는 value라는 property를 가집니다. 이것은 Chocolate 객체들에대한 배열이 저장되는 곳입니다.

Variable의 마술은 asObservable()이라고 불리는 메소드로 부터 옵니다. 매번 수동으로 value 를 확인하는 대신에, 여러분 대신에 계속 지켜봐줄 Observer를 추가할 수 있습니다. 값이 변경될 때, Observer가 여러분에게 알려줍니다. 그래서 변경에 반응할 수 있습니다.

이 설정에서 단점은 chocolates 배열에 어떤 것에 접근하거나 변경할 필요가 있을 때, 직접적으로 하지 못하고, value property를 통해서 해야 하는 겁니다. 이것이 컴파일러가 에러를 내뱉는 이유입니다. 이제 그 오류들을 수정할 시간입니다.!

ShoppingCart.swift에서 totalCost()메소드를 찾고

return chocolates.reduce(0) {

에서

return chocolates.value.reduce(0) {

으로 변경하세요.

itemCountString()에서

guard chocolates.count > 0 else {

에서

guard chocolates.value.count > 0 else {

으로 변경하세요.

그리고

let setOfChocolates = Set<Chocolate>(chocolates)

에서

let setOfChocolates = Set<Chocolate>(chocolates.value)

으로 변경하세요.

마지막으로

let count: Int = chocolates.reduce(0) {

에서

let count: Int = chocolates.value.reduce(0) {

으로 변경하세요.

CartViewController.swift에서 reset()을 찾고,

ShoppingCart.sharedCart.chocolates = []

에서

ShoppingCart.sharedCart.chocolates.value = []

으로 변경하세요.

ChocolatesOfTheWorldViewController.swift 에서 다시, updateCartButton()의 구현을

cartButton.title = "\(ShoppingCart.sharedCart.chocolates.value.count) \u{1f36b}"

으로 변경하세요.

그리고 tableView(_:didSelectRowAt:)에서

ShoppingCart.sharedCart.chocolates.append(chocolate)

에서

ShoppingCart.sharedCart.chocolates.value.append(chocolate)

으로 변경하세요.

휴, 마침내, Xcode 가 행복할 겁니다. 그리고 에러가 없을 겁니다. 이제 chocolates가 관찰될(observed) 수 있다는 이점을 얻을 수 있습니다!

ChocolatesOfTheWorldViewController.swift 로 가세요. 그리고 property들의 리스트에 다음을 추가 하세요.

let disposeBag = DisposeBag()

이 코드는 DisposeBag을 생성합니다. DisposeBag은 deinit()가 호출될 때, 여러분이 설정한 Observer들이 정리되도록(clean up)하기 위한 겁니다.

//MARK: Rx Setup 주석 아래에 다음의 코드를 추가 하세요.

//MARK: Rx Setup

private func setupCartObserver() {
  //1
  ShoppingCart.sharedCart.chocolates.asObservable()
    .subscribe(onNext: { //2
      chocolates in
      self.cartButton.title = "\(chocolates.count) \u{1f36b}"
    })
    .addDisposableTo(disposeBag) //3
}

이 코드는 cart를 자동으로 갱신하기 위해서 reactive Observer를 설정합니다. 보시다 시피, RxSwift는 chained function들을 많이 사용합니다. chained function들은 각각의 함수들이 이전 함수의 결과를 이용한다는 것을 의미합니다.

이 경우에 어떻게 발생하는지 설명하면,

1.먼저, shopping cart의 chocolates 변수를 Observable로 잡습니다.

2.Observable의 value 에 변경사항이 있는지 알기 위해서 해당 Observable에 대해서 subscribe(onNext:)를 호출합니다. subscribe(onNext:)는 값들이 변경될때 마다 실행될 클로저를 받습니다. 클로저에 입력 파라미터는 Observable의 새로운 값입니다. 그리고 unsubscribe하거나 subscribe이 dispose될 때까지 이런 알림을 계속 받을 것입니다. 이 메소드로 부터 돌려 받는 것은 Disposable에 부합하는 Observer입니다.

3.이전 단계로 부터 얻은 Observer를 구독하고 있는(subscribing) 객체가 deallocte될 때 subscribe가 dispose되도록 disposeBag에 추가합니다.

마지막으로 명령형의 updateCartButton() 메소드를 제거하세요. 이것은 viewWillAppear(:) 와 tableView(:didSelectRowAt:)에 오류를 유발할 겁니다.

오류들을 수정하기 위해서, 전체 viewWillAppear(:)를 제거하세요.(super를 호출하는 것 이외에는 updateCartButton()를 호출하는 것이 전부이기 때문에) 그리고 나서 tableView(:didSelectRowAt)에서 updateCartButton 호출을 제거하세요.

빌드하고 실행하세요. 초콜릿 리스트가 보일 겁니다.

하지만 cart를 위한 버튼이 단지 "item"이라고만 표시되는 있다는 것을 아세요. 그리고 초콜릿들의 리스트를 탭을 해도 아무일도 일어나지 않습니다. 뭐가 잘못된 걸까요?

Rx Observer들을 설정하는 함수를 만들었습니다. 하지만, 지금은 그 함수를 실제로 호출하는 부분이 없습니다. 그래서 Observer들이 설정되지 않습니다. 이 오류를 수정하기 위해서, viewDidLoad():에 다음의 코드를 추가하세요.

setupCartObserver()

초콜릿 리스트를 다시 보기 위해서 앱을 빌드하고 실행하세요.

몇개의 초콜릿에 탭을 하세요.-item들의 숫자가 자동으로 업데이트 됩니다!

성공!모든 초콜릿을 카트에 담을 수 있습니다.

RxCocoa: TableView reactive하게 만들기

지금 우리는 RxSwift를 이용해서 카트를 reactive하게 만들었습니다. UITableView를 역시 reactive하게 만들기 위해서 RxCocoa를 사용할 겁니다.

RxCocoa는 여러 다른 유형의 UI 요소들을 위한 reactive API들을 가집니다. 이것들은 override delegate 혹은 data source 메소드들없이 바로 UITableView를 같은 것을 설정할 수 있는 옵션을 제공합니다.

이런 것들이 어떻게 동작하는지 보여주기 위해서, 전체 UITableViewDataSource와 UITableViewDelegate extension들과 그것들의 모든 메소드들을 지우세요. 다음으로, viewDidLoad() 에 있는 tableView.dataSource 와 tableView.delegate에 대한 할당을 지우세요.

앱을 빌드하고 실행하세요. 그러면 초콜릿들이 모두 갑자기 사라져서 꽤 슬프고 텅비어 있는 tableView를 보게 될 겁니다.

재미 없네요. 초콜릿들을 복원할 시간입니다!

먼저, reactive tableView를 가지기 위해서, tableView가 반응할 무엇이 필요합니다. ChocolatesOfTheWorldViewController.swift 에서 europeanChocolates property를 Observable로 갱신하세요.

let europeanChocolates = Observable.just(Chocolate.ofEurope)

just(_:) 메소드는 Observable의 기초가 되는 value 에 대한 변경이 실제로 없지만, Observable value로서 접근할 수 있다는 것을 나타냅니다.

주의: 때때로, just(_:)를 호출하는 것은 Reactive Programming이 과도할수 있다는 표시입니다. 결국 값이 변경 되지 않는다면, 값에 반응하도록 설계된 프로그래밍 스킬을 왜 써야 할까요? 이 예제에서는, 나중에 변경될 view cell들의 반응을 설정하기 위해서 사용하고 있습니다. 하지만 Rx를 사용하는 방법을 주의깊게 살펴보는 것은 좋은 생각입니다. 망치를 가지고 있다고 모든 문제가 못이라는 것을 의미하는 것은 아니기 때문입니다.:]

이제 europeanChocolates을 Observable로 만들었습니다. 다음을 추가 하세요.

private func setupCellConfiguration() {
  //1
  europeanChocolates
    .bindTo(tableView
      .rx //2
      .items(cellIdentifier: ChocolateCell.Identifier,
             cellType: ChocolateCell.self)) { // 3
        row, chocolate, cell in
        cell.configureWithChocolate(chocolate: chocolate) //4
      }
      .addDisposableTo(disposeBag) //5
}

여기서 일어나는 일은 다음과 같습니다.

1.tableView 에 각각의 row에 대해서 실행되어야 할 코드와 europeanChocolates Observable을 연계시키기 위해서 bindTo(_:)를 호출합니다.

2.호출하는 클래스가 무엇이든 간에 rx를 호출함으로써 RxCocoa extension들에 접근할 수 있습니다. -이 경우에는, UITableView

3.Rx 메소드 items(cellIdentifier:cellType:)을 호출합니다. cell Identifier와 cell type 의 class를 파라미터로 넘겨주면서 호출합니다. 이것은 Rx framework가 보통 tableView가 그것의 원래 delegate들을 가지고 있다면 호출될 dequeuing 메소드를 호출할 수 있도록 합니다.

4.각각의 새로운 item 에 대해서 실행될 Block을 넘깁니다. row, row에 있는 초콜릿 그리고 cell 에 대한 정보를 다시 얻게 될 겁니다. 이것은 cell 설정을 정말 쉽게 만듭니다.

5.bindTo(_:)에 의해서 돌려 받았던 Disposable 을 disposeBag 에 추가 합니다.

보통 tableView(:numberOfRowsInSection) 과 numberOfRowsInSection(in:) 에 의해서 생성되는 값들은 이제 관찰되는(observed) data에 기반해서 자동으로 계산됩니다. tableView(:cellForRowAt:)은 클로저에 의해서 효과적으로 대체됩니다.

viewDidLoad()로 가세요. 그리고 새로운 setup 메소드를 호출하는 줄을 추가 하세요.

setupCellConfiguration()

앱을 빌드하고 실행시키세요. 그렇습니다! 초콜릿들이 돌아 왔습니다.

하지만, 각각의 초콜릿에 탭을 할 때, 카드에 더해지지 않습니다. 이전의 Rx Method 들에서 뭔가 잘못되었나요?

아닙니다! tableView(_:didSelectRowAt:)를 제거함으로써, cell에 대한 탭들을 가져오거나 그것들을 어떻게 처리해야 하는지 알수 있는 어떤 것을 가져왔습니다.

이것을 해결하기 위해서, RxCocoa가 UITableView에 추가한 또 다른 extension method가 있습니다. 이 메소드는 modelSelected(_:)라고 불립니다. 그리고 model 객체들이 언제 선택될 때, 정보를 보는데 사용할 수 있는 Observable을 리턴합니다.

다음 메소드를 추가하세요:

private func setupCellTapHandling() {
  tableView
    .rx
    .modelSelected(Chocolate.self) //1
    .subscribe(onNext: { //2
      chocolate in
      ShoppingCart.sharedCart.chocolates.value.append(chocolate) //3

      if let selectedRowIndexPath = self.tableView.indexPathForSelectedRow {
        self.tableView.deselectRow(at: selectedRowIndexPath, animated: true)
      } //4
    })
    .addDisposableTo(disposeBag) //5
}

한 단계씩 이렇게 됩니다.:

1.talbleView의 reactive extension의 modelSelected(_:) 함수를 호출합니다. 적절한 타입의 item을 돌려 받기 위해서 Chocolate model을 같이 넘깁니다. 이 함수는 Observable을 리턴합니다.

2.Observable을 받으면, subscribe(onNext:)를 호출합니다. 모델이 선택될때마다(예를 들어 cell이 탭될 때) 실생되어야 하는 클로저를 뒤따르게 해서 호출합니다.

3.subscribe(onNext:)에 넘겨진 뒤따르는 클로저안에서, 선택된 초콜릿을 카트에 넣습니다.

4.역시 클로저안에서 탭된 row가 deselect가 되도록 합니다.

5.subscribe(onNext:)는 Disposable 를 리턴합니다. disposeBag에 그 Disposable을 추가합니다.

마지막으로, viewDidLoad()로 갑니다. 그리고 새로운 설정 메소드를 추가합니다.

setupCellTapHandling()

앱을 빌드하고 실행하세요. 친숙한 초콜릿 리스트를 볼 수 있을 겁니다.

하지만 이제는 초콜릿을 추가할 수 있습니다!

RxSwfit 와 직접 Text 입력

RxSwift의 다른 유용한 기능은 유저에 의한 직접 text 입력을 받고 반응할 수 있는 기능입니다.

반응형 text 입력 처리의 맛보기를 위해, 신용 card 입력 폼에 간단한 validation 과 card type 감지 기능을 추가할 것입니다.

Non-reactive 프로그램에서는 신용 card 입력 폼이 UITableViewDelegate 메소드들이 뒤엉켜서 처리됩니다. 종종 각각의 함수들은 많은 어떤 textField가 입력되고 있는지에 기반하여 어떤 동작과 로직이 적용되어야 하는지를 구분하는 if/else 문을 가지고 있습니다.

Reactive Programming 은 그 처리들을 좀 더 직접적으로 각각의 입력 field에 묶습니다. 마찬가지로 어떤 로직들이 어떤 text field에 적용되는지를 명확히 하면서 입력 field에 묶습니다.

BillingInfoViewController.swift로 가세요. 클래스의 처음에 다음을 추가하세요.

private let disposeBag = DisposeBag()

이전 처럼, 이것은 클래스 인스턴스가 deallocte 될 때 Observable 들이 적절하게 폐기되도록 하기 위해서 DisposeBag을 정의합니다.

유저가 신용 card 번호를 입력할 때 도움이 되는 한가지는 known card types( https://en.wikipedia.org/wiki/Payment_card_number )에 기반하여 유저가 입력하고 있는 신용 card의 종류가 무엇인지 보여주는 것입니다.

이것을 하기 위해서, //MARK: - Rx Setup 주석 아래에 다음을 추가하세요.

//MARK: - Rx Setup

private func setupCardImageDisplay() {
  cardType
    .asObservable()
    .subscribe(onNext: {
      cardType in
      self.creditCardImageView.image = cardType.image
    })
    .addDisposableTo(disposeBag)
}

곧, card type 변화에 기반해서 카드 이미지를 업데이트 할 겁니다. Observer를 Variable의 value에 추가합니다. Variable의 value가 변경될 때 실행될 클로저와 함께 추가합니다. 그리고 나서 observer가 disposeBag에서 적절하게 폐기되도록 합니다.

이제 재미있는 부분입니다.:Text 변경 처리

유저가 빠르게 타이핑할 수 있기 때문에, 모든 글자 키가 눌러질 때마다 validation을 실행시키고 싶지 않을 겁니다. 매번 validation 검사를 하는 것은 비용적으로 비싸고, 느린 UI를 유발합니다.

이것을 처리하는 좋은 방법은 validation process에 얼마나 빨리 사용자입력을 넣을 것인지 debounce 혹은 throttle 하는 것입니다. 이것은 입력되는 모든 글자가 변경될 때마다 처리되기 보다는 throttle 간격에 사용자 입력 validation 검사가 이루어 진다는 것이라는 것을 의미합니다. 아무리 빠른 입력도 앱을 멈추게 하지 않습니다.

변경될 때마다 실행되어야 할 로직이 종종 많기 때문에, throttle 하는 것은 RxSwift의 특별히 우수한 점입니다. 이 경우에는 크게 로직이 많지 않지만, 작은 throttle를 만들 충분히 가치가 있습니다.

우선, BillingInfoViewController의 property 선언 아래 다음의 코드를 추가 하세요.

private let throttleInterval = 0.1

이것은 초단위 throttle 길이에 대한 상수값입니다.

이제 다음을 추가 하세요.

private func setupTextChangeHandling() {
  let creditCardValid = creditCardNumberTextField
    .rx
    .text //1
    .throttle(throttleInterval, scheduler: MainScheduler.instance) //2
    .map { self.validate(cardText: $0) } //3

  creditCardValid
    .subscribe(onNext: { self.creditCardNumberTextField.valid = $0 }) //4
    .addDisposableTo(disposeBag) //5
}

주의: 만약 creditCardValid를 설정할 때 "Generic parameter R could not be inferred" 컴파일 에러가 발생한다면, 타입을 명시적으로 선언함으로써 Compiler를 조용하게 만들수 있습니다. (에를 들어 let creditCardValid: Observable). 이론적으로, 컴파일러는 이것을 추론할 수 있어야 합니다. 하지만 가끔 조금 도움이 필요합니다.:]

이 코드가 하는 것입니다.:

1.text 는 또 따른 RxCocoa extension입니다.(사용하기 전에 rx를 호출함으로써 카리켜지는 RxCocoa extension입니다.) 이번에는 UITextField에 대한 것입니다. 그것은 Observable value로써 textField의 내용을 리턴합니다.

2.입력을 throttle합니다. 그래서 위에서 정의한 간격에 기반한 대로만 설정한 validation이 실행됩니다. sheduler 파라미터는 더 진보된 개념입니다. 하지만 짧은 버전은 그것을 스레드로 한정됩니다. 메인 스레드에서 모두 유지하기를 원하기 때문에, MainScheduler를 사용합니다.

3.throttle된 입력을 validate(cardText:)에 적용함으로써 throttle된 입력을 변형시킵니다. validate(cardText:)는 이미 클래스에 의해서 제공되고 있습니다. 만약 카드 입력이 유효하다면, 관찰되는(observed) boolean 의 최종값은 true가 될 것입니다.

4.입력되는 값에 기반한 textField 의 유효성이 업데이트되는 Observable 을 subscribe 합니다.

5.결과인 Disposable 을 disposeBag 에 추가합니다.

(CVV로 알려진)card security code 와 expiration date 에 대한 Observable 값들을 생성하기 위해서
다음의 코드를 setupTextChangeHandling() 의 끝에 추가 하세요.

let expirationValid = expirationDateTextField
  .rx
  .text
  .throttle(throttleInterval, scheduler: MainScheduler.instance)
  .map { self.validate(expirationDateText: $0) }

expirationValid
  .subscribe(onNext: { self.expirationDateTextField.valid = $0 })
  .addDisposableTo(disposeBag)

let cvvValid = cvvTextField
  .rx
  .text
  .map { self.validate(cvvText: $0) }

cvvValid
  .subscribe(onNext: { self.cvvTextField.valid = $0 })
  .addDisposableTo(disposeBag)

이제 3개의 text field들의 유효성에 대해 설정된 Observable 값들을 얻었습니다. 다음을 추가하세요.

let everythingValid = Observable
  .combineLatest(creditCardValid, expirationValid, cvvValid) {
    $0 && $1 && $2 //All must be true
  }

everythingValid
  .bindTo(purchaseButton.rx.enabled)
  .addDisposableTo(disposeBag)

이 코드는 Observable의 combineLatest(_:) 메소드를 사용합니다. 이미 만들었던 3개의 Observable들을 받기 위해서 사용합니다. 그리고 4번째 Observable을 만듭니다. 이 Observable은 verythingValid 이라는 불립니다. 그리고 이 Observable은 모든 3개의 입력이 유효한지 아닌지에 따라 true 혹은 false 값입니다.

그런 다음 everythingValid 는 UIButton의 reactive extnsion의 enabled property에 bind 됩니다. 그래서 구매 버튼의 상태가 everythingValid의 값에 의해서 제어됩니다.

만약 모든 세 값들이 유효하다면, 기반하는 everythingValid의 값이 true일 겁니다. 만약 그렇지 않다면, 기반한 값이 false 일 겁니다. 다른 경우에 rx.enabled는 구매 버튼에 적용된 기반 값이 적용되도록 할 것입니다. 그래서 카드 상세 정보가 유효할 때만 enable 됩니다.

이제 setup methods를 만들었습니다. viewDidLoad()에 그것들을 추가 하세요.

setupCardImageDisplay()
setupTextChangeHandling()

앱을 빌드하고 실행시키세요. 신용카드 입력을 받기 위해서 초콜릿을 탭하여 카트에 추가하고, 카트로 가기위해서 카트 버튼을 탭하세요. 최소 하나 이상의 초콜릿이 카트에 있기 때문에, 체크아웃 버튼이 활성화 되어야 합니다.

카드 입력 화면을 보여줄 체크 아웃 버튼을 탭하세요.

Card Number text field에 4를 타이핑하세요. - 카드 이미지가 즉시 Visa로 변하는 것을 볼 수 있을 겁니다.

4를 지우세요. 그리고 카드 이미지가 unknown으로 변경될 겁니다. 55를 입력하세요. 이미지가 MasterCard로 변경될 겁니다.

멋집니다! 이 앱은 미국에서 4개의 메이저 타입에 대해서 지원합니다.(Visa, MasterCard, Amerian Express, Discover). 만약 이 카드 타입들중에서 하나를 가지고 있다면, 이미지가 제대로 나오고 번호가 유효한지를 보기 위해서 카드 번호를 입력해볼 수 있습니다.

주의:만약 이들 신용 카드 중 하나를 가지고 있지 않다면, PayPal()이 그들의 card sandbox를 테스트하기 위해서 사용하는 카드 번호 중 하나를 사용할 수 있습니다. 이 카드 번호들은 비록 실제로 사용할 수는 없지만, 앱에서 모든 validation을 통과 해야 합니다.

유효한 카드 번호가 미래의 어느 시간의 (2자리 월과 4자리 년도를 사용하는)expiration date, 적절한 길이의 CVV와 함께 입력되면, Buy Chocolate! 버튼이 활성화될 겁니다.

여러분이 산 것과 얼마나 지불했는지에 대한 요약 그리고 작은 이스터 에그를 보려면, 구매 버튼을 탭하세요.

축하합니다! RxSwift와 RxCocoa에게 감사합니다. 여러분은 치과의사가 뭐라고 하지 않을 만큼 초콜릿을 살수 있습니다.

여기로 부터 가야 할 곳

완료된 프로젝트는 여기( https://koenig-media.raywenderlich.com/uploads/2016/10/Chocotastic-finished-s3-rxs-3b1.zip )에서 찾을 수 있습니다.

만약 도전을 원한다면, 이 앱을 좀 더 reactive하게 위해서 몇가지 추가해 보세요.

*CartViewController를 (label 대신에)reactive table view를 이용해서 카트의 내용을 표시하도록 수정해 보세요.

*유저가 카트에서 바로 초콜릿을 추가하거나 삭제할 수 있도록 해보세요. 가격도 자동으로 업데이트 되도록 하구요.

이제 Rx programming의 맛을 보았습니다. 여기 당신에게 당산의 여행을 계속하도록 도울 리소스가 더 있습니다.

*RxSwift Slack( http://slack.rxswift.org/ )

*RxSwift’s Getting Started guide( https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md )

*Max Alexander’s talk on Rx at Realm ( https://realm.io/news/slug-max-alexander-functional-reactive-rxswift/ )

마지막으로, Marin Todorov( https://realm.io/news/slug-max-alexander-functional-reactive-rxswift/ )는 rx_marin( http://rx-marin.com/ )이라고 불리는 Reactive programming에 있어서 그의 모험에 대한 훌륭한 블로그를 가지고 있습니다. 확인해 보세요!

만약 이 튜터리얼을 통해서 배우는 것이 즐거우셨다면, 스토어에 있는 RxSwift book( https://store.raywenderlich.com/products/rxswift?_ga=1.9745141.1558722521.1451401789 )을 확인해보는 것은 어떠세요?

이것은 이 책에 있는 맛보기 입니다.:

  • 시작하기: Reactive programming 패러다임에 대한 소개를 얻고, 포함된 용어를 배우세요. 그리고 여러분의 프로젝트에서 어떻게 RxSwift를 시작할지 알아 보세요.

  • Event Management: Rx- Observable들과 Observer들의 두가지 개념을 가지고 비동기 이벤트 sequence들을 처리하는 방법을 배우세요.

  • Selective 되기: filter, transform, combine, 과 time operator들과 같은 개념을 이용해서 다양한 이벤트들을 처리하는 방법을 알아 보세요.

  • UI Development: RxSwift는 RxCocoa를 사용하는 앱의 UI작업을 쉽게 합니다. RxCocoa는 Cocoa와 UIKit 둘의 통합을 제공합니다.

  • intermediate Topics: reactive networking, multi-threading, error handling에 관한 여러분의 RxSwift 지식을 레벨업하세요.

  • Advanced Topcis: MVVM 앱 구조, scene-based navigation, service들을 통한 데이터 노출에 대해 배움으로써 RxSwift 교육을 마무리 지우세요.

  • 그리고 더 더 더!

이 책의 끝 쯤에 reactive 패러다임에서 일반적인 이슈를 해결하는 실제 겸험을 얻을 수 있습니다. - 그리고 여러번 자신의 Rx 패턴과 솔루션들을 생각해 내는데 제대로 접근하게 될 것입니다.

질문이 있으시거나 제공할 다른 Rx 리소스가 있으시다면? 아래에 코멘트를 남겨 주시거나 포럼에 남겨 주세요.

감사합니다.