一个 ViewModel 的修改建议

# 摘要

有位朋友问了下关于这段 ViewModel 的问题。 在 init 中 如果用 subscribeNext 在用到 weak self 是 有错误 , 如何避免这个错误?

import Result
import ObjectMapper
class PhgViewModel: NSObject {
    let refreshTrigger = PublishSubject<Void>()
    let loadNextPageTrigger = PublishSubject<Void>()
    let elements = Variable<[Brand]>([])
    let page = Variable(1)
    let hasNextPage = Variable(true)
    let refreshing = Variable(false)
    let loading = Variable<Bool>(true)
    let error = PublishSubject<ErrorType>()
    init(
        input: (
        refreshTriger: Observable<Void>,
        loadMoreTriger: Observable<Void>,
        )
    ) {
        let refreshRequest :Observable<Result<[Brand],NSError>>  = input.refreshTriger
            .map {1}
            .flatMapLatest { page -> Observable<Result<[String : AnyObject],NSError>> in
                EmeAPI.sharedInstance.request(PhgApi.getList(begin: page , length: 10))
                .observeOn(Mainscheduler.instance)
                .mapRegMessageData([String : AnyObject])
                .map{Result.Success($0)}
                .catchError{ error in
                    let errir  = error as NSError
                    let right = Result<[String : AnyObject], NSError>> in
                    return Observable.just(right)
                }
            }.flatMapLatest { response -> Observable<Result<[Brand],NSError>> in
                return Observable.create { observer -> Disposable in
                    switch response {
                    case .Success(let relDict):
                        if let brand = Mapper<Brand>().mapArray(relDict["list"]){
                            observer.onNext(Result.Success(brand))
                        }
                        observer.onNext(Result.Failure(NSError.init(domain: "获取数据错误", code: 100, userInfo: nil)))
                    case .Failure(let error):
                        observer.onNext(Result.Failure(error))
                    }
                    return NoDisposable.instance
                }
            }
            refreshRequest.subscribeNext{ [weak self]  result in
                switch result {
                case .Success(let branch):
                    self?.page.value  = 2
                    self?.elements.value = brands
                case .Failure(  let error) :
                    print(error)
                }
            }
        ...

# 编译错误

先解决编译错误。

不能调用 self 是因为初始化没有完成,可能这个问题不会被一下子发现。仔细看下代码,会发现 RhgViewModel 继承自 NSObject ,也就是说还没有调用 super.init 方法。在初始化最开始加入 super.init() 就可以了。

注:从这段代码中可以看到 NSObejct 是没有存在的必要,我们可以考虑去掉。如果是因为这里面还有一些 Objective-C 的 delegate 之类的代码,可以尝试实现一个 ProxyDelegate 解决。

# Driver

Driver 不是必备的,当然可以不用,具体可以参考后面的文章 Driver 和 Observable

# 逻辑错误

代码中有一个严重逻辑错误(54 - 57 行):

if let brand = Mapper<Brand>().mapArray(relDict["list"]){
		observer.onNext(Result.Success(brand))
}
observer.onNext(Result.Failure(NSError.init(domain: "获取数据错误", code: 100, userInfo: nil)))

不论是否成功,后面都会收到一个获取数据错误Error 。 以及这里没有调用 observer.onCompleted()

# 代码规范

在继续改进的话题,谈一谈代码规范也是比较好的,有如下几点问题:

  • 使用了 NSError 的同时使用了 ErrorType
  • 属性 error 没有清晰的表明是什么 error (虽然可能目的是表明统一下 error
  • refreshing -> isRefreshingloading -> isLoading
  • 属性 error 上面换行换了两行
  • 空格不规范(两个空格,一个空格,不空格等等)
  • 第二个 flatMapLatest 没有换到下一行(要换就都换
  • input 中的 Trigger 少了一个 g
  • API & Api

# 改进建议

不需要 NSObject

44 行 .map{Result.Success($0)} 可以改写为 .map(Result.Success)

属性中的 refreshTriggerloadNextPageTrigger 貌似是多余了,refresh 要么在初始化中决定,要么就用这个 refreshTrigger 属性。

Result 指定具体的 error 类型处理起来会很累。比如 error 类型的转换,每次都需要指明 error 类型。

相比 ObjectMapper ,SwiftyJSON 是更好的选择。

网络请求到解析成 Model ,这一过程的代码一般大量存在,可以进行复用。具体可以参考 在实践中应用 RxSwift 2 - 使用函数式复用代码 (opens new window)

从最开始就不要调用 onError ,具体体现在 EmeAPI.sharedInstance.request ,我想这里应该是调用了 onError ,所以才在后面有 map 成 枚举,catch error ,返回可能存在 error 的枚举。

当然了,笔者曾经也是这样写的,事实上网络层的 Block 回调变成 Observable 完全可以我们自己来写,然后只调用 onNext 和 onCompleted 。 注:本代码是体现出流的感觉的,这是我们在写 Rx 时最重要的一点,建立流的概念。本文如有任何错误或建议,随时欢迎提出来一起讨论。