【发布时间】:2017-08-02 15:58:57
【问题描述】:
我是 RxSwift 和 MVVM 的新手。
我的 viewModel 有一个名为 rx_fetchItems(for:) 的方法,它负责从后端获取相关内容的繁重工作,并返回 Observable<[Item]>。
我的目标是提供一个名为 collectionItems 的 viewModel 的可观察属性,以及从 rx_fetchItems(for:) 返回的最后一个发射元素,为我的 collectionView 提供数据。
Daniel T 提供了这个我可能会使用的解决方案:
protocol ServerAPI {
func rx_fetchItems(for category: ItemCategory) -> Observable<[Item]>
}
struct ViewModel {
let collectionItems: Observable<[Item]>
let error: Observable<Error>
init(controlValue: Observable<Int>, api: ServerAPI) {
let serverItems = controlValue
.map { ItemCategory(rawValue: $0) }
.filter { $0 != nil }.map { $0! } // or use a `filterNil` operator if you already have one implemented.
.flatMap { api.rx_fetchItems(for: $0)
.materialize()
}
.filter { $0.isCompleted == false }
.shareReplayLatestWhileConnected()
collectionItems = serverItems.filter { $0.element != nil }.dematerialize()
error = serverItems.filter { $0.error != nil }.map { $0.error! }
}
}
这里唯一的问题是我当前的 ServerAPI aka FirebaseAPI 没有这样的协议方法,因为它被设计为使用单一方法来触发所有请求,如下所示:
class FirebaseAPI {
private let session: URLSession
init() {
self.session = URLSession.shared
}
/// Responsible for Making actual API requests & Handling response
/// Returns an observable object that conforms to JSONable protocol.
/// Entities that confrom to JSONable just means they can be initialized with json.
func rx_fireRequest<Entity: JSONable>(_ endpoint: FirebaseEndpoint, ofType _: Entity.Type ) -> Observable<[Entity]> {
return Observable.create { [weak self] observer in
self?.session.dataTask(with: endpoint.request, completionHandler: { (data, response, error) in
/// Parse response from request.
let parsedResponse = Parser(data: data, response: response, error: error)
.parse()
switch parsedResponse {
case .error(let error):
observer.onError(error)
return
case .success(let data):
var entities = [Entity]()
switch endpoint.method {
/// Flatten JSON strucuture to retrieve a list of entities.
/// Denoted by 'GETALL' method.
case .GETALL:
/// Key (underscored) is unique identifier for each entity, which is not needed here.
/// value is k/v pairs of entity attributes.
for (_, value) in data {
if let value = value as? [String: AnyObject], let entity = Entity(json: value) {
entities.append(entity)
}
}
// Need to force downcast for generic type inference.
observer.onNext(entities as! [Entity])
observer.onCompleted()
/// All other methods return JSON that can be used to initialize JSONable entities
default:
if let entity = Entity(json: data) {
observer.onNext([entity] as! [Entity])
observer.onCompleted()
} else {
observer.onError(NetworkError.initializationFailure)
}
}
}
}).resume()
return Disposables.create()
}
}
}
rx_fireRequest 方法最重要的一点是它接受了一个FirebaseEndpoint。
/// Conforms to Endpoint protocol in extension, so one of these enum members will be the input for FirebaseAPI's `fireRequest` method.
enum FirebaseEndpoint {
case saveUser(data: [String: AnyObject])
case fetchUser(id: String)
case removeUser(id: String)
case saveItem(data: [String: AnyObject])
case fetchItem(id: String)
case fetchItems
case removeItem(id: String)
case saveMessage(data: [String: AnyObject])
case fetchMessages(chatroomId: String)
case removeMessage(id: String)
}
为了使用 Daniel T 的解决方案,Id 必须将每个枚举案例从 FirebaseEndpoint 转换为 FirebaseAPI 中的方法。在每种方法中,请致电rx_fireRequest... 如果我是正确的。
如果它有助于更好的服务器 API 设计,我会渴望做出这种改变。所以简单的问题是,这个重构是否会改善我的整体 API 设计以及它与 ViewModel 的交互方式。我意识到这正在演变为代码审查。
还...这是该协议方法及其助手的实现:
func rx_fetchItems(for category: ItemCategory) -> Observable<[Item]> {
// fetched items returns all items in database as Observable<[Item]>
let fetchedItems = client.rx_fireRequest(.fetchItems, ofType: Item.self)
switch category {
case .Local:
let localItems = fetchedItems
.flatMapLatest { [weak self] (itemList) -> Observable<[Item]> in
return self!.rx_localItems(items: itemList)
}
return localItems
// TODO: Handle other cases like RecentlyAdded, Trending, etc..
}
}
// Helper method to filter items for only local items nearby user.
private func rx_localItems(items: [Item]) -> Observable<[Item]> {
return Observable.create { observable in
observable.onNext(items.filter { $0.location == "LA" })
observable.onCompleted()
return Disposables.create()
}
}
如果我对 MVVM 或 RxSwift 或 API 设计的方法是错误的,请批评。
【问题讨论】: