Swift面向协议编程技术细节与工程演练 陈刚 2016 06 24
2020-03-01 303浏览
- 1.Swift面向协议编程技术细节与工程 演练 北京世纪好未来教育科技有限公司 研发工程师 陈刚
- 2.
- 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 { varhaveFun:Bool{ get set } varownMoney:Bool{ get set } } protocol Swifter { varcodingLevel:Int{ get set } }
- 22.协议扩展详解 structCoderFromA:Coder{ varname:String'>name:String