(WWDC) 高级iOS应用程序架构和模式
内容概览
- 前言
- 设计信息流
- 定义明确的责任
- 用不变性进行简化
前言
大多数大型应用的架构都会经历从简单到复杂的过程。
随着业务逻辑不断增多,应用需要处理的任务也会越来越繁杂,各种bug也会显现出来。
如果应用没有采用合适的软件架构和模式,问题将变得更加棘手。
你对此是否深有体会?
简单架构 规模逐步扩大的简单架构 不合理的简单架构 不合理的复杂架构 不合理的复杂架构导致的问题开始突显 在不合理的架构上进行单元测试 单元测试成为了新的麻烦
针对这些问题,我们需要对架构进行不断地探索,因此我们对架构的理解也会更加深入。
渐渐地,我们对架构的要求会越来越高。然而,往往我们的能力没有和我们的品位一起提升。
架构不是教条主义,它需要我们的洞察力!
设计信息流
混乱的信息流 清晰直观的信息流
真实数据 和 派生数据
派生数据的特点:
- 根据输入数据计算得出
- 当输入数据变化时,需要重新计算
- 很像缓存
示例:
这是一个很常见的需求。
在界面放置一个UITextView,旁边有一个UILabel作为UITextView的文字计数器。
当UITextView的文本发生变化时,我们会在代理方法中更新UILabel的character count。
如果通过代码来直接更新UITextView的文本,是不会有代理方法被调用的。
这时候可能会出现什么问题?
如果,此时只是通过代码来更新UITextView的文本,character count将得不到更新:
所以,我们需要通过model来直接更新TextView的文本和character count,因为实际的数据在model处。
如果用户输入了新的值,model也需要被更新。
但是,如果只是这样做的话,我们就丢失了原始数据。
假如,当前页面是主界面列表中某一条记录的详情页,用户通过点击主界面列表跳转到当前页面。如果我们直接对原始的模型进行修改,这是不合理的。
所以,合理的设计应该是这样的:
总结一下设计信息流的过程:
- 找到真实数据在哪里
- 找到真实数据和派生数据的联系
- 更新真实数据
定义明确的责任
职责不明确 职责明确
对输入数据进行校验是软件开发中常见的情况,尤其是对注册、登录模块的输入数据进行校验。
常见的验证流程:
- 对每个输入框进行验证
- 当所有输入框的验证都通过时,将按钮设为可用状态。
全部输入框的验证流程:
- 检查用户名输入框
- 检查密码输入框
- 检查第二个密码输入框的值是否和第一个相同
-
检查邮箱输入框
某个输入框的验证流程:
- 创建正则表达式
- 查找匹配的用户名
- 如果有匹配项,用户名有效
- 否则,在输入框提示错误
在这个过程中,我们还需要判定nil:
然后,我们可能会写出这样的代码:
NSString *username = [self.usernameField text];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@“[a-zA-Z0-9_]{6,}” options:0 error:nil];
NSRange result = [regex rangeOfFirstMatchInString:username options:NSMatchingAnchored range:NSMakeRange(0, [username length])];
if (username && result.location == NSNotFound) {
allValid = NO;
[self.usernameField setBackgroundColor:[UIColor redColor]];
} else {
if (!username) {
anyNil = YES;
}
[self.usernameField setBackgroundColor:[UIColor whiteColor]]];
}
请注意,验证过程直接对输入框的背景色进行了更改,这是不合理的。
而且,验证过程被定义在了ViewController中,这也是不合理的。
如果有多个页面需要进行相同的校验,一定会出现这样的情况:
所以,我们应该将验证过程独立出来:
验证过程的特征:
- 接收输入数据
- 检查输入数据是否有效
- 如果无效,给出原因
根据以上特征,定义一个验证器协议:
protocol Validator {
validateWithError(error: NSErrorPointer) -> Bool
}
这样定义有以下优势:
- 由协议的遵守者来定义输入
- 可以由小的验证来构建大的验证
- 可以组合
- 同样适用于ObjC
然后,定义用户名验证器:
class UsernameValidator: Validator {
var input: NSString?
func validateWithError(error: NSErrorPointer) -> Bool {
let regex = NSRegularExpression(pattern: ...)
...
}
}
然后,定义密码验证器:
class PasswordValidator: Validator {
var input: NSString?
...
}
class SetPasswordValidator: Validator {
let firstPasswordValidator = PasswordValidator()
let secondPasswordValidator = PasswordValidator()
...
}
然后,只需要组合前面的验证器就可以轻松定义注册验证器:
class SignUpValidator: Validator {
let usernameValidator = UsernameValidator()
let setPasswordValidator = SetPasswordValidator()
let emailAddressValidator = EmailAddressValidator()
...
}
用不变性进行简化
假设有三个实例 A, B, C:
当你需要把A中的值赋给B时,如果这个值为引用类型,那么A仍然指向了这个值。
如果,B对这个值进行修改,A也会受到影响。
如果再将这个引用值赋给C,情况将会变得更加复杂。
如果使用值类型,而不是引用类型呢?
把A中的值赋给B。
B如果改变这个值也不会影响A。
Swift中的 struct
是很好用的值类型,它有以下特征:
- 可选择的可变性(mutating 关键字)
- 传递的是值,而不是引用 (Copy-On-Write 写时复制)
参考内容:
Advanced iOS Application Architecture and Patterns
转载请注明出处,谢谢~