文档章节

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

法斗斗
 法斗斗
发布于 2016/03/01 18:05
字数 1587
阅读 20
收藏 0
点赞 1
评论 0

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


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

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

03.png

View Cell

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

一个复杂的配置

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

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

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

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

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

面向协议的编程方法

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

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

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

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

ViewModel

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

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

ViewController

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

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

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

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

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

View Cell

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

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

ViewModel

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

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

ViewController

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

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

代码已经上传[GitHub]

本文转载自:

共有 人打赏支持
法斗斗
粉丝 20
博文 336
码字总数 6335
作品 0
杨浦
程序员
Swift2编程之道:POP+MVVM

Swift2.0中引入了协议扩展的特性,并且建议开发者一切从协议(Protocol)出发,经过几个月的学习探索,博主发现Swift作为一门面向协议编程(POP)的语言非常适合时下火热的MVVM架构。MVVM已经...

cg1991130 ⋅ 2016/03/01 ⋅ 0

fir.im Weekly - 揭秘 iOS 面向协议编程

本期 fir.im Weekly 重点推荐关于 iOS 面向协议编程相关文章,还有 iOS 多线程安全、Swift 进阶、Android MVVM 应用框架、Android 蓝牙实践等技术文章分享和工具源码分享~ 『iOS / Android...

风起云飞fir_im ⋅ 2016/12/05 ⋅ 0

基于MVVM 的IOS开发框架--EasyIOS

Swift版本最新发布: https://github.com/EasyIOS/EasyIOS-Swift 全新基于MVVM(Model-View-ViewModel)编程模式架构,开启EasyIOS开发函数式编程新篇章。 EasyIOS 2.0类似AngularJs,最为核心...

墨水 ⋅ 2014/07/18 ⋅ 5

Swift黑科技:还在争论MVC和MVVM?博主独创幽灵架构MV!

本人原创,长文慎入,但此文可能会改写你的编程风格。我认为数据和模型交互的关键问题是如何处理数据源和视图源本身的异构性。通过面向协议编程的不断实践,总结他人的理论经验,我发现了使用...

cg1991130 ⋅ 2016/04/11 ⋅ 0

fir.im Weekly - 论个人技术影响力是如何炼成的

每个圈子都有一群能力强且懂得经营自己的人,技术圈也是如此。本期 fir.im Weekly 一如往期精选了一些实用的 iOS,Android 开发工具和源码分享,还有一些关于程序员的成长 Tips 和活动分享~...

风起云飞fir_im ⋅ 2016/04/11 ⋅ 0

冰镇闪灵/EasyIOS

EasyIOS-Swift版本发布!欢迎Star、Fork EasyIOS is a new generation of development framework based on which makes faster and easier app development, Build your app by geek's way. E......

冰镇闪灵 ⋅ 2014/07/30 ⋅ 0

Service Oriented 的 iOS 应用架构

Intro 前不久我们上线了一款新的 App - Glow Baby,App 针对 0 - 12 个月大的新生宝宝,提供爸爸妈妈全面、健康、科学的育儿知识,帮助记录宝宝成长的点点滴滴。在 Glow Baby 的开发中,我们...

zh_iOS ⋅ 2016/06/16 ⋅ 0

iOS 开发的最佳实践、MVVM 架构以及编程艺术

1 、我整理翻译的最新版 iOS 最佳实践,该译本不仅呈现了原版的原汁原味,还扩充了相关的知识点及最新的开发实践,在原版的基础上融合了以往 Objective-C 和最近一些关于 Swift 的内容, 放在...

KevinHM ⋅ 2015/08/26 ⋅ 1

MVVM与Controller瘦身实践

前言 MVC是一个做iOS开发都知道的设计模式,也是Apple官方推荐的设计模式。实际上,Cocoa Touch就是按照MVC来设计的。 这里,我们先不讲MVC是什么,我们先来谈谈软件设计的一些原则或者说理念...

黄文臣 ⋅ 2017/06/14 ⋅ 0

免费的编程中文书籍索引【收藏速度】

语言无关类 优质博客 PyTab在线手册中心 ImportNew 廖雪峰的官方网站 程序员博客墙 操作系统 开源世界旅行手册 鸟哥的Linux私房菜 Linux 系统高级编程 The Linux Command Line (中英文版) L...

yonghu86 ⋅ 2015/04/15 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

PHP语言系统ZBLOG或许无法重现月光博客的闪耀历史[图]

最近在写博客,希望通过自己努力打造一个优秀的教育类主题博客,名动江湖,但是问题来了,现在写博客还有前途吗?面对强大的自媒体站点围剿,还有信心和可能型吗? 至于程序部分,我选择了P...

原创小博客 ⋅ 23分钟前 ⋅ 0

IntelliJ IDEA 2018.1新特性

工欲善其事必先利其器,如果有一款IDE可以让你更高效地专注于开发以及源码阅读,为什么不试一试? 本文转载自:netty技术内幕 3月27日,jetbrains正式发布期待已久的IntelliJ IDEA 2018.1,再...

Romane ⋅ 48分钟前 ⋅ 0

浅谈设计模式之工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻...

佛系程序猿灬 ⋅ 今天 ⋅ 0

Dockerfile基础命令总结

FROM 指定使用的基础base image FROM scratch # 制作base image ,不使用任何基础imageFROM centos # 使用base imageFROM ubuntu:14.04 尽量使用官方的base image,为了安全 LABEL 描述作...

ExtreU ⋅ 昨天 ⋅ 0

存储,对比私有云和公有云的不同

导读 说起公共存储,很难不与后网络公司时代的选择性外包联系起来,但尽管如此,它还是具备着简单和固有的可用性。公共存储的名字听起来也缺乏专有性,很像是把东西直接堆放在那里而不会得到...

问题终结者 ⋅ 昨天 ⋅ 0

C++难点解析之const修饰符

C++难点解析之const修饰符 c++ 相比于其他编程语言,可能是最为难掌握,概念最为复杂的。结合自己平时的C++使用经验,这里将会列举出一些常见的难点并给出相应的解释。 const修饰符 const在c...

jackie8tao ⋅ 昨天 ⋅ 0

聊聊spring cloud netflix的HystrixCommands

序 本文主要研究一下spring cloud netflix的HystrixCommands。 maven <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-clo......

go4it ⋅ 昨天 ⋅ 0

Confluence 6 从其他备份中恢复数据

一般来说,Confluence 数据库可以从 Administration Console 或者 Confluence Setup Wizard 中进行恢复。 如果你在恢复压缩的 XML 备份的时候遇到了问题,你还是可以对整个站点进行恢复的,如...

honeymose ⋅ 昨天 ⋅ 0

myeclipse10 快速搭建spring boot开发环境(入门)

1.创建一个maven的web项目 注意上面标红的部分记得选上 2.创建的maven目录结构,有缺失的目录可以自己建立目录补充 补充后 这时候一个maven的web项目创建完成 3.配置pom.xml配置文件 <proje...

小海bug ⋅ 昨天 ⋅ 0

nginx.conf

=========================================================================== nginx.conf =========================================================================== user nobody; #......

A__17 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部