AirJD 焦点
AirJD

没有录音文件
00:00/00:00
加收藏

Swift面向协议编程技术细节与工程演练 by 陈刚@好未来

发布者 mobile
发布于 1476070295927  浏览 5226 关键词 移动开发, iOS 
分享到

第1页

Swift面向协议编程技术细节与工程 演练

北京世纪好未来教育科技有限公司 研发工程师 陈刚



第3页

面向协议编程之前的协议

• OC&Swift2.0版本之前,协议是没有实现的 ,协议的用法大致就是:delegate、 datasource。

• 比如最常用的“点赞”功能,在cell上放置按 钮,覆盖cell的用户响应、保留按钮的用户 响应。

• 用户点击按钮后需要修改数据源的状态。



第4页

面向协议编程之前的协议

• 按钮的定义在cell的子类中,IBAction也在定 义在cell的子类中,而数据源却在Controller 的子类中。

• 需要解决的问题是:如何把按钮的点击事件 传递给Controller的子类。

• 写一个delegate协议,请看工程演示。



第5页

面向协议编程之前的协议

• 让我们来分解一下上面的操作:

• 1.声明一个Deleagte协议,这个协议可能只用到一次 • 2.在cell中定义可选型类型的属性,把1中定义的协议

作为类型使用。 • 3.Controller子类遵循自定义的Delegate协议,并定义具

体实现。 • 4.在获得每个Cell的实例的时候,指定实例的delegate

属性为Controller,实现绑定。



第6页

面向协议编程之前的协议

• 更加Swift化的方案:使用闭包替代协议。 • 请看工程演示。



第7页

面向协议编程之前的协议

• 使用闭包的步骤分解: • 1.向定义其他类型的属性一样,定义一个闭

包并定义初始值(一般都是空操作)。 • 2.在controller子类中为闭包属性重新赋值。



第8页

面向协议编程之前的协议

• 在Swift中闭包(当然还有函数和方法)是“ 一级公民”,补充一些闭包的知识。

• 使用闭包的好处:

1.步骤更简单,可读性强

2.代码耦合度更高,避免跨越代码,增加无意义的 理解成本



第9页

面向协议编程之前的协议

• 使用闭包的注意事项:

• 闭包和类一样,也是引用类型的,会持有内部的对象,所以有“循环引 用”的风险。在示例中使用了“捕获列表”来避免“循环引用”。不过不需 要太过担心,苹果的官方文档中有说明,闭包只在特定情况下才有出 现“循环引用”的风险。

• 方法中的闭包参数不会出现“循环引用”的风险。需要注意的是,如果 方法体中有循环调用某个闭包参数的代码,经常在参数列表中为闭包 参数加上@noescape关键字。@noescape的主要目的是避免在循环中不 断引用相同的闭包,提升内存利用率。标准库中CollectionType协议中 的map、filter等常用的方法都是基于@noescape的。



第10页

面向协议编程之前的协议

• 捕获列表是API的使用者在使用时添加的, 使用者需要明确“循环引用”的触发条件,避 免添加无意义的关键字。

• @noescape是API的开发者在定义时加入的, 在使用闭包时{}中不会要求加入self关键字, API的使用者可以不用关心@noescape。



第11页

面向协议编程之前的协议

• 来明确一下“循环引用”风险的触发条件。 • 方法和函数都是以“名称”为第一标识符的,然后是参

数类型和返回值类型,作为一门强类型语言,参数 类型和返回值类型同时适用于方法重载。而闭包是 匿名的,如示例中所示,闭包单独使用时,需要作 为一个常量或者变量的类型使用,因此闭包自然而 然会成为某个数据类型的成员。如果持有闭包的是 类,那么会有“循环引用”的风险。这种情况需要特别 注意。



第12页

面向协议编程之前的协议

• 闭包作为方法参数时不会出现循环引用。 Swift中的方法的实现方式很有趣,无论是类 型方法还是实例方法。闭包作为参数不会发 生循环引用是因为方法本身并不属于某个数 据结构,实例方法、构造器的实现机制是基 于“柯里化”的。



第13页

面向协议编程之前的协议

• “柯里化”是什么? • “柯里化”的方法都有多组参数列表,每传入

一组参数,都会返回剩下的参数列表与返回 值所组成的新函数。 • 请看playground演示。



第14页

面向协议编程之前的协议

• 结论:Swift的方法、构造器不是保存在数据结构 中的,数据结构为定义在它内部的每个方法(无 论是类型方法还是实例方法)定义了命名空间。 调用方法的实例其实是这个命名空间中的方法的 第一组参数,闭包是第二组参数列表中的参数, 所以二者是平级的,不存在持有关系,因此即便 在尾随闭包中使用了self中的内容,也不会发生“循 环引用”。



第15页

面向协议编程之前的协议

• Swift中的闭包合理地接管了delegate的职责 ,此时协议的定义有点尴尬了。直到 Swift2.0中协议扩展的引入。

• 首先来看个简单的例子,感受一下Swift2.0 之后标准库中的协议扩展。

• 请看playground中的演示。



第16页

协议扩展初探

• Swift标准库中的Equatable协议要求用户重载 ==操作符(操作符是全局的函数,不用太在 意函数和方法,二者本质是一样的),由于 !=和==在逻辑上是互斥的,所以在Equatable 的API设计中,一旦重载了==操作符, Equatable在扩展中定义的!=的默认实现就起 作用了。



第17页

协议扩展初探

• 协议是什么?

• 一个人拥有名字、一辆车可以行使、一个数据集合可 以搜索等等都可以抽象成协议。

• 协议是功能的最小化描述,粒度要比继承小得多。并 且协议本身不能保存任何数据(协议扩展中不能定义 存储属性),因此遵循一个协议不会带来额外的数据 负担。

• 协议的核心思想:组合优于继承



第18页

协议扩展详解

• 协议扩展的使用步骤:

第一步 协议的声明,很像其他数据类型的声明,只不过没有实现 而已。

第二步 协议的扩展,可以指定扩展的适用对象,在扩展中定义默认 的实现。

第三步 有类、结构体或者枚举表示遵循这个协议。 第四步 遵循协议的数据类型来实现协议中声明的属性和方法,改

写免费获得的默认实现



第19页

协议扩展详解

• 协议的声明:

protocol 协议:继承的协议1,继承的协议2 { var 某个属性:类型{set,get} func 某个方法(参数列表) -> 返回值类型 init 构造器(参数列表)

}

属性需要明确读写的最低权限,协议遵守者需要符合属性的 名称和最低权限,但是不限制属性是存储属性还是计算属性。



第20页

协议扩展详解

• 遵循协议

class 某个类:父类,协议1,协议2...{}

不同于OC,遵循协议格式上与继承是相同的。如果多个协议总是一起 出现,可以使用typealias关键字给多个协议起一个别名,typealias并不会 生成新的协议,现在上面的代码可以使用如下的格式来遵守协议:

typealias 协议组合别名 = protocol<协议1,协议2,…> class 某个类:父类,协议组合别名{} struct 某个结构体:协议组合别名{}



第21页

协议扩展详解

• 协议扩展

因为Swift是单类继承,而且结构体和枚举还不能被继承,

这就为很多有用信息的传递造成了一定的麻烦。首先从数据

冗余的角度来举例:

protocol Coder { var haveFun:Bool{ get set } var ownMoney:Bool{ get set }

} protocol Swifter {

var codingLevel:Int{ get set } }



第22页

协议扩展详解

struct CoderFromA:Coder { var name:String var haveFun:Bool var ownMoney:Bool

} struct CoderFromB:Coder,Swifter {

var name:String var haveFun = true var ownMoney = true var codingLevel = 3 } struct CoderFromC:Coder,Swifter { var name:String var haveFun = true var ownMoney = true var codingLevel = 5 }



第23页

协议扩展详解

所有的程序员都关心自己是否快乐、是否有钱,所以每个结构体都 遵循协议Coder。A公司的程序员不是Swift程序员,而B公司和C公司 的程序员都是Swift程序员,每个公司的Swift程序员的编程能力等级不 同。观察上述代码可以发现,Swift程序员都是快乐且富有的,因此结 构体CoderFromB和CoderFromC中会有冗余的部分,这是由不同协议 Swifer与Coder间的因果关系所引起的。虽然我们知道这个事实,但是 由于规则的关系我们不得不重复地去赋值haveFun和ownMoney属性。



第24页

协议扩展详解

• 使用协议扩展解决数据冗余: extension Coder where Self:Swifter { var haveFun:Bool{ return true } var ownMoney:Bool{ return true } } 老话说过了:协议扩展中的属性不能是存储属性。协议扩展的语法是非常“语言化”的,上面的代码很 好理解:如果一个程序员是Swift程序员,那么他有钱又开心。

协议扩展影响的是协议的遵循。现在你可以删掉CoderFromB以及CoderFromC中的haveFun以及 ownMoney的定义了。

很多时候计算属性和方法是很相似的,让我们来看看协议扩展中的方法,请看playground演示。



第25页

协议扩展详解

• 协议扩展的动态特性和静态特性:声明在协议 声明列表中的方法会具有动态特性,会被完全 重写。声明在协议扩展中的方法会具有静态特 性,在切换上下文时可以获得协议的两个版本 。在协议扩展中定义的方法如果调用了另一个 在协议扩展中定义的方法,那么它得到的永远 是最原始的版本。



第26页

协议扩展详解

• 简单来说:在设计一个协议时,把希望被重 写的那些方法定义在协议的声明中,把希望 保留原始逻辑的协议定义在协议的扩展中, 以备其他基于该方法默认实现的方法使用该 方法的原始版本,这样即便在该方法被遵守 者重写的时候,其他方法的逻辑不会受到影 响。



第27页

协议扩展中的泛型

• 面向协议编程中泛型是非常重要的。

首先依旧从用法来切入,Swift标准库中数组的判等定义如下: public func ==<Element : Equatable>(lhs: [Element], rhs: [Element]) -> Bool

尖括号中是泛型定义,Element是一个占位符,代表了所有 遵循Equatable协议的数据结构。



第28页

协议扩展中的泛型

• 声明一个泛型协议:

protocol SomeProtocol{ associatedtype PrivateElement func elementMethod1(element:PrivateElement) func elementMethod2(element:PrivateElement)

}

这里虽然没有出现节点语法,但是上面的协议却是个不折不扣的泛型协议, PrivateElement起到了占位符的作用,指示了某种类型。根据协议的规则,协议SomeProtocol 的遵守者必须实现上面两个方法,此时PrivateElement除了显式地体现了泛型的优势,还隐 性地约束了两个方法的参数必须是相同类型的。你不用刻意去指定PrivateElement的具体类 型,编译器会根据你实现方法的时候传入的参数类型确定PrivateElement的具体类型



第29页

协议扩展中的泛型

• 由于关联对象本身也是一种泛型,所以可以 使用协议对关联对象做限制:

protocol SomeProtocol{ associatedtype PrivateElement:Comparable func elementMethod1(element:PrivateElement) func elementMethod2(element:PrivateElement)

}



第30页

协议扩展中的泛型

• 在协议的遵守者的声明中泛型转变成具体的类型:

struct TestStruct:SomeProtocol{ func elementMethod1(element:String){ print("elementFromMethod1:\(element)")

}

func elementMethod2(element:String){ print("elementFromMethod2:\(element)")

} }

注意在实现的时候不能直接用PrivateElement了,PrivateElement只存在

于具体实现之前,如果你尝试使用PrivateElement,编译器会报错。



第31页

协议扩展中的泛型

类似于的associatedtype的还有Self关键字,代表协议的遵守者本身的类型,适用于像比较这类 的方法,你必须传入另一个相同类型的参数才有意义:

protocol CanCompare{ func isBigger(other:Self) -> Bool

}

然后使用我们之前定义的盒子类型来试验一下: struct BoxInt:CanCompare{

var intValue:Int func isBigger(other: BoxInt) -> Bool {

return self.intValue > other.intValue } } 编译通过,现在新建两个实例测试一下: BoxInt(intValue: 3).isBigger(BoxInt(intValue: 2))//结果为true



第32页

协议扩展中的泛型

关联对象是协议层面的泛型,类似于数据结构层面的类型,

上面的方法如果使用数据结构层面的泛型的话,格式如下:

struct TestStruct< T:SignedNumberType >{ func elementMethod1(element:T){ print("elementFromMethod1:\(element)")

}

func elementMethod2(element:T){ print("elementFromMethod2:\(element)")

} }

let test = TestStruct<Int>() test.elementMethod1(1)



第33页

协议扩展中的泛型

• 泛型特化:无论是数据结构层面的泛型还是协议层面的泛型, 泛型特化都发生在编译期,泛型特化之后需要是一个类型,不 能再是泛型,否则在类型推断时会出现问题:

struct TestStruct< T:Comparable >{ var array:[T] = [1,2,””]//报错

}



第34页

方法中的泛型

• 数据结构和协议层面的泛型在特化后都是某一个具体的类型 ,如果你想要定义泛型的方法,使用如下方式:

struct TestStruct{

func elementMethod1< T:Comparable >(element:T){ print("elementFromMethod1:\(element)")

}

func elementMethod2< T:Comparable >(element:T){ print("elementFromMethod2:\(element)")

} } let test = TestStruct() test.elementMethod1(1) test.elementMethod1("abc")



第35页

协议扩展中的Where关键字

我们可以在协议扩展中继续“开发”SomeProtocol,定义在协议扩 展中的方法可以对某些遵守者“隐身”,而对另一些遵守者“可见”, 依赖where关键字所进行的筛选。

继续深入上面的例子,需要注意的一点是,协议的遵守者如果 遵守了多个协议,那么这些协议的关联对象名称不能重复,否则编 译器会报错,所以关联对象的命名最好能有足够的描述力,并且避 开标准库中的关联对象命名:比如Element、Generator等。



第36页

协议扩展中的Where关键字

where关键字可以与Self关键字配合(注意首字母大写),在协议扩展中新定 义一个方法,指定当协议的遵守者必须是集合类型时,打印出遵守者的元素个数 。

extension SomeProtocol where Self:CollectionType{ func showCount(){ print(self.count) }

}

我们所熟悉的count实际是定义在协议CollectionType中的,也就是说遵循 CollectionType的对象必须要返回count属性的值,where的限制是强制的,它是一 个编译器强制的开关。



第37页

协议扩展中的Where关键字

下面让我们来尝试一下,Array类型已经遵循了CollectionType协议,所以如 果你让Array同时遵循SomeProtocol,那么它将免费获得shwoCount方法:

extension Array:SomeProtocol{

func elementMethod1(element:String){ print("elementFromMethod1:\(element)")

}

func elementMethod2(element:String){ print("elementFromMethod2:\(element)")

} }

[1,2,3].showCount()//打印3



第38页

协议扩展中的Where关键字

协议扩展中的where关键字为我们提供了一个安全的数据世界

。除了使用Self关键字指定遵守者本身,where所限定的粒度还

可以继续缩小。比如协议SomeProtocol,你可以使用where限定

协议中的关联对象: extension SomeProtocol where

PrivateElement:SignedNumberType{ func showElement(){ print(OwnElement) }

}



第39页

协议扩展中的Where关键字

• 如果关联对象中在定义时指定了必须遵循的协议,那么 where可以对关联对象所遵循的协议中的成员做限制:

extension SequenceType where Self.Generator.Element : Comparable {

@warn_unused_result public func sort() -> [Self.Generator.Element]

}



第40页

协议的继承

协议是可以被继承的,不过因为协议的粒度足够小,秉承组合优于继承的理念,大部分 情况下协议都不需要继承。不过在数据结构比较复杂的时候依然需要用到协议的继承。

依旧参考Swift标准库中用到的协议继承:

public protocol CollectionType : Indexable, SequenceType{…}

标准库中有非常多的SequenceType协议的遵守者是使用Int作为下标类型的,比如Array、 Range,所以这些数据结构遵循了CollectionType,CollectionType对下标等操作的定义做了封 装,以减少定义,所以协议定义中的继承其实也是一种“组合”。这种组合不会丧失底层协议 的一般性,String.characters不是使用Int作为下标的,所以它遵循了SequenceType协议。



第41页

协议的继承

• 通常我们的数据结构不会像标准库那样复杂 ,所以你可以避免使用协议的继承,专注于 协议的组合。



第42页

协议总结

• Swift协议是“绝对开关”,所有遵循协议的数 据结构,必须满足协议参数列表中的所有方 法,如果有不能满足的情况,那只能去单独 定义该方法,用泛型去限制方法的参数。比 如Array可以使用==,但是它没有遵循 Equatable协议。



第43页

协议扩展的影响

• 协议扩展对iOS开发的影响:为什么 WWDC2015中408号414号视频主讲人在互相 推荐?

• 408_protocoloriented_programming_in_swift • 414_building_better_apps_with_value_types_

in_swift



第44页

协议扩展的影响

• 协议扩展解放了Swift中的值类型。 • 值类型是定长的,运行在栈上,速度更快,

不会被共享,线程安全,不会产生循环引用 ,不会递归… • 为什么一直以来值类型都得不到广泛的使用 ? • 因为我们太过依赖类的继承特性。



第45页

协议扩展的影响

• 跨出面向协议编程的第一步,从使用值类型 开始。

• 暂歇一下,稍后将带来:

• 1.面向协议编程“伪架构”,终结MVC与MVVM之争。 • 2.一些编程建议。 • 3.自由交流时间



第46页

基础数据

• 从一个简单的Demo开始:

这个应用会使用一个TableView混合展示一个时间段的所有待 办事宜和这个时间段的节日提醒,由于待办事件和节日的数据 构成是不同的,所以需要创建两个模型,它们在TableView上展 示的样式也应该有所不同,很常规的我们还需要一个 TableViewCell的子类。



第47页

基础数据

• 需要两个数据模型(结构体而不是类)

struct Event { var date = "" var eventTitle = "" init(date:String,title:String){ self.date = date self.eventTitle = title }

} struct Festival {

var date = "" var festivalName = "" init(date:String,name:String){

self.date = date self.festivalName = name } }



第48页

基础视图

• cell子类的定义:

class ShowedTableViewCell: UITableViewCell { //用来展示事件主题或节日名称的Label @IBOutlet weak var mixLabel: UILabel! //用来展示日期的Label @IBOutlet weak var dateLabel: UILabel!

}



第49页

数据源异构

• 由于我们的table需要混排,所以需要数据源 以后,不要使用子类,在面向协议编程中使 用协议的手段制造异构:

protocol HasDate{ var date:String {get}

}



第50页

控制器中的异构数据源

• 异构数据源:

var dataList = [HasDate](){ didSet{ tableView.reloadData() }

} var loadeddataList:[HasDate] = [Event(date: "2月14", eventTitle: "送礼物"),Festival(date: "1月1日", festivalName: "元旦"),Festival(date: "2月14", festivalName: "情人节")]



第51页

模拟数据源的加载

• 模拟的延迟加载:

override func viewDidLoad() { super.viewDidLoad() let delayInSeconds = 2.0 let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds *

Double(NSEC_PER_SEC))) dispatch_after(popTime, dispatch_get_main_queue())

{ () -> Void in self.dataList =

self.loadeddataList.sort{$0.date < $1.date} }

}



第52页

传统的MVC

• 传统MVC中,数据与视图绑定的过程在控制器中:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID, forIndexPath: indexPath) as! ShowedTableViewCell

//注意这里,通过可选绑定进行异构数据的类型控制 if let event = dataList[indexPath.row] as? Event{

cell.mixLabel.text = event.eventTitle cell.dateLabel.text = event.date cell.backgroundColor = UIColor.redColor() return cell } else if let festival = dataList[indexPath.row] as? Festival{ cell.mixLabel.text = festival.festivalName cell.dateLabel.text = festival.date cell.backgroundColor = UIColor.whiteColor() return cell } else { return cell } }



第53页

运行效果

• 运行的效果如下:



第54页

分析MVC

• 我不喜欢谈架构,架构有种莫名的压力。 • 我喜欢MVC,因为一切都看起来那么自然,

创建了一个Cell的实例,从数据源中取出一 个数据的实例,将二者结合。 • 可惜控制器里面的代码太多了,而数据和视 图中除了定义什么都没有,这不公平!



第55页

MVVM

• 来看看MVVM吧!

• 不修改数据源和视图中的代码,定义一个协议,这里的属性 并不是真实的数据源中的属性,只能通过名字来关联记忆。

protocol CellPresentable{ var mixLabelData:String {get set} var dateLabelData:String {get set} var color: UIColor {get set} func updateCell(cell:ShowedTableViewCell)

}



第56页

中间层的协议扩展

• 声明一个cell样式所需要的全部数据,然后 对号入座。

extension CellPresentable{ func updateCell(cell:ShowedTableViewCell){ cell.mixLabel.text = mixLabelData cell.dateLabel.text = dateLabelData cell.backgroundColor = color }

}



第57页

视图中新增方法

• 你需要在视图中新增一个方法:

class ShowedTableViewCell: UITableViewCell { //用来展示事件主题或节日名称的Label @IBOutlet weak var MixLabel: UILabel! //用来展示日期的Label @IBOutlet weak var dateLabel: UILabel!

func updateWithPresenter(presenter: CellPresentable) {

presenter.updateCell(self) } }



第58页

定义一个ViewModel

• 定义ViewModel,吸纳异构数据源的每一个类型: struct ViewModel:CellPresentable{ var dateLabelData = "" var mixLabelData = "" var color = UIColor.whiteColor() init(modal:Event){ self.dateLabelData = modal.date self.mixLabelData = modal.eventTitle self.color = UIColor.redColor() } init(modal:Festival){ self.dateLabelData = modal.date self.mixLabelData = modal.festivalName self.color = UIColor.whiteColor() } }



第59页

修改控制器

• 终于,我们可以回到控制器中来了:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID, forIndexPath: indexPath) as! ShowedTableViewCell

if let event = dataList[indexPath.row] as? Event{ let viewModel = ViewModel(modal: event) cell.updateWithPresenter(viewModel) return cell

} else if let festival = dataList[indexPath.row] as? Festival{ let viewModel = ViewModel(modal: festival) cell.updateWithPresenter(viewModel) return cell

} else { return cell

} }



第60页

MVVM运行结果

• 运行结果是正确的:



第61页

MVVM总结

• 就我们的刚才的例子来说,收益甚低,因为 控制器中的代码没有省掉几行,却创建了一 堆中间代码。或许MVVM更适合复杂的视图

• 什么时候用MVC,什么时候用MVVM?



第62页

终结MVC与MVVM之争

• 终结MVC与MVVM之争!



第63页

伪架构

• 四月份的时候我研究出了“新套路”,在博文 中起了个炫酷的名字叫“幽灵架构”。因为相 比于MVVM,效果真的有点鬼魅。从编程舒 适度的角度来看,你不需要把它当做架构, 因为真的很简单,暂且称之为“伪架构”



第64页

伪架构核心协议

//视图使用的协议 protocol ViewType{

func getData<M:ModelType>(model:M) } extension ViewType{

//定义默认实现,因为可能出现方法重载 func getData<M:ModelType>(model:M){

} } //数据使用的协议 protocol ModelType{ } //定义默认方法giveData extension ModelType{

func giveData<V:ViewType>(view:V){ view.getData(self)

} }



第65页

方法参数说明

• 请注意参数类型是泛型的:

func getData<M:ModelType>(model:M)

• 在方法中不要把协议当做类型:

func getData(model:ModelType)

• 2015WWDC408号视频中有提到,但是没具体 讲,因为协议类型的参数会产生Box类型,浪 费性能。

• 请看playground演示



第66页

伪架构的模型

• 需要展示的数据模型都要遵循ModelType • 别忘了组合一下协议:

• typealias DateViewModel
 = 
 protocol<hasDate,ModelType>

• 现在的模型:

struct Event:DateViewModel{…} struct Festival:DateViewModel{…}

• 除此之外没有别的工作了



第67页

伪架构的视图

extension ShowedTableViewCell:ViewType{ func getData<M : ModelType>(model: M) { //xcode7.3之前这里不能写成guard let dateModel = model as?

DateViewModel else{} guard let dateModel = model as? DateViewModel else{ return } //处理相同属性 dateLabel.text = dateModel.date //处理数据源异构 if let event = dateModel as? Event{ mixLabel.text = event.eventTitle backgroundColor = UIColor.redColor() } else if let festival = dateModel as? Festival{ mixLabel.text = festival.festivalName backgroundColor = UIColor.whiteColor() }

} }



第68页

伪架构的控制器

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID, forIndexPath: indexPath) as! ShowedTableViewCell

dataList[indexPath.row].giveData(cell)

return cell }



第69页

伪架构工程演示

• 运行效果请直接看工程演示。 • 如果数据源不是异构的,比如[Event]或者

[Festival],直接调用getData方法即可, giveData方法是专门拆封异构数据源的,比如 工程中的CollectionView展示的是Festival,直 接用了getData。



第70页

伪架构工程演示

• 如果数据和视图的绑定逻辑需要发生变化,那么重载giveData和getData, 同理同构数据源的时候只重载getData即可:

//针对数据源的扩展 extension ModelType where Self:HasDate{

func giveData<V:ViewType>(view:V,tag:Bool){ if let tableCell = view as? ShowedTableViewCell{ tableCell.getData(self,tag:tag) }

} } //针对视图的扩展 extension ViewType where Self:ShowedTableViewCell {

func getData<M:ModelType>(model:M,tag:Bool){ //留空,依旧在视图代码中重新实现

} }



第71页

伪架构工程演示

• 视图中只重写一个getData方法(默认的或定制的): extension ShowedTableViewCell:ViewType{ func getData<M : ModelType>(model: M,tag:Bool) { guard let dateModel = model as? HasDate else{ return } //处理相同属性 dateLabel.text = dateModel.date //处理数据源异构 if let event = dateModel as? Event{ mixLabel.text = event.eventTitle backgroundColor = tag ? UIColor.redColor() : UIColor.whiteColor()//加入tag的逻辑

} else if let festival = dateModel as? Festival{ mixLabel.text = festival.festivalName backgroundColor = tag ? UIColor.whiteColor(): UIColor.redColor()

} } }



第72页

伪架构工程演示

• 在如上的工程中,存在两种视图:UICollectionViewCell、 UITableViewCell。同时也存在两种数据模型:Event和 Festival。数据源和视图自由绑定的话有非常多种情况, 这是一种多对多的关系,所以非常适合使用408号视频中 的异构处理,也就是getData方法中的:

let 别名 = model as? 类型{…}



第73页

伪架构总结

• 是不是很清爽?没有引入中间层,一切都依 靠协议完成,转移了数据和视图的绑定。



第74页

伪架构总结

• 想想在下次开发的时候你能做点什么? • 在后台人员返回可用的数据接口的之前,在

你思考控制器中复杂的数据处理之前,你的 数据模型和视图的绑定已经提早完成了。 • getData中的代码是不依赖控制器的,即便 你删掉整个控制器代码也不会有影响。



第75页

面向协议编程的数据处理

• 上面展示了处理数据和视图的绑定的伪架构 ,接着展示如何使用面向协议编程思想处理 纯数据。

• 为上面的Demo增加搜索功能,由于数据保 存在tableView和CollectionView,所以我们需 要一个通用的数据处理模型



第76页

通用搜索模型

• 日期的模糊匹配搜索协议:

protocol SearchDate{ }

extension SearchDate{ func search<D:HasDate>(source:[D],key:String) ->

[D]{ return source.filter{ $0.date.containsString(key) }

} }



第77页

同构数据

• 上面的通用搜索协议只能搜同构的数据源, 并且返回的也是相同类型的数据源,异构的 数据不能定义泛型的方法,返回的类型也是 异构的,但是处理异构也是非常简单的。



第78页

异构数据

• 定义一个针对异构数据源的方法:

extension SearchDate{ func search<D:HasDate>(source:[D],key:String) -> [D]{ return source.filter{ $0.date.containsString(key) } } func search(source:[DateViewModel],key:String) ->

[DateViewModel]{ return source.filter{

// if let festival = $0 as? Festival{ // return festival.date.containsString(key) // } else if let event = $0 as? Event{ // return event.date.containsString(key) // } // return false

//实际上不需要对异构数据源特化,只需要写下面这行代码 return $0.date.containsString(key)

} } }



第79页

运行结果

• 请看示例工程中的控制台



第80页

总结

• 面向协议编程有无限的可能性等待发掘,下 面是我个人的一些学习建议,仅做参考:

• 1.使用值类型,注意定义Copy-­‐on-­‐Write • 2.不要用Any、AnyObject • 3.使用泛型提升性能 • 4.合理使用可选型,需要过程信息的API使用错误处理。



支持文件格式:*.pdf
上传最后阶段需要进行在线转换,可能需要1~2分钟,请耐心等待。