(WWDC) 高级iOS应用程序架构和模式


内容概览

  • 前言
  • 设计信息流
  • 定义明确的责任
  • 用不变性进行简化





前言


大多数大型应用的架构都会经历从简单到复杂的过程。

随着业务逻辑不断增多,应用需要处理的任务也会越来越繁杂,各种bug也会显现出来。
如果应用没有采用合适的软件架构和模式,问题将变得更加棘手。

你对此是否深有体会?


1306450-5b23158d27528025.png
简单架构
1306450-88dc7826837b842e.png
规模逐步扩大的简单架构
1306450-b876622beb30df07.png
不合理的简单架构
1306450-abd08881d9ae2f4d.png
不合理的复杂架构
1306450-01ddcf5430a3870c.png
不合理的复杂架构导致的问题开始突显
1306450-d50f30a1d13b07e9.png
在不合理的架构上进行单元测试
1306450-84bb63c9fdfdec88.png
单元测试成为了新的麻烦




针对这些问题,我们需要对架构进行不断地探索,因此我们对架构的理解也会更加深入。
渐渐地,我们对架构的要求会越来越高。然而,往往我们的能力没有和我们的品位一起提升。

1306450-d7725044605fc42d.png

1306450-06590a44b4d800ec.png


架构不是教条主义,它需要我们的洞察力!





设计信息流


1306450-df6508b2315d62ed.png
混乱的信息流
1306450-ca006ea48bfc1701.png
清晰直观的信息流





真实数据 和 派生数据


1306450-3c965a9afde7804d.png
1306450-42550829ce9ae0d1.png



派生数据的特点:

  • 根据输入数据计算得出
  • 当输入数据变化时,需要重新计算
  • 很像缓存





示例:

这是一个很常见的需求。
在界面放置一个UITextView,旁边有一个UILabel作为UITextView的文字计数器。
当UITextView的文本发生变化时,我们会在代理方法中更新UILabel的character count。

1306450-f85b7d0ef6babcd3.png



如果通过代码来直接更新UITextView的文本,是不会有代理方法被调用的。
这时候可能会出现什么问题?



如果,此时只是通过代码来更新UITextView的文本,character count将得不到更新:

1306450-c99fcd66d1c38805.png

1306450-61355b0fb6bddea7.png



所以,我们需要通过model来直接更新TextView的文本和character count,因为实际的数据在model处。

1306450-40ac6611ee23c7e2.png



如果用户输入了新的值,model也需要被更新。

1306450-0f71b876629c519a.png



但是,如果只是这样做的话,我们就丢失了原始数据。
假如,当前页面是主界面列表中某一条记录的详情页,用户通过点击主界面列表跳转到当前页面。如果我们直接对原始的模型进行修改,这是不合理的。



所以,合理的设计应该是这样的:

1306450-fc0ffb61b463c093.png



总结一下设计信息流的过程:

  • 找到真实数据在哪里
  • 找到真实数据和派生数据的联系
  • 更新真实数据





定义明确的责任


1306450-f37622e35547f49c.png
职责不明确
1306450-b1edc700266bc0ce.png
职责明确



对输入数据进行校验是软件开发中常见的情况,尤其是对注册、登录模块的输入数据进行校验。

1306450-6c6d23c84e8381c8.png



常见的验证流程:

  • 对每个输入框进行验证
  • 当所有输入框的验证都通过时,将按钮设为可用状态。
1306450-24a7c5d75367db03.png



全部输入框的验证流程:

  • 检查用户名输入框
  • 检查密码输入框
    • 检查第二个密码输入框的值是否和第一个相同
  • 检查邮箱输入框


    1306450-23be00600b17872e.png



某个输入框的验证流程:

  • 创建正则表达式
  • 查找匹配的用户名
  • 如果有匹配项,用户名有效
  • 否则,在输入框提示错误
1306450-88c8f1c9c279740a.png



在这个过程中,我们还需要判定nil:

1306450-cef56516bc5779c5.png

1306450-93664767dec04f3a.png



然后,我们可能会写出这样的代码:

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]]];
}

请注意,验证过程直接对输入框的背景色进行了更改,这是不合理的。

1306450-348bb6fef2a99699.png

而且,验证过程被定义在了ViewController中,这也是不合理的。



如果有多个页面需要进行相同的校验,一定会出现这样的情况:

1306450-51a0608f5cc4e7cd.png



所以,我们应该将验证过程独立出来:

1306450-ba92c449bc66ba9a.png



验证过程的特征:

  • 接收输入数据
  • 检查输入数据是否有效
  • 如果无效,给出原因



根据以上特征,定义一个验证器协议:

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:

1306450-c7440bcdff59af89.png

当你需要把A中的值赋给B时,如果这个值为引用类型,那么A仍然指向了这个值。

1306450-3aec54478614bf39.png

如果,B对这个值进行修改,A也会受到影响。

1306450-1f06e0c5964a1233.png

如果再将这个引用值赋给C,情况将会变得更加复杂。

1306450-7631466d6c4664b1.png




如果使用值类型,而不是引用类型呢?

1306450-c7a36c9a41c86236.png

把A中的值赋给B。


1306450-bbd3fe67a1055f52.png

B如果改变这个值也不会影响A。


1306450-c34611c3067dce37.png




Swift中的 struct 是很好用的值类型,它有以下特征:

  • 可选择的可变性(mutating 关键字)
  • 传递的是值,而不是引用 (Copy-On-Write 写时复制)





参考内容:
Advanced iOS Application Architecture and Patterns




转载请注明出处,谢谢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值