RxSwift, RxDataSource, MVVM 패턴
* 이 포스트는 RxSwift 2.6 버전을 기준으로 작성되었습니다.
* JSON 파싱에는 SwiftyJSON 을 사용했습니다.
이 글을 읽기 전에 MVVM 패턴에 대한 이해를 위해 아래의 링크의 글을 읽길 권한다.
https://justhackem.wordpress.com/2017/03/05/mvvm-architectural-pattern
위 작성자 분이 github 에 샘플까지 작성해 두신것도 링크한다.
https://github.com/gyuwon/SwiftMvvmTodoList
본 포스팅도 실제 MVVM 패턴을 구현한 내용이 아닌 바인딩에 집중해져 있기 때문에,
RxTableView DataSource 관련 바인딩 기법의 하나로 이해해주었으면 한다.
꼭! 위의 MVVM 관련 글을 읽어보자!
( 아래의 포스팅은 나쁜 예제이다 MVVM 패턴으로 설계하고 싶다면 절대 아래와 같이 코딩하지는 말자.
이 글을 쓰던 시점에는 그것을 잘 알지 못했다.
나와 같은 오류를 범하지 않기를 바라며 글을 그대로 남겨둔다. )
RxSwift 는 아니지만 예제가 좋아보여서 링크~
https://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-1
이건 RxSwift 를 통해 보여주는 예제. 설명도 매우 잘되어있다.
자 이제 페이스북 뉴스피드와 같은 테이블뷰를 상상해보자.
응답은 뉴스들을 구성할 json 으로 내려올 것이다.
여기서는 다음과 같은 JSON 응답을 받았다고 가정한다.
http://www.jsoneditoronline.org/?id=9a3c4e8685fcdc8d03365dcd302b0b5a
뷰모델은 아래와 같이 만들자.
struct NewsViewModel {
let newsID: Int
let newsText: String
init(json: JSON) {
newsID = json["news_id"].intValue
newsText = json["text"].stringValue
}
}
JSON 은 서버에서 불러오겠지만 테스트를 위해 번들에서 불러오도록 아래와 같이 구성하자.
func newsViewModelLoad(){
let jsonData = NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("newsJSON", ofType: "json")!)
let json = JSON(data: jsonData!)
let newsViewModels = json["news"].arrayValue.map{ NewsViewModel(json: $0) }
// JSON 을 NewsViewModel의 배열로 만들어냈다.
}
자 이제 준비는 됐고, RxDatasource 를 통해 UITableView 와 뷰모델을 바인딩 하자.
RxDataSource 중 여기서는 RxTableViewSectionedReloadDataSource 를 사용하겠다.
RxTableViewSectionedDataSource 프로토콜을 따르고 있고, 내부적으로 SectionModel 를 사용하는 것을 RxDataSource 를 보면 알수 있다.
그러므로 우리는 NewsViewModel 을 SectionModel 로 매핑해서 바인딩 해야 한다.
ViewController 에서 아래와 같이 객체를 생성하자.
typealias TrackedModelType = SectionModel<String,NewsViewModel>
var trackedSectionViewModel = Variable([TrackedModelType]())
var sectionViewModels: Driver<[TrackedModelType]> {
get {
return trackedSectionViewModel.asDriver().map{ $0 }
}
}
모델 타입은 SectionModel 로 생성하고, 섹션은 여러개가 될수 있으므로, Variable 객체는 ( 실제 우리가 서버에서 받은 데이터를 NewsViewModel로 만들고, 이를 SectionModel로 만든뒤, 그 데이터를 넣어줄 객체이다. ) SectionModel의 배열을 값으로 가지는 객체로 생성했다.
이제 위에 만든 뉴스 모델들을 SectionModel 로 만들어서 넣어주자.
func newsViewModelLoad(){
let jsonData = NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("newsJSON", ofType: "json")!)
let json = JSON(data: jsonData!)
let newsViewModels = json["news"].arrayValue.map{ NewsViewModel(json: $0) }
trackedSectionViewModel.value = [SectionModel(model: "section", items: newsViewModels)]
}
자 이제 테이블 뷰에 바인딩 하자.
func bindDataSource() {
func createDataSource() -> RxTableViewSectionedReloadDataSource<TrackedModelType> {
let dataSource = RxTableViewSectionedReloadDataSource<TrackedModelType>()
dataSource.configureCell = { [weak self] ds, tv, ip, newsViewModel -> UITableViewCell in
let cell = tv.dequeueReusableCellWithIdentifier("NewsTableCell", forIndexPath: ip) as! NewsTableCell
cell.mainTextLabel.text = "\(newsViewModel.newsText) : newsID \(newsViewModel.newsID)"
return cell
}
return dataSource
}
self.sectionViewModels
.drive(tableView.rx_itemsWithDataSource(createDataSource()))
.addDisposableTo(disposeBag)
}
createDataSouce 에서는 RxTableViewSectionedReloadDataSource 를 만든다.
여기서는 데이터 소스의 configureCell closure 를 넣어주는데,
이를 통해 뷰모델이 뷰와 바인드된다.
만들어진 데이터 소스는 위에서 만든 driver 객체인 sectionViewModels 에 연결된다.
위의 과정을 좀더 쉽게 설명하자면
1. json data 를 서버에서 다운받는다.
2. json data 를 통해 NewsViewModel 를 만든다.
3. NewsViewModel 을 테이블뷰에 바인딩 하기위해 SectionModel 로 만든다.
4. variable 객체에 SectionModel을 넣어준다.
5. variable 객체에 변화가 발생하면 ( 모델 값이 업데이트 되면 ) driver 를 통해 tableview의 Datasource 와 바인딩 된다.
6. 테이블 뷰의 데이터 소스에서는 뷰모델을 뷰에 연결되어( configureCell ) 테이블 셀에 반영되고, 최종적으로는 테이블 뷰가 구성된다.
MVVM 패턴으로 설명해보면
Model
서버에서 받은 뉴스 모델이다.
ViewModel 은
Model을 테이블 뷰에 반영하기 위해 SectionModel객체로 매핑된다.
TableCell View 는 NewsViewModel 과 연결되어 표시된다.
View
뷰 컨트롤러는 테이블뷰를 가지고 있지만 직접적으로 모델을 테이블 뷰에 반영하거나 하지 않는다.
RxDataSource 는 SectionModel을 통해 테이블뷰는 테이블을 구성한다.
필자도 MVVM 패턴을 시작한지 얼마 되지 않아서 설명이 다소 부족할 수 있다. MVVM 은 직접 여러가지 자료들을 보고 개념을 확실히 이해하고 설계를 하는게 좋을 것 같다.
https://github.com/tilltue/RxSwift_MVVMTutorial
Git 에 예제 소스를 업로드 했다.
또 괜찮은 예제를 발견해서 링크를 첨부한다.
테이블을 구성하다보면, 여러가지 뷰모델이 생성될 수 있다.
이때마다 섹션 모델을 만들고, 바인딩 하는 것을 수행하는게 번거로울 수 있다.
다음 포스트에서는 여러가지 뷰모델을 사용할 수 있도록 tableview model protocol 을 구성해보겠다.