RxExample GitHubSignup 部分代码解读
FauS76
8年前
<p>GitHubSignup 是一个注册例子的 Demo ,同时也是一个 MVVM 的 Demo 。但本节将重点介绍代码上 <strong>为什么这样写</strong> ,你可以从中了解到何时在代码中用 Rx 处理异步,如何合理的书写代码,以及如何优雅地处理网络请求状态。</p> <p>事实上这个例子处理网络请求的方式是使用 using 操作符 hook 网络请求 Observable 的生命周期。</p> <p>代码均在 RxExample 项目中,相关涉及文件如下:</p> <ul> <li>GitHubSignup 文件夹所有内容</li> <li>ActivityIndicator.swift</li> </ul> <p>我们先来简单思考一下注册需要注意哪几个点,这里主要是表单验证问题:</p> <ul> <li>用户名不能重复,需要提交用户名到服务器验证</li> <li>注册密码有等级限制,比如长度、带大小写字母</li> <li>两次输入的密码相同</li> </ul> <p>从 Protocols.swift 文件入手,这个文件有两个枚举 ValidationResult 和 SignupState ,两个协议 GitHubAPI 和 GitHubValidationService 。</p> <p>ValidationResult 包含了四个验证结果:</p> <pre> enumValidationResult{ case ok(message: String) case empty case validating case failed(message: String) } </pre> <p>分别是验证成功、验证为空、正在验证、验证失败。</p> <p>在验证成功和验证失败两种情况中,会带上一个消息供展示。</p> <p>SignupState 用于标记注册状态,表示是否已经注册,代码如下:</p> <pre> enumSignupState{ case signedUp(signedUp: Bool) } </pre> <p>协议 GitHubAPI 和 GitHubValidationService 代码如下:</p> <pre> protocolGitHubAPI{ funcusernameAvailable(_username: String) -> Observable<Bool> funcsignup(_username: String, password: String) -> Observable<Bool> } protocolGitHubValidationService{ funcvalidateUsername(_username: String) -> Observable<ValidationResult> funcvalidatePassword(_password: String) -> ValidationResult funcvalidateRepeatedPassword(_password: String, repeatedPassword: String) -> ValidationResult } </pre> <p>在讨论这段代码的设计前,我们先思考一下哪些是异步场景:</p> <ul> <li>检查用户名是否可用</li> <li>注册</li> </ul> <p>而验证密码和验证重复输入密码都可以同步地形式进行。</p> <p>在设计到 <strong>检查用户名</strong> 和 <strong>注册</strong> 时,应当返回一个 Observable 代替 callback ,而密码的验证只需要在一个方法中返回验证结果即可。</p> <p>所以上述两个协议中 usernameAvailable 、 signup 和 validateUsername 都是异步事件,都应当返回 Observable 。</p> <p>DefaultImplementations.swift 文件给出了上述两个协议的实现,先来看 GitHubDefaultAPI :</p> <pre> classGitHubDefaultAPI:GitHubAPI{ let URLSession: Foundation.URLSession static let sharedAPI = GitHubDefaultAPI( URLSession: Foundation.URLSession.shared ) init(URLSession: Foundation.URLSession) { self.URLSession = URLSession } funcusernameAvailable(_username: String) -> Observable<Bool> { // this is ofc just mock, but good enough let url = URL(string: "https://github.com/\(username.URLEscaped)")! let request = URLRequest(url: url) return self.URLSession.rx.response(request: request) .map { (response, _) in return response.statusCode == 404 } .catchErrorJustReturn(false) } funcsignup(_username: String, password: String) -> Observable<Bool> { // this is also just a mock let signupResult = arc4random() % 5 == 0 ? false : true return Observable.just(signupResult) .concat(Observable.never()) .throttle(0.4, scheduler: MainScheduler.instance) .take(1) } } </pre> <p>方法 usernameAvailable 验证了用户名是否可用,这里验证的方案是请求该用户名对应的主页,返回 404 说明没有该用户。</p> <p>signup 是一个带延时的 mock 方法,对每一次的注册返回一个随机结果,并对该结果延迟 0.4s 。</p> <p>你可能会问代码 .concat(Observable.never()) 存在的意义,回顾操作符 throttle ,当 <strong>接到 completed 时,立即传递 completed</strong> 。而 just 发射第一个值后立即发射 completed ,从而没有延时效果。当 concat 一个 never 时, Observable 永远不会发射 completed ,从而得到延时效果。</p> <p>来看 GitHubDefaultValidationService , GitHubDefaultValidationService 提供了 <strong>用户名验证</strong> 、 <strong>密码验证</strong> 、 <strong>重复密码验证</strong> 三个功能。</p> <p>我们只需关注方法 validateUsername :</p> <pre> funcvalidateUsername(_username: String) -> Observable<ValidationResult> { if username.characters.count == 0 { return .just(.empty) } // this obviously won't be if username.rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) != nil { return .just(.failed(message: "Username can only contain numbers or digits")) } let loadingValue = ValidationResult.validating return API .usernameAvailable(username) .map { available in if available { return .ok(message: "Username available") } else { return .failed(message: "Username already taken") } } .startWith(loadingValue) } </pre> <p>首先验证输入的用户名是否为空,为空则直接返回 .just(.empty) ,再验证输入的用户名是否均为数字或父母,不是则直接返回 .just(.failed(message: "Username can only contain numbers or digits")) 。</p> <p>当通过以上两种验证时,我们需要请求服务器验证用户名是否重复。 .startWith(loadingValue) 为我们请求数据时添加了 loading 状态。</p> <h2>UsingVanillaObservables > 1</h2> <p>本节示例在代码上使用 Observable 和 Driver 区别不大,以使用 Observable 代码为例。</p> <p>GithubSignupViewModel1 是对应的ViewModel。</p> <h2>ActivityIndicator</h2> <h3>Using 操作符</h3> <p>使用 using 操作符可以创建一个和 Observable 相同生命周期的实例对象·。</p> <p>当 subscribe 时,创建该实例,当 dispose 时,调用该实例的dispose。</p> <pre> extensionObservablewhereElement{ public static funcusing<R: Disposable>(_resourceFactory: @escaping() throws -> R, observableFactory: @escaping (R) throws -> Observable<E>) -> Observable<E> } </pre> <p>在 resourceFactory 中传入一个工厂方法,返回一个可以 dispose 的实例。</p> <p>在 observableFactory 中同样传入一个工厂方法,这里的 R 是 resourceFactory 中返回的实例,返回一个 Observable ,这正是与 resource 对应生命周期的 Observable 。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/71962069e91927f0b1b36ba5f40e507f.png"></p> <p>来看 ActivityIndicator 是如何使用 using 管理请求状态的。</p> <pre> extensionObservableConvertibleType{ public functrackActivity(_activityIndicator: ActivityIndicator) -> Observable<E> { return activityIndicator.trackActivityOfObservable(self) } } </pre> <p>为 Observable 创建的扩展方法 trackActivity 中传入一个 ActivityIndicator 就可以跟踪加载状态了。</p> <p>ActivityIndicator 服从协议 SharedSequenceConvertibleType ,直接调用 asObservable() 即可获取 loading 状态。</p> <p>移除保证线程安全部分代码, ActivityIndicator 代码如下:</p> <pre> public classActivityIndicator:SharedSequenceConvertibleType{ public typealias E = Bool public typealias SharingStrategy = DriverSharingStrategy private let _variable = Variable(0) private let _loading: SharedSequence<SharingStrategy, Bool> public init() { _loading = _variable.asDriver() .map { $0 > 0 } .distinctUntilChanged() } fileprivate functrackActivityOfObservable<O: ObservableConvertibleType>(_source: O) -> Observable<O.E> { return Observable.using({ () -> ActivityToken<O.E> in self.increment() return ActivityToken(source: source.asObservable(), disposeAction: self.decrement) }) { t in return t.asObservable() } } private funcincrement() { _variable.value = _variable.value + 1 } private funcdecrement() { _variable.value = _variable.value - 1 } public funcasSharedSequence() -> SharedSequence<SharingStrategy, E> { return _loading } } </pre> <p>我们通过 _variable 表示正在执行的 Observable ,当 _variable 中的值为 0 时, _loading 发射一个 false ,表示加载结束,当 _variable 中的值大于 0 时, _loading 会发射 true 。</p> <p>方法 increment 和 decrement 处理的在执行的 Observable 的数量。</p> <p>而在 trackActivityOfObservable 中使用了 using 将 increment 和 decrement 与 Observable 的生命周期绑定起来。</p> <p>调用 using 的 resourceFactory 时,调用 increment 将资源数加1。</p> <p>当 dispose 时,调用 ActivityToken 的 dispose 方法。</p> <p>ActivityToken 代码如下:</p> <pre> private structActivityToken<E> :ObservableConvertibleType,Disposable{ private let _source: Observable<E> private let _dispose: Cancelable init(source: Observable<E>, disposeAction: @escaping () -> ()) { _source = source _dispose = Disposables.create(with: disposeAction) } funcdispose() { _dispose.dispose() } funcasObservable() -> Observable<E> { return _source } } </pre> <p>这就完成了对 Observable 的监听。</p> <p> </p> <p>来自:http://blog.dianqk.org/2017/03/03/rxexample-githubsignup/</p> <p> </p>