Swift 2.0 下面向协议的MVVM架构实践

移动开发 iOS
自从令人兴奋的[《面向协议的编程方法》]在Swift的WWDC大会上发布以来。我对协议的使用考虑了很多。但是在现实中,我并没有太多的顾及和使用这些功能。我还仍旧在消化到底面向协议的编程方法是什么,在代码的哪些地方应该使用,而不是使用我目前使用的`go-to`编程方法。

自从令人兴奋的[《面向协议的编程方法》]在Swift的WWDC大会上发布以来。我对协议的使用考虑了很多。但是在现实中,我并没有太多的顾及和使用这些功能。我还仍旧在消化到底面向协议的编程方法是什么,在代码的哪些地方应该使用,而不是使用我目前使用的`go-to`编程方法。

 

...所以,当我想起来要在哪里应用这些概念性的东西时,我非常激动,那就是MVVM !我已经在之前的博客中使用过MVVM架构,如果你想了解更多MVVM相关知识请参考[这里]。接下来我将讲解,如何添加面向协议。

 

我将会使用一个简单的例子。一个只有一个设置选项的设置页面,把应用设置为Minion模式,当然你也可以扩展为多个设置选项。

[[149011]]

View Cell

一个极其普通的Cell,它包含一个Label和一个开关控件。你也可以在其他地方使用这个Cell,例如注册页面添加一个“记住我”的开关选项。所以,你应该保持这个页面通用性。

一个复杂的配置

通常,我在cell中使用一个设置方法,来监听所有对应用设置可能的变更,这看起来是这样的:

  1. class SwitchWithTextTableViewCell: UITableViewCell { 
  2.  
  3. @IBOutlet private weak var label: UILabel! 
  4. @IBOutlet private weak var switchToggle: UISwitch! 
  5.  
  6. typealias onSwitchToggleHandlerType = (switchOn: Bool) -> Void 
  7. private var onSwitchToggleHandler: onSwitchToggleHandlerType? 
  8.  
  9. override func awakeFromNib() { 
  10. super.awakeFromNib() 
  11.  
  12. func configure(withTitle title: String, 
  13. switchOn: Bool, 
  14. onSwitchToggleHandler: onSwitchToggleHandlerType? = nil) 
  15. label.text = title 
  16. switchToggle.on = switchOn 
  17.  
  18. self.onSwitchToggleHandler = onSwitchToggleHandler 
  19.  
  20. @IBAction func onSwitchToggle(sender: UISwitch) { 
  21. onSwitchToggleHandler?(switchOn: sender.on) 

通过 Swift 的默认参数,可以添加其他的设置选项到这个设置方法,而不必改变代码中的其他地方,使用起来非常方便。例如,当设计师说开关按钮的颜色需应该各不相同,这时候我就可以添加一个默认参数。

  1. func configure(withTitle title: String, 
  2. switchOn: Bool, 
  3. switchColor: UIColor = .purpleColor(), 
  4. onSwitchToggleHandler: onSwitchToggleHandlerType? = nil) 
  5. label.text = title 
  6. switchToggle.on = switchOn 
  7. // color option added! 
  8. switchToggle.onTintColor = switchColor 
  9.  
  10. self.onSwitchToggleHandler = onSwitchToggleHandler 

虽然在这种情况下看起来并不是什么大问题,但是随着时间的增加,事实上这个方法将会变得非常冗长、复杂!是时候由面向协议的编程方法登场了。

面向协议的编程方法

  1. protocol SwitchWithTextCellProtocol { 
  2. var title: String { get } 
  3. var switchOn: Bool { get } 
  4.  
  5. func onSwitchTogleOn(on: Bool) 
  6.  
  7. class SwitchWithTextTableViewCell: UITableViewCell { 
  8.  
  9. @IBOutlet private weak var label: UILabel! 
  10. @IBOutlet private weak var switchToggle: UISwitch! 
  11.  
  12. private var delegate: SwitchWithTextCellProtocol? 
  13.  
  14. override func awakeFromNib() { 
  15. super.awakeFromNib() 
  16.  
  17. func configure(withDelegate delegate: SwitchWithTextCellProtocol) { 
  18. self.delegate = delegate 
  19.  
  20. label.text = delegate.title 
  21. switchToggle.on = delegate.switchOn 
  22.  
  23. @IBAction func onSwitchToggle(sender: UISwitch) { 
  24. delegate?.onSwitchTogleOn(sender.on) 

当设计师说需要改变开关控件颜色的时候会发生什么?以下代码可以展现协议扩展的奇妙之处。

 

 

  1. extension SwitchWithTextCellProtocol { 
  2.  
  3. // set the default color here! 
  4. func switchColor() -> UIColor { 
  5. return .purpleColor() 
  6.  
  7. class SwitchWithTextTableViewCell: UITableViewCell { 
  8.  
  9. // truncated, see above 
  10.  
  11. func configure(withDelegate delegate: SwitchWithTextCellProtocol) { 
  12. self.delegate = delegate 
  13.  
  14. label.text = delegate.title 
  15. switchToggle.on = delegate.switchOn 
  16. // color option added! 
  17. switchToggle.onTintColor = delegate.switchColor() 

在以上代码中协议的扩展实现了默认的switchColor选项,所以,任何已经实现了这个协议或者并不关心设置开关颜色的人,不用关注这个扩展。只有一个具有不同颜色的新的开关控件可以实现。

ViewModel

所以现在剩下的事情将会非常简单。我将会为MinionMode的设置cell写一个ViewModel。

  1. import UIKit 
  2.  
  3. struct MinionModeViewModel: SwitchWithTextCellProtocol { 
  4. var title = "Minion Mode!!!" 
  5. var switchOn = true 
  6.  
  7. func onSwitchTogleOn(on: Bool) { 
  8. if on { 
  9. print("The Minions are here to stay!"
  10. else { 
  11. print("The Minions went out to play!"
  12.  
  13. func switchColor() -> UIColor { 
  14. return .yellowColor() 
  15.  
  16. ViewController 

 

***一步就是在ViewController中设置cell的时候将ViewModel传给cell。

  1. import UIKit 
  2.  
  3. class SettingsViewController: UITableViewController { 
  4.  
  5. enum Setting: Int { 
  6. case MinionMode 
  7. // other settings here 
  8.  
  9. override func viewDidLoad() { 
  10. super.viewDidLoad() 
  11.  
  12. // MARK: - Table view data source 
  13.  
  14. override func tableView(tableView: UITableView, 
  15. numberOfRowsInSection section: Int) -> Int 
  16. return 1 
  17.  
  18. override func tableView(tableView: UITableView, 
  19. cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
  20. if let setting = Setting(rawValue: indexPath.row) { 
  21. switch setting { 
  22. case .MinionMode: 
  23. let cell = tableView.dequeueReusableCellWithIdentifier("SwitchWithTextTableViewCell", forIndexPath: indexPath) as! SwitchWithTextTableViewCell 
  24.  
  25. // this is where the magic happens! 
  26. cell.configure(withDelegate: MinionModeViewModel()) 
  27. return cell 
  28.  
  29. return tableView.dequeueReusableCellWithIdentifier("defaultCell", forIndexPath: indexPath) 
  30.  

通过使用协议的扩展,是面向协议的编程方法有了很大的意义,并且我在寻找更多的使用场景。以上代码的全部内容放在[github]上。

更新:将数据源和代理分开

在评论中,Marc Baldwin 建议分开cell的数据源和代理方法到两个协议中,就像UITableView中的那样。我很赞成这个意见,以下是我修改后的代码。

View Cell

Cell将拥有两个协议,并且任何一个协议都可以设置这个cell。

  1. import UIKit 
  2.  
  3. protocol SwitchWithTextCellDataSource { 
  4. var title: String { get } 
  5. var switchOn: Bool { get } 
  6.  
  7. protocol SwitchWithTextCellDelegate { 
  8. func onSwitchTogleOn(on: Bool) 
  9.  
  10. var switchColor: UIColor { get } 
  11. var textColor: UIColor { get } 
  12. var font: UIFont { get } 
  13.  
  14. extension SwitchWithTextCellDelegate { 
  15.  
  16. var switchColor: UIColor { 
  17. return .purpleColor() 
  18.  
  19. var textColor: UIColor { 
  20. return .blackColor() 
  21.  
  22. var font: UIFont { 
  23. return .systemFontOfSize(17
  24.  
  25. class SwitchWithTextTableViewCell: UITableViewCell { 
  26.  
  27. @IBOutlet private weak var label: UILabel! 
  28. @IBOutlet private weak var switchToggle: UISwitch! 
  29.  
  30. private var dataSource: SwitchWithTextCellDataSource? 
  31. private var delegate: SwitchWithTextCellDelegate? 
  32.  
  33. override func awakeFromNib() { 
  34. super.awakeFromNib() 
  35.  
  36. func configure(withDataSource dataSource: SwitchWithTextCellDataSource, delegate: SwitchWithTextCellDelegate?) { 
  37. self.dataSource = dataSource 
  38. self.delegate = delegate 
  39.  
  40. label.text = dataSource.title 
  41. switchToggle.on = dataSource.switchOn 
  42. // color option added! 
  43. switchToggle.onTintColor = delegate?.switchColor 
  44.  
  45. @IBAction func onSwitchToggle(sender: UISwitch) { 
  46. delegate?.onSwitchTogleOn(sender.on) 

ViewModel

你现在可以在扩展里把数据源和delegate逻辑分开了:

  1. import UIKit 
  2.  
  3. struct MinionModeViewModel: SwitchWithTextCellDataSource { 
  4. var title = "Minion Mode!!!" 
  5. var switchOn = true 
  6.  
  7. extension MinionModeViewModel: SwitchWithTextCellDelegate { 
  8.  
  9. func onSwitchTogleOn(on: Bool) { 
  10. if on { 
  11. print("The Minions are here to stay!"
  12. else { 
  13. print("The Minions went out to play!"
  14.  
  15. var switchColor: UIColor { 
  16. return .yellowColor() 

ViewController

这一部分是我不十分确定,ViewController不能传递ViewModel两次:

 

 

 

  1. override func tableView(tableView: UITableView, 
  2. cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
  3. if let setting = Setting(rawValue: indexPath.row) { 
  4. switch setting { 
  5. case .MinionMode: 
  6. let cell = tableView.dequeueReusableCellWithIdentifier("SwitchWithTextTableViewCell", forIndexPath: indexPath) as! SwitchWithTextTableViewCell 
  7.  
  8. // this is where the magic happens! 
  9. let viewModel = MinionModeViewModel() 
  10. cell.configure(withDataSource: viewModel, delegate: viewModel) 
  11. return cell 
  12.  
  13. return tableView.dequeueReusableCellWithIdentifier("defaultCell", forIndexPath: indexPath) 

代码已经上传[GitHub]

责任编辑:chenqingxiang 来源: CocoaChina
相关推荐

2015-08-04 08:56:14

swift子类

2018-05-10 13:45:15

Swift网络层协议

2018-07-23 15:55:28

协议自定义viewSwift

2022-04-29 13:43:00

谷歌SWIFT参考架构

2015-08-14 11:37:37

Swift语言中文版

2022-07-30 23:41:53

面向过程面向对象面向协议编程

2010-07-09 11:12:09

UDP协议

2015-01-29 09:52:43

Swift 开源苹果

2015-06-23 15:48:41

Swift 2.0iOS9

2019-02-21 08:30:00

边缘计算物联网5G

2017-07-17 15:19:10

MVVM模式iOS开发MVP

2009-07-24 13:54:39

MVVM模式

2021-08-02 06:49:46

OIDC认证协议

2010-07-28 09:18:03

Flex2.0

2016-12-12 15:22:41

编程

2015-10-10 09:35:38

swift规范

2020-07-29 11:02:35

物联网安全技术

2015-07-08 10:47:57

Using Swift CocoaObjective-C

2017-02-21 13:24:41

iOSMVVM架构

2021-06-03 08:55:58

面向协议编程
点赞
收藏

51CTO技术栈公众号