Tips-1. 高雅的注册可复用的表格视图

张狂的热身运动
协议 WsReusable 包括一个只读的特点 identifier,这个特点回来的是一个遵从该协议的类的类名的字符串儿,有点儿绕口,但不难了解

public protocol WsReusable: class {
    static var identifier: String { get }
}
extension WsReusable {
    public static var identifier: String {
        // 这儿的 describing 能够变成 reflecting ,reflecting
        // 愈加完整的表述了类名
        return String(describing: Self.self)
    }
}

只需让咱们自定义的 UICollectionViewCell 遵从该协议,他就免费获得了下面的注册办法:

extension Ws where Base: UICollectionView {
    // 封装了一层 UICollectionViewCell的注册办法,自动把 cell 的 identifier
    // 特点作为 ReuseIdentifier
    public func register<T: UICollectionViewCell>(cell: T.Type) where T: WsReusable {
        base.register(cell, forCellWithReuseIdentifier: T.identifier)
    }
}

然后又增加了一个复用办法,复用的时分也不需求强转cell类型了

extension Ws where Base: UICollectionView {
    .../
    public func dequeueReusableCell<T: UICollectionViewCell>(for indexPath: IndexPath) -> T where T: WsReusable {
        // 这儿把cell类型解包
        guard let cell = base.dequeueReusableCell(withReuseIdentifier: T.identifier, for: indexPath) as? T else {
            fatalError("Could not dequeue reusable cell with identifier: \(T.identifier)")
        }
        return cell
    }
}

当然,获得这些办法的条件还是你的 UICollectionViewCell 遵从 WsReusable 协议哦~

直奔主题

// 第一步,恪守协议
class FamilyManagerCell: UICollectionViewCell, WsReusable { ... }
// 第二步,注册
collectionView.ws.register(cell: FamilyManagerCell.self)
// 第三步,复用
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    // 无需强转
    let cell: FamilyManagerCell = collectionView.ws.dequeueReusableCell(for: indexPath) 
    return cell
}

高潮冲刺
不仅是 UICollectionViewCellUITableViewCell 也能够这么用
乃至继承自 UICollectionReusableView 类型的 UICollectionViewsectionHeadersectionFooter 也能够恪守 WsReusable 协议,只不过注册时分的写法稍稍有所改变,篇幅约束,不再赘述。

贤者模式
粗犷的字符串赋值就像潘多拉魔盒,是bug之源,而swift的协议+泛型真的能够把一切粗犷的东西变的高雅和赏心悦目。


Tips-2 命名空间由 rx_ 到 rx.

你是否有给自己封装的办法或特点加过前缀呢?在 OC 时代引荐的是 前缀_ 的办法,而在 Swift 时代,由于其语言的特性,面向协议编程,咱们触摸到了 .rx .kf 这样比较高雅的办法,所以在万顺的 WSUIKit 组件库里就有了这个东西 WS.swift.

完成原理,代码如下

public struct Ws<Base> {
    public let base: Base
    init(_ base: Base) {
        self.base = base
    }
}
// 凡是恪守了 WsProtocol 协议的类型就都获取了 ws 特点
// ws 的类型是 Ws, Ws的相关类型(泛型)就是他自己
public protocol WsProtocol {}
extension WsProtocol {
    public var ws: Ws<Self> {
        return Ws(self)
    }
    public static var ws: Ws<Self>.Type {
        return Ws.self
    }
}
// 一切 NSObject 的子类都默认遵从了 WsProtocol
extension NSObject: WsProtocol {}

运用

extension Ws where Base: UICollectionView {
    public func register<T: UICollectionViewCell>(cell: T.Type) where T: WsReusable {
        base.register(cell, forCellWithReuseIdentifier: T.identifier)
    }
}
collectionView.ws.register(cell: FamilyManagerCell.self)

高雅,永不过期


Tips-3 将 MJRefresh 用的更 RxSwift 一点

在主工程的 home 目录下有一个 MJRefresh+ws.swift

目的

  1. 监听 MJRefreshComponent 的改写状况,将这个状况封装成一个 ControlEvent,咱们只订阅“正在改写中(refreshing)”的状况。
  2. 将各种改写动作抽象成一个 MJRefreshAction,将header和footer的各种相似beginRefreshing 这样的动作封装起来,便于解耦 viewModelviewController 之间的粘连代码,防止在 viewModel中呈现相似 header.beginRefreshing 的代码,将mvvm的架构变得愈加纯洁。
  3. MJRefresh+ws 还贴心的为 UIScrollView 增加了 addHeader办法,你能够直接调

    tableVie.ws.addHeader()
    

    这样的代码,而不必去初始化一个 MJRefreshNormalHeader 或许 WSRefreshGifHeader。如果某天咱们的产品或许规划突然开窍了,觉得现在的下拉改写菊花作用太丑了,要规划一个带动效的样式,那咱们能够坚决果断的把代码改成这样

    tableView.ws.addHeader(animation: true)
    

    或许其他端两天的工作量,咱们只需求两分钟!这属所以提早预判了产品需求~

MJRefresh+ws 正确运用五步走

1、增加 header/footer

tableView.ws.addHeader()

2、在 vc 中设置事情

if let header = tableView.mj_header {
    header.rx.refresh.subscribe(onNext: { [weak self] in
        guard let self = self else { return }
        self.viewModel.requestFetchBalanceList(checkMore: false)
    }).disposed(by: bag)
}

3、在 viewModel 中创建一个特点,一致处理Refresh的各种状况

let refreshAction = PublishRelay<MJRefreshAction>()

4、refreshAction 的状况变更,在恳求结束的回调里履行

self.refreshAction.accept(.stopRefresh)

5、在 vc 中把 refreshAction 绑定到 scrollView 上

viewModel.refreshAction
.asSignal()
.emit(to: tableView.rx.refreshAction)
.disposed(by: bag)

留意事项⚠️:

showNomoreData 状况设置后,一定要记住在适宜的机遇重置。设置为 resetNomoreData

参照

运用场景能够参照 `OtherPaidListController`
设置-亲情号办理-亲情号代付订单列表页

提示: 如果需求详细代码,关注后私信,看到会发你代码。


Tips-4 张狂打call的 ActivityIndicator(loading显示器)

怎么运用?

// viewModel 中的触发
class AddFamilyMemberViewModel {
    var loadable: Driver<Bool>
    init() {
        // 1.初始化一个 ActivityIndicator
        let activity = ActivityIndicator()
        // 2.loadable 供外界订阅
        loadable = activity.asDriver()
        dataSource = service.getFamilyType()
            // 3.在网络恳求的办法后调用trackActivity
            .trackActivity(activity) 
            .map({ users in
               ...
            })
            .asDriver(onErrorJustReturn: [])
    }
} 
// viewController 中的订阅
class AddFamilyMemberController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 4.vc中订阅loadable,便是整个异步恳求函数的 loading 过程
        viewModel.loadable
        .drive(rx.isHUDLoadingDisplay)
        .disposed(by: bag)
   }
}

完成原理?
ActivityIndicator 使用 using 操作符,捕获了前一个 Observable 的生命周期(留意这个生命周期是一个信号开始被订阅到 onNextonError 闭包被履行。这儿需求深入了解RxSwift 信号流通的全过程)。也就是说 activity的相关类型是一个默认为 falseBool 值,当时一个 Observable开始发射信号的时分,activity的相关值是 true, 当时一个Observable发射的信号被监听到今后,activity的相关值变回了 false。然后有用的监听到了一个网络恳求的全过程,loadable 顺其自然的绑定到了一个HUD上面。

番外废话:
ActivityIndicatorRxSwift 作者Krunoslav Zaher给出来的处理方案,并不是我瞎编的~想学习源码的 github.com/ReactiveX/R…


Tips-5 站在阴暗里的英豪 ErrorIndicator

得益于对 ActivityIndicator的充分了解,我觉得 ViewModel 中的网络恳求过错处理相同能够用这种思路处理。

先看用法

// viewModel
class AddFamilyMemberViewModel: AddFamilyMemberViewModelDataType { 
    var networkError: Driver<NetworkingError>
    init () {
        // 网络恳求中或许呈现的过错捕捉
        let error = ErrorIndicator()
        networkError = error.compactMap { ($0 as! NetworkingError) }
                            .asDriver()
        dataSource = service.getFamilyType()
            .trackError(error)
            .map({ users in
                ...
            })
            .asDriver(onErrorJustReturn: [])
    }
}
// viewController
class AddFamilyMemberController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 捕捉网络恳求的过错,提示用户
        viewModel.networkError.drive(onNext: { error in
            HUD.showMessage("\(error.description)")
        }).disposed(by: bag)
    }
}

这和ActivityIndicator的用法基本没两样,乃至 ErrorIndicator的完成代码愈加简略

public class ErrorIndicator: SharedSequenceConvertibleType {
    public typealias Element = Swift.Error?
    public typealias SharingStrategy = DriverSharingStrategy
    private let _lock = NSRecursiveLock()
    private let _relay = PublishRelay<Element>()
    private let _error: SharedSequence<SharingStrategy, Element>
    public init() {
        _error = _relay.asDriver(onErrorJustReturn: nil)
    }
    fileprivate func trackErrorOfObservable<Source: ObservableConvertibleType>(
    _ source: Source, 
    justReture element: Source.Element?) -> Observable<Source.Element> {
        // 这儿只需求运用 catchError 这个操作符把 error 捕捉到,回传给
        // ErrorIndicator 即可
        return source.asObservable().catchError { error in
            self._lock.lock()
            self._relay.accept(error)
            self._lock.unlock()
            if let e = element {
                return Observable<Source.Element>.just(e)
            } else {
                return Observable<Source.Element>.empty()
            }
        }
    }
    public func asSharedSequence() -> SharedSequence<DriverSharingStrategy, Element> {
        return _error
    }
}
extension ObservableConvertibleType {
    public func trackError(_ errorIndicator: ErrorIndicator, 
    justReturn element: Element? = nil)
        -> Observable<Element> {
            return errorIndicator.trackErrorOfObservable(self, justReture: element)
    }
}

这叫抄吗?这叫学以致用~

后续

之后我还会更新我在实践项目里是怎么规划 ViewModel 的心得,以及一些参考代码,关注我,一同学习~

Swift 实践小技巧继续更新中~