close
当前位置: 物联网在线 > 技术文库 > ios >

RxExample GitHubSignup 部分代码解读

GitHubSignup 是一个注册例子的 Demo ,同时也是一个 MVVM 的 Demo 。但本节将重点介绍代码上 为什么这样写 ,你可以从中了解到何时在代码中用 Rx 处理异步,如何合理的书写代码,以及如何优雅地处理网络请求状态。

事实上这个例子处理网络请求的方式是使用 using 操作符 hook 网络请求 Observable 的生命周期。

代码均在 RxExample 项目中,相关涉及文件如下:

GitHubSignup 文件夹所有内容

ActivityIndicator.swift

我们先来简单思考一下注册需要注意哪几个点,这里主要是表单验证问题:

用户名不能重复,需要提交用户名到服务器验证

注册密码有等级限制,比如长度、带大小写字母

两次输入的密码相同

从 Protocols.swift 文件入手,这个文件有两个枚举 ValidationResult 和 SignupState ,两个协议 GitHubAPI 和 GitHubValidationService 。

ValidationResult 包含了四个验证结果:

enumValidationResult{ case ok(message: String) case empty case validating case failed(message: String) }

分别是验证成功、验证为空、正在验证、验证失败。

在验证成功和验证失败两种情况中,会带上一个消息供展示。

SignupState 用于标记注册状态,表示是否已经注册,代码如下:

enumSignupState{ case signedUp(signedUp: Bool) }

协议 GitHubAPI 和 GitHubValidationService 代码如下:

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 }

在讨论这段代码的设计前,我们先思考一下哪些是异步场景:

检查用户名是否可用

注册

而验证密码和验证重复输入密码都可以同步地形式进行。

在设计到 检查用户名注册 时,应当返回一个 Observable 代替 callback ,而密码的验证只需要在一个方法中返回验证结果即可。

所以上述两个协议中 usernameAvailable 、 signup 和 validateUsername 都是异步事件,都应当返回 Observable 。

DefaultImplementations.swift 文件给出了上述两个协议的实现,先来看 GitHubDefaultAPI :

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) } }

方法 usernameAvailable 验证了用户名是否可用,这里验证的方案是请求该用户名对应的主页,返回 404 说明没有该用户。

signup 是一个带延时的 mock 方法,对每一次的注册返回一个随机结果,并对该结果延迟 0.4s 。

你可能会问代码 .concat(Observable.never()) 存在的意义,回顾操作符 throttle ,当 接到 completed 时,立即传递 completed 。而 just 发射第一个值后立即发射 completed ,从而没有延时效果。当 concat 一个 never 时, Observable 永远不会发射 completed ,从而得到延时效果。

来看 GitHubDefaultValidationService , GitHubDefaultValidationService 提供了 用户名验证密码验证重复密码验证 三个功能。

我们只需关注方法 validateUsername :

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) }
(责任编辑:ioter)

用户喜欢...