Skip to content

Latest commit

 

History

History
580 lines (432 loc) · 25.7 KB

File metadata and controls

580 lines (432 loc) · 25.7 KB

이글은 "A practical MVVM example in Swift – Part 2 (featuring RxSwift)"에 대한 번역입니다. (http://candycode.io/a-practical-mvvm-example-in-swift-part-2/) (번역 허락을 받은 것은 아니지만, 도움이 필요하신 분들을 위해서 올려봅니다. 문제가 될 경우 바로 삭제하겠습니다. ㅡㅜ)

A practical MVVM example in Swift – Part 2 (featuring RxSwift)

the practical MVVM example in Swift 파트 2에 오신 것을 환영합니다. 파트1을 놓치셨다면, 여기(http://candycode.io/a-practical-mvvm-example-in-swift-part-1/)에서 확인하세요.

MVVM을 의미있는 것에 연결시키기

이 튜터리얼의 파트 1에서 MVVM을 적용함에 있어서의 이점을 살펴 보았습니다. 하지만 데이터를 보여주는 것이 대부분이었죠. 이번에는 마찬가지로 데이터 갱신을 해볼 것입니다.

무엇을 사용할까?

iOS에서는 첫번째로 머리 속에 떠오르는 것은 보통 Key-Value Observing (KVO)가 입니다. 꽤 괜찮습니다. 하지만 상당량의 boilerplate(사용하기 위해서 기본적으로 있어야 하는) 코드를 작성해야 합니다. 그리고 우리는 그런 것을 원하지 않습니다. 안 그런가요?

이 예제 때문에, 저는 Functional reactive programming 이라고 불리는 재미있는 프로그래밍 패러다임을 가지고 놀기로 결정했습니다. 만약 여러분이 그것과 친숙하지 않다면, @andrestaltz 에 의해서 소개된 이 훌륭한 문서를 읽어 보세요. 그런 다음에 당연히(?) 돌아오세요.

FRP 의 iOS 구현체가 몇가지가 있습니다. 가장 유명한 2가지가 ReactiveCocoa 와 RxSwift 이죠.

저의 지난 Objective-C 프로젝트에서는 ReactiveCocoa를 사용했었지만, RxSwift는 Rx(https://github.com/Reactive-Extensions/Rx.NET) 의 Swift 구현체이기 때문에, 이 프로젝트에서 사용할 겁니다. 구현체 자체로는 크게 문제 될 것이 없습니다만, 짧지만 강력하고 작은 데모를 작성하는데 MVVM과 함께 어떻게 사용하는지 시연하는 것이 문제입니다.

주의: 이 포스트는 우선적으로 MVVM에 관한 것이기 때문에 RxSwift에 대해서 너무 자세히 들어가지는 않을 겁니다.

RxSwift 설치하기

우리는 CocoaPods를 위한 RxSwift 설치 가이드를 따를 겁니다. Podfile을 생성합니다. (이 포스트가 작성된 것이 2016년 5월이기 때문에, Swfit 2.0기준으로 작성된 것으로 보인다. )

def rx_swift
  pod 'RxSwift',    '~> 2.0'
  pod 'RxCocoa',    '~> 2.0'
end

target 'MVVM' do
  rx_swift
end

target 'MVVMTests' do
  rx_swift
end

target 'MVVMUITests' do

end

Swift 3.0 에서는 아래와 같이 작성합니다. (CocoaPods 1.1.1 기준)

# Podfile
use_frameworks!

target 'YOUR_TARGET_NAME' do
    pod 'RxSwift',    '~> 3.0'
    pod 'RxCocoa',    '~> 3.0'
end

# RxTests and RxBlocking make the most sense in the context of unit/integration tests
target 'YOUR_TESTING_TARGET' do
    pod 'RxBlocking', '~> 3.0'
    pod 'RxTest',     '~> 3.0'
end

그리고 pod install을 실행합니다. 그리고 MVVM.xcworkspace를 엽니다.

CarViewModel을 reactive로 만들기

확실히, 첫 단계는 CarViewModel을 reactive로 만드는 겁니다. Car model은 UI와 직접적으로 소통하지 않기 때문에 변경되지 않도록 유지해야 합니다. 기억나시나요? Car Model은 단지 데이터만 보관하는 겁니다.

우리는 signal, observer, observable을 이용해서 작업할 것이기 때문에, 모든 것을 clean up할 방법을 가지고 있음을 확실히 할 필요가 있습니다. 그래서 우리는 영구적으로 할당되는 어떤 resource들도 가지지 않습니다.(메모리 릭을 유발하기 때문에)

CarViewModel에 우리가 할 첫 번째 일은 RxSwift 와 RxCocoa를 import하고 dispose bag을 추가 하는 겁니다.

import RxSwift
import RxCocoa

// class CarViewModel {
// ...

  let disposeBag = DisposeBag()

// ...
// }

이제 CarViewModel의 property들을 살짝 비틀겁니다. 그러면 그 property들은 관찰(observe)하고 변화에 반응(react)할 수 있게 됩니다. BehaviorSubject가 그런 것에 최적입니다.

// class CarViewModel {
// ...

  var modelText: BehaviorSubject<String>
  var makeText: BehaviorSubject<String>
  var horsepowerText: BehaviorSubject<String>
  var kilowattText: BehaviorSubject<String>
  var titleText: BehaviorSubject<String>

// ...
// }

눈썰미가 있으신 분들은 이전에 없던 kilowattText가 추가된 것을 알아 채셨을 겁니다. kilowattText는 일단 binding 해놓으면 인생이 좀 쉽게 만들어 주기 때문에 추가 되었습니다.

이제 이전에 CarViewModel에 Car를 할당했던 initializer만 남았습니다. 하지만 이번에는 좀더 많은 일을 해야 합니다.(미래에 제값을 톡톡히 할 일들입니다.)

여기 나중에 좀더 설명할 full initializer가 있습니다.

// class CarViewModel {
// ...

init(car: Car) {
  self.car = car

  // 1
  modelText = BehaviorSubject<String>(value: car.model) // initializing with the current value of car.model
  modelText.subscribeNext { (model) in
    car.model = model                                   // subscribing to changes in modelText which will be reflected in CarViewModel's car
  }.addDisposableTo(disposeBag)

  // 2
  makeText = BehaviorSubject<String>(value: car.make)
  makeText.subscribeNext { (make) in
    car.make = make
  }.addDisposableTo(disposeBag)

  // 3
  titleText = BehaviorSubject<String>(value: "\(car.make) \(car.model)")
  [makeText, modelText].combineLatest { (carInfo) -> String in
    return "\(carInfo[0]) \(carInfo[1])"
  }.bindTo(titleText).addDisposableTo(disposeBag)

  // 4
  horsepowerText = BehaviorSubject(value: "0")
  kilowattText = BehaviorSubject(value: String(car.kilowatts))
  kilowattText.map({ (kilowatts) -> String in
    let kw = Int(kilowatts) ?? 0
    let horsepower = max(Int(round(Double(kw) * CarViewModel.horsepowerPerKilowatt)), 0)
    return "\(horsepower) HP"
  }).bindTo(horsepowerText).addDisposableTo(disposeBag)

}

// ...
// }

첫 두가지 것들은 대부분 같습니다. - 현재 값을 가지고 초기화 되고 car 값이 각기 갱신되도록 변경사항들을 등록 하는 것입니다. 세번째 것들은 (titleText) 약간의 속임수입니다. makeText와 titleText가 받는 변경사항에 연결시키는 combineLatest을 사용합니다. 그것들을 하나의 string에 연결하고 그것을 titleText에 할당합니다.

네번째 것들은 kilowatts로 부터 horsepower를 계산하기 위해 map() 이라고 불리는 특성을 사용합니다. 그것은 kilowattText subject가 가지는 string이 무엇이든 number로 해석하려고 시도합니다. 그리고 계산되고 장식된 horsepower string을 horsepowerText로 연결합니다.

아마도 여러분이 알다시피, 앞에서 이야기한 이유(메모리 릭을 피하기 위해서 모든 것을 clean up할 방법)때문에 disposeBag 에 모든 signal을 명시적으로 추가합니다.

프로젝트를 다시 컴파일하기

완전히 새로운 개념을 앱에 도입했습니다. 그래서 실행 시키려고 하면 TableViewController에서 2줄에서 에러가 발생합니다.

// override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// ...

  cell.textLabel?.text = carViewModel.titleText
  cell.detailTextLabel?.text = carViewModel.horsepowerText

// ...
// }

이 오류들은 titleText 과 horsepowerText가 더이상 일반적인 String이 아니기 때문이다. 좋은 소식은 TableViewController를 완전히 버리고 약간의 Rx 마술을 포함할 (좀 더 짧은) 새로운 View Controller를 가지고 시작할 것이라는 겁니다.

ReativeTableViewController

첫째로, 기존의 TableViewController.swift과 Main.stroyboard에서 해당 View Controller를 지웁니다.

다음으로, 새로운 (기본 UIViewController)를 추가하고 ReativeTableViewController라고 이름을 정합니다.

Main.storyboard에 아무것도 없기 때문에, 관계된 View Controller를 추가하고 그것의 class에 ReativeTableViewController를 설정합니다.

주의:예제에서 navigation을 사용할 것입니다. 그래서 Editor> Embed in> navigation Controller로 가서 선택함으로써, navigation Controller안에 ViewController를 포합시켰습니다.

우리는 UIViewController에 ReativeTableViewController를 설정했습니다. 그래서 tableView 자체를 추가 해야 합니다. 이렇게 tableView를 fullscreen으로 설정했습니다.

이제 우리는 swift class와 storyboard에 View Controller를 가지고 있습니다. 그것들을 IBOutlet으로 연결하지 않으면 춤은 끝나지 않을 겁니다. 우리는 어떻게 할지 알고 있습니다. 그렇죠? (힌트: View > Assistant Editor> Show Assistant Editor, 그런 후에 tableView를 ReativeTableViewController의 시작으로 Ctrl + drag)

Custom table view cell

파트 1의 독자중 한분이 지적했듯이, 예제에 우리는 지정된 Car View가 없습니다. 이제 하나 추가 할 겁니다.

새로운 UITableViewCell을 새롭게 만들어진 table view에 drag 하고 cell에 이미지 하나와 두개의 label을 추가합니다.

추신:만약 여러분이 AutoLayout에 친숙하시다면, AutoLayout Fundamentals for iOS(https://gumroad.com/l/autolayoutfundamentals) 를 확인해보세요.

우리의 custom cell을 위해서 새로운 class를 추가할 필요가 있을 겁니다. 이름을 CarTableViewCell이라고 합시다.

다음 할 것은 우리의 prototype cell 의 class를 CarTableViewCell로 설정하는 것입니다. 그렇게 해서 우리는 storyboard를 우리의 CarTableViewCell에 연결할 수 있습니다.

우리는 몇가지 반응이 일어날 것을 압니다. 그래서 DisposeBag과 한가지 더 중요한 것 - CarViewModel 객체를 가지는 변수를 추가합시다.

import RxSwift // Don't forget this!

// class CarTableViewCell: UITableViewCell {
// ...

  let disposeBag = DisposeBag()
  var carViewModel: CarViewModel?

// ...
// }

기본틀은 설정되었습니다. 자, 우리의 ReativeTableViewController를 reactive하게 만들기 위해서 RxSwift를 어떻게 사용해야 하는지 마지막으로 한번 봅시다.

우리가 봐 오던 것보다는 가장 boilerplate(기본적으로 있어야 하는 틀)가 적은 table view controller

ReativeTableViewController에서 시작하기 위해서, 우리는 두가지를 추가 해야 합니다.

  • 반응성(reactiveness)을 위한 DisposeBag 과 명확한 car data source:
import RxSwift // Don't forget this!

// class ReactiveTableViewController: UIViewController {
// ...

  var cars: Variable<[CarViewModel]> = Variable((UIApplication.sharedApplication().delegate as! AppDelegate).cars)
  let disposeBag = DisposeBag()

// ...
// }

cell size를 정의하기 위해서, viewDidLoad() 에 이 줄을 추가 합니다.

// class ReactiveTableViewController: UIViewController {
// ...

  override func viewDidLoad() {
    super.viewDidLoad()

    tableView.estimatedRowHeight = 80 // cells will be 80pt high
 }

// ...
// }

어디쯤에서 반응성(reactiveness)를 요청할 수 있을까요? 이것을 확인해 보세요.(아직 viewDidLoad안 입니다.)

// class ReactiveTableViewController: UIViewController {
// ...
//   override func viewDidLoad() {
//     ..

    cars.asObservable().bindTo(tableView.rx_itemsWithCellIdentifier("CarCell", cellType: CarTableViewCell.self)) { (index, carViewModel: CarViewModel, cell) in
      cell.carViewModel = carViewModel
    }.addDisposableTo(disposeBag)

    // Is this the real life? Is this just fantasy?

//     ..
//   }
// ...
// }

만약 우리가 앱을 실행시려고 하면, 아직 동작 안할 것입니다. 하지만, "커멘드 키 + B"로 컴파일은 되어야 한다.

중간 정리

이제까지 우리가 해왔던 것을 정리해보면,

-우리 프로젝트에 RxSwift를 추가했고,
-CarViewModel를 비틀어서 Rx-friendly 값을 가지게 되었습니다.
-기존의 TableViewController를 제거했습니다.
-기존의 TableViewController를 UIViewController를 상속한 ReactiveTableViewController로 교체 했습니다.
-(UITableViewCell을 상속한)CarTableViewCell을 추가했습니다.
-storyboard로 부터 모든 것을 code로 연결시켰습니다.
-우리가 봐 오던 것보다는 가장 boilerplate(기본적으로 있어야 하는 틀)가 적은 table view controller를 작성했습니다.

display-puzzle의 하나 빠진 조각은 두개의 label과 image view에 실제 데이터를 표시하기 위해 CarTableViewCell을 설정하는 것입니다.

Reactive table view cell

우리의 (깨끗해진) CarTableViewCell 은 현재 이렇게 보입니다.

import UIKit
import RxSwift

class CarTableViewCell: UITableViewCell {

  @IBOutlet weak var carPhotoView: UIImageView!
  @IBOutlet weak var carTitleLabel: UILabel!
  @IBOutlet weak var carPowerLabel: UILabel!

  let disposeBag = DisposeBag()
  var carViewModel: CarViewModel?

}

carViewModel을 cell에 할당할 때, 아무것도 발생하지 않습니다. 그것을 바꿔봅시다.

// class CarTableViewCell: UITableViewCell {
// ...

  var carViewModel: CarViewModel? {
    didSet {
      guard let cvm = carViewModel else {
        return
      }

      cvm.titleText.bindTo(carTitleLabel.rx_text).addDisposableTo(self.disposeBag)
      cvm.horsepowerText.bindTo(carPowerLabel.rx_text).addDisposableTo(self.disposeBag)
    }
  }

// ...
// }

CarViewModel에서 했던 것을 기억한다면, titleText와 horsepower를 Car model에 연결했고, titleText과 horsepower이 변경될 때마다 그것들의 property들은 signal을 발생시킬 겁니다.

위의 재미있는 두 줄에서, 우리는 기본적으로 그런 observale들을 cell 안에 carTitleLabel 과 carPowerLabel label들에 연결 시켰습니다. 그리고 우리는 이미 disposable bags에 이미 친숙합니다. 그렇죠?

아직 cell과 관련해서 100% 완료하지 않았습니다. 그러나 우리는 안정적으로 프로젝트를 실행시킬 수 있습니다. 그리고 테이블 뷰에 적절하게 표현되는 세 car 를 볼 수 있습니다. 만세!

명백히, car 사진들이 없습니다. 사진이 없으면 약간 지루하시죠, 그렇죠? 우리의 첫 예제에서, 우리는 NSData(contentsOfUrl:)을 이용해서 꽤 괜찮았던 async data fetching을 사용했습니다. 그러나 우리는 이제 reactive입니다. 우리는 더 잘 할 수 있습니다.

사진 loading을 enable 시키기 위해서, 우리는 RxSwift에서 지원하는 NSURLSession을 사용할 수 있습니다.

// var carViewModel: CarViewModel? {
// ...

  guard let photoURL = cvm.photoURL else {
    return
  }

  NSURLSession.sharedSession().rx_data(NSURLRequest(URL: photoURL)).subscribeNext({ (data) in
    dispatch_async(dispatch_get_main_queue(), {
      self.carPhotoView.image = UIImage(data: data)
      self.carPhotoView.setNeedsLayout()
    })
  }).addDisposableTo(self.disposeBag)

// ...
// }

"커맨드 키" + R 하고 자! 갑시다!

테스트는 어떤가요?

여러분이 옳습니다. UI test들은 UI가 비슷하고 파트1에서 유지된 모든 정도가 유지되고 있기 때문에 여전히 통과 되어야 합니다. Unit test들은 String들이 아니라 BehaviorObjects을 유지하기 위해서 CarViewModel을 변경했기 때문에 심지어 build도 되지 않을 것입니다.

unit tests를 다시 통과시키기 위해서, 우리는 그것을 유지하는 현재 값을 표시하는 모든 BehaviorObjects들에 대한 value()를 호출해야 합니다.

func testCarViewModelWithFerrariF12() {
  let ferrariF12 = Car(model: "F12", make: "Ferrari", kilowatts: 544, photoURL: "http://auto.ferrari.com/en_EN/wp-content/uploads/sites/5/2013/07/Ferrari-F12berlinetta.jpg")
  let ferrariViewModel = CarViewModel(car: ferrariF12)
  XCTAssertEqual(try! ferrariViewModel.modelText.value(), "F12")
  XCTAssertEqual(try! ferrariViewModel.makeText.value(), "Ferrari")
  XCTAssertEqual(try! ferrariViewModel.horsepowerText.value(), "730 HP")
  XCTAssertEqual(ferrariViewModel.photoURL, NSURL(string: ferrariF12.photoURL))
  XCTAssertEqual(try! ferrariViewModel.titleText.value(), "Ferrari F12")
}

"커멘드 키" + U 로 전체 test suite를 실행시키세요. 모든 테스트가 통과되어야 합니다.

갱신을 시작하자!

이제까지 우리는 기본적으로 파트1에서 했던 것을 반복했습니다. - MVVM architecture 를 설정, cars data source로 부터 데이터를 표시. 그러나 지금은 훨씬 더 간결해졌습니다. 최고의 부분은 아직 오지 않았습니다.

이 글의 시작에서 data 갱신을 하겠다고 말했습니다. 그것을 하기 위해서, 우리는 데이터를 갱신하고 MVVM 방식의 장점을 볼 수 있는 3가지 필드가 있는 새로운 View Controller를 추가 할 겁니다.

그 전에, 우리는 CarViewModel을 갱신하고 우리가 예상하는 대로 모든 것이 계산되는 것을 검증하기 위해 적어도 하나 이상의 테스트를 추가해야 합니다.

func testAdjustingKilowattsAdjustsHorsepower() {
  let someCar = Car(model: "Random", make: "Car", kilowatts: 100, photoURL: "http://candycode.io")
  let someCarViewModel = CarViewModel(car: someCar)
  XCTAssertEqual(try! someCarViewModel.horsepowerText.value(), "134 HP")
  someCarViewModel.kilowattText.onNext("200")
  XCTAssertEqual(try! someCarViewModel.horsepowerText.value(), "268 HP")
  // Minimum power is 0
  someCarViewModel.kilowattText.onNext("-20")
  XCTAssertEqual(try! someCarViewModel.horsepowerText.value(), "0 HP")
}

이 새로운 테스트에서 horsepowerText를 적절하게 kilowatts로 변환되는 것을 검증합니다. CarViewModel initializer에 있는 우리의 똑똑한 map() 때문에, 값이 적절하게 계산되고 심지어 마이너스 kilowatts 값이라도 0으로 표현되도록 처리됩니다.

이제 우리는 View Model이 제대로 작동한다고 자신할 수 있습니다. 우리는 car 배열에 데이터를 갱신할 수 있게 할 새로운 view Controller 를 만드는 것을 계속 할 수 있습니다.

CarViewController

약간의 UI를 위한 시간! CarViewController 라고 불리는 UIViewController를 상속한 새로운 classs를 것으로 시작합니다.

그런 다음, Main.storyboard 에서 3개의 textField를 가지는 ViewController를 추가할 것입니다. 그 ViewController는 ReactiveTableViewController에 showCar라고 불리는 segue로 연결될 겁니다.

이 textField들을 연결하기 위해서, 우리는 무엇을 해야 하는지 알고 있습니다. 그렇지요? ViewController의 class를 CarViewController로 설정하고 storyboard로 부터 그것들을 Ctrl + drag 하여 Xcode안에 Assistant Editor를 사용해서 우리의 클래스에 연결합니다. 결과는 이렇습니다.

우리는 또한 CarViewModel을 가지는 변수가 필요하고, RxSwift를 위한 dispose bag이 필요합니다.

import RxSwift // Don't forget this

// class CarViewController: UIViewController {
// ...

  var carViewModel: CarViewModel?
  let disposeBag = DisposeBag()

// ...
// }

자 이제 viewDidLoad 함수로 전환하여 데이터를 갱신하고 표시하는 코드를 작성합시다. 만약 우리가 과거에 Functional reactive programming 과 MVVM 없이 했다면, textField delegate가 필요하고, user가 편집하는 필드를 확인해야 하고, 필드들을 Car 속성에 map하거나 적절한 필드가 갱신하기 위해 일련의 if나 switch를 가져야 할 것입니다.

그러나 RxSwift와 MVVM와 함께라면 그럴 필요가 없습니다.

시작합니다.(우리는 viewDidLoad에 있다는 것을 주의하세요.)

override func viewDidLoad() {
  super.viewDidLoad()

  // We're using guard to make sure we've got a carViewModel assigned
  guard let carViewModel = carViewModel else {
    return
  }   

  // Assigning carViewModel's properties to our three text fields
  carViewModel.makeText.bindTo(makeField.rx_text).addDisposableTo(disposeBag)
  carViewModel.modelText.bindTo(modelField.rx_text).addDisposableTo(disposeBag)
  carViewModel.kilowattText.bindTo(kilowattField.rx_text).addDisposableTo(disposeBag)

  // Binding whatever the input is in our three text fields to our carViewModel's properties
  makeField.rx_text.bindTo(carViewModel.makeText).addDisposableTo(disposeBag)
  modelField.rx_text.bindTo(carViewModel.modelText).addDisposableTo(disposeBag)
  kilowattField.rx_text.filter({ (string) -> Bool in
   // Validate we are only passing integer or empty strings (which result in 0 HP)
   return Int(string) != nil || string.isEmpty
  }).bindTo(carViewModel.kilowattText).addDisposableTo(disposeBag)

  // Assigning the titleText to our View Controller title
  carViewModel.titleText.subscribeNext { (title) in
    self.navigationItem.title = title
  }.addDisposableTo(disposeBag)
}

이것은 조금 복잡해 보일 수도 있습니다만, 주석들이 안내해줄 겁니다. 실제로 CarViewModel 의 initializer 에서 보지 못한 새로운 것은 없습니다. 이제 (주석 포함해서) 15줄의 짧은 이 코드들을 text field가 어떤 car 속성을 갱신하는 확인하는 UITextFieldDelegate들을 이용해서 작성했던 것들과 비교해 봅시다... 더할 나위 없는 기쁨입니다.

좋습니다. 우리가 field들을 제대로 연결했는지를 확인하기 위해서 우리는 실제로 carViewModel을 할당하고 우리의 showCar segue를 실행해야 합니다. 우리는 이것을 위해 RxSwift를 사용할 것이고, 우리의 ReactiveTableViewController를 깔끔하고 좋게 유지합니다. 파일을 열고 viewDidLoad 의 끝으로 이동합시다. 그리고 이걸을 추가합시다.

// class ReactiveTableViewController: UIViewController {
// ...
//   override func viewDidLoad() {
//   ...

      tableView.rx_itemSelected.subscribeNext { (indexPath) in
        self.performSegueWithIdentifier("showCar", sender: indexPath)
        self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
      }.addDisposableTo(disposeBag)

//   ...
//   }
// ...
// }

이 코드들이 무엇하나요? 여러분이 함수 이름을 본다면, 그것 자체로 설명이 될 겁니다. item이 선택될 때마다 (rxItemSelected), 우리의 subscribeNext는 fire될 것입니다. 그리고 우리는 showCar segue를 실행할 겁니다. 그리고 그 줄을 deselect 합니다(이것을 잊어서는 안됩니다.).

이 시점에서 프로젝트를 실행시킨다면, CarViewController 가 나타나야 합니다. 그러나 viewDidLoad 에서 guard들이 리턴하기 때문에 textField들이 어떤 데이터도 유지 하지 않아야 합니다.

우리는 아직 선택된 CarViewModel을 우리의 새로운 VC에 넘겨주지 않습니다. 그것을 하는 방법입니다.

// class ReactiveTableViewController: UIViewController {
// ...

  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    guard let indexPath = sender as? NSIndexPath, carVC = segue.destinationViewController as? CarViewController else {
      return
    }
    carVC.carViewModel = cars.value[indexPath.row]
  }

// ...
// }

올바른 목표 대상 view controller을 가지고 있는지 와 sender object 가 indexPath 인지를 확실히 하기 위한 guard. 그것은 또한 carViewModel 그 자체가 될수도 있습니다. - 두 가지 접근 모두 괜찮습니다.

다음으로, 목표 VC 에 있는 carViewModel 설정하고 프로젝트를 실행시킵니다.

우리가 얼마나 적게 코드를 작성했는지 생각해 볼때, 결과는 놀랄만큼 아름답습니다. 아직 완벽하지는 않습니다. - 우리는 아직 몇가지 확인을 더 넣어야 합니다. 사용자가 미친 값을 kilowattField과 그런 것들에 에 넣지 않는지,- 하지만 여전히 binding 매커니쯤과 결합된 MVVM 의 힘을 보여주는 멋진 데모입니다.

마지막 결과입니다.

늘 그렇듯이, 우리는 full project를 github(https://github.com/jurezove/mvvm-swift)에서 혹은 zip 버전으로 다운로드(https://github.com/jurezove/mvvm-swift/archive/master.zip) 받을 수 있습니다. 이 포스트가 좋았다던가 질문이 있다면, 코멘트를 아래에(원 사이트 - http://candycode.io/a-practical-mvvm-example-in-swift-part-2/) 코멘트를 남겨 주세요. 저의 newsletter를 구독하세요!

이 글을 읽어 주셔서 감사합니다.