κ³°νκΉλ κ°μ μμμ μ½λλ step1 ~ 4 κΉμ§ μλλ°μ. step1 μ JSON νμΌμ λ€μ΄λ‘λνλ μμ μ λΉλκΈ°λ‘ μ²λ¦¬νκ³ μ΄λ₯Ό editView μ λμμ£Όλ κ²μ΄κ³ . step2 λ step1 κ³Ό κ°μ JSON νμΌμ tableView λ‘ λ³΄μ¬μ£Όλ μ½λμμ. κ°μμμ step2 λ ν¨μ€νμ μ μ΄λ₯Ό μ°μ΅ν΄λ΄€μ΅λλΉ π§
π MVVM + RxSwift TableView μμ
step2 μμ± μ½λμμλ νμ΄λ¨Έμ LOAD λ²νΌμ΄ μμ§λ§, λ€μ΄λ‘λ μμ μ΄ λΉλκΈ°λ‘ λλμ§ νμΈνκΈ° μν΄ νμ΄λ¨Έμ λ‘λ© & μΈλμΌμ΄ν° μ λλ©μ΄μ μ λ λκ³ , LOAD λ²νΌμ λλ₯Όλλ§λ€ λ°μ΄ν°κ° κ°±μ λλλ‘ νμ΄μ!
π± MVVM ν¨ν΄ μ μ©νκΈ°
κ°μκ° MVVM + RxSwift μΈ λ§νΌ, MVVM ν¨ν΄μΌλ‘ ꡬλΆνλ €λ©΄ μ΄λ»κ² ν΄μΌνμ§? λ₯Ό κ³ λ―Όνμ΅λλ€.
μ ννκ² κ΅¬λΆλμμ μ§λ λͺ¨λ₯΄κ² μ§λ§.. λ€μκ³Ό κ°μ΄ Model / View / View Model μ ꡬλΆνμ΄μ.
Model μ κΈ°μ‘΄ MVC μμλ λͺ¨λΈμ΄λκΉ λμΌνμ§λ§, View / View Model λΆλ¦¬κ° μ΄λ €μ μ΄μ π₯Ί
λ‘μ§ κ΄μ μμ νμν μ½λλ View Model, UI κ΄μ μμ νμν μ½λλ View Controller μ μμ±νλ€κ³ μκ°νμλ©΄ λ©λλ€!
1. Model - View μ 보μ¬μ€ λ°μ΄ν°
- members - List View Controller μμ TableView μ 보μ¬μ€ Member νμ μ λ°°μ΄μΈ λͺ¨λΈ λ°μ΄ν°
- selectedMember - Detail View Controller μμ μμΈνκ² λ³΄μ¬μ€ Member νμ μ λͺ¨λΈ λ°μ΄ν°
2. View (View Controller) - UI μμ λ€
- TableView, Cell λ± UI μ κ΄λ ¨λ μμ μ μ€ν 리보λλ‘ μ§ννμ΅λλ€.
- View, View Controller μμ λ°μ΄ν°λ₯Ό μ κ·Όν λμλ view Model μ ν΅ν΄ μ κ·Όνλλ‘ ν©λλ€!
- TableView μ λ°μ΄ν°λ₯Ό 그리기 μν΄ BehaviorSubject μΈ members μ tableView.rx.items λ₯Ό bind ν΄μ£Όμμ΅λλ€. * UITableViewDataSource νλ‘ν μ½μ κΈ°λ³Έλ©μλμ ꡬννλ κ²κ³Ό λμΌν¨.
- TableView μ κ° Cell μ λλ₯Όλλ§λ€ Detail VC λ‘ μ΄λνκΈ° μν΄, tableView.rx.modelSelected λ₯Ό subscribe νμ¬, Detail VC λ‘ μ΄λνκ²λ νμ΅λλ€. * UITableViewDelegate νλ‘ν μ½μ didSelectRowAt λ©μλμ λμΌν¨.
- μΆκ°μ μΌλ‘ κ³ λ €ν΄μΌν λΆλΆμ UITableViewCell κ³Ό DetailVC μμ μ΄λ―Έμ§ λ€μ΄λ‘λμ λν μμ μ λλ€!
- Member ꡬ쑰체λ avatar μ΄λ―Έμ§μ λν url λ§ κ°μ§κ³ μκΈ° λλ¬Έμ, μ΄λ₯Ό λ€μ νλ² λΉλκΈ°λ‘ λ€μ΄λ‘λνλ μμ μ΄ νμν΄μ!
π ListViewController / DetailViewController / MemberCell μμΈν보기
3. View Model - μ€μ λ‘μ§ κ΄λ ¨λ μμ
- View Model μ UIμ λ 립μ μ΄λ©°, μ€μ λ‘μ§μ κ΄λ ¨λ μμ λ€μ μνν©λλ€! * UI μ λ 립μ μ΄λ―λ‘ Testable ν©λλ€.
- View Model μ model μ κ°κ³ μμΌλ©°, ν΄λΉ μμ μμλ members, selectedMember λΌλ λͺ¨λΈμ κ°μ§κ³ μμ΅λλ€.
- μ¬μ€ μ΄ μμ λ λͺ¨λΈμ κ°κ³΅νλ μμ μ΄ μκΈ° λλ¬Έμ, JSON λ°μ΄ν°λ₯Ό λΉλκΈ°λ‘ λ€μ΄λ‘λλ°μ μ μ₯νλ μμ λ§ μννκ³ μμ΄μ.
- μ΄ λ, APIManager.fetchData λ©μλλ₯Ό ν΅ν΄ λ°νλ°μ Observable<Data> λ₯Ό subscirbe νμ¬ onNext μ΄λ²€νΈ λ°μ μ, map μ ν΅ν΄ data -> [Member] λ‘ λ§λ€μ΄μ£Όκ³ , ν΄λΉ λ°°μ΄μ κ°κ³ members (Subject) μ onNext λ₯Ό λ°©μΆνλλ‘ ν©λλ€.
import Foundation
import RxSwift
class MemberViewModel {
static let shared = MemberViewModel() // singleton
var members = BehaviorSubject<[Member]>(value: [])
var selectedMember = Member.EMPTY
init() {
reloadData()
}
func reloadData() {
// Observable<Data> --> subscribe --> members
_ = APIManager.fetchData(url: APIManager.MEMBER_LIST_URL)
.map { data -> [Member] in
let members = try! JSONDecoder().decode([Member].self, from: data)
return members
}
.take(1) // λ²νΌ λλ₯Ό κ²½μ°μλ νΈμΆλλ―λ‘, 1λ²λ§ μννκ²λ ν¨
.subscribe(onNext: { self.members.onNext($0) })
}
}
4. APIManager - λ€νΈμν¬ λ€μ΄λ‘λ λΉλκΈ° μμ
- URLSession μ ν΅ν΄ data λ₯Ό λ€μ΄λ‘λνλ λΉλκΈ° μμ μ λ°λ‘ APIManager ν΄λμ€ λ΄μ λ©μλλ‘ λ§λ€μμ΅λλ€.
- APIManager.fetchData(url:) λ©μλλ Observable<Data> λ₯Ό λ°νν©λλ€.
- μ΄ λ©μλλ₯Ό ν΅ν΄ μμ±λ Observable<Data> λ₯Ό subscribe νμ¬ members λͺ¨λΈ κ°μ λ³νμν€κ³ ,
- μ΄λ―Έμ§ λ€μ΄λ‘λ μμ μμλ Observable<Data> λ₯Ό subscribe νμ¬ UIImage λ‘ λ³ννλλ‘ κ΅¬ννμ΅λλ€.
import Foundation
import RxSwift
class APIManager {
static let MEMBER_LIST_URL = "https://my.api.mockaroo.com/members_with_avatar.json?key=44ce18f0"
// JSON url -> Observable<Data> // --(subscribe)--> Member model
// Image url -> Observable<Data> // --(subscribe)--> UIImage
static func fetchData(url: String) -> Observable<Data> {
return Observable.create() { emitter in
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
// λ°μ΄ν° μμ μ€ν¨
if let error = error {
emitter.onError(error)
return
}
// λ°μ΄ν°κ° μμ λ http status code μ λ¬
guard let data = data else {
let httpResponse = response as! HTTPURLResponse
emitter.onError(NSError(domain: "no data", code: httpResponse.statusCode, userInfo: nil))
return
}
// μ±κ³΅ν κ²½μ°
emitter.onNext(data)
emitter.onCompleted()
}
task.resume()
return Disposables.create {
task.cancel() // dispose λ κ²½μ°
}
}
}
}
μ 체 μ½λ
μ°Έκ³ λ§ν¬