brunch

You can make anything
by writing

C.S.Lewis

by Tilltue Sep 09. 2016

RxCocoa, UITableView 구현 #1

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 를 통해 보여주는 예제. 설명도 매우 잘되어있다.

https://medium.cobeisfresh.com/implementing-mvvm-in-ios-with-rxswift-updated-for-swift-2-51cc3ef7edb3#.a3b1gmxn7



자 이제 페이스북 뉴스피드와 같은 테이블뷰를 상상해보자.

응답은 뉴스들을 구성할 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 을 구성해보겠다.

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari