Swift编程模式(面向对象以及函数式编程等)

51 阅读32分钟

函数式编程

范式转换-从一个题目说起

  • 读入一个文本文件,确定所有单词的使用频率从高到低排序,打印所有单词及其频率的排序列表
  • 这道题目出自计算机科学史上的著名事件,是当年Communicaitons of the ACM杂志"Programming Pearls"专栏的作者Jon Bentley向计算机科学先驱Donald Knuth提出的挑战
范式转换-传统解决方案

image.png

范式转换-函数式

image.png

范式转换
  • 命令式编程风格常常迫使我们出于性能考虑,把不同的任务交织起来,以便能够用一次循环来完成多个任务
  • 而函数式编程用map()、filter()这些高阶函数报我们解放出来,让我们站在更高的抽象层次上去考虑问题,把问题看得更清楚
简洁
  • 面向对象编程通过封装不确定因素来使代码能被人理解;函数式编程通过尽量减少不确定因素来使代码能被人理解
  • 在面向对象的命令式编程语言里面,重用的单元是类和类之间沟通用的消息
  • 函数式编程语言实现重用的思路很不一样。函数式语言提倡在有限的几种关键数据结构(如list、set、map)上运用针对这些数据结构高度优化过的操作,以此构成基本的运转机构。开发者再根据具体用途,插入自己的数据结构和高阶函数去调整机构的运转方式。
  • 比起一味创建新的类结构体系,把封装的单元降低到函数级别,更有利于达到细粒度、基础层面的重用。
  • 函数式程序员喜欢用少数几个核心数据结构,围绕它们去建立一套充分优化的运转机构。面向对象程序员喜欢不断地创建新的数据结构和附属的操作,因为压到一切的面向对象编程范式就是建立新的类和类间的消息。把所有的数据结构都封装成类,一方面压制了方法层面的重用,另一方面鼓励了大粒度的框架式重用。函数式编程的程序构造更方便我们在比较细小的层面上重用代码。
课后题
  • 找到一个字符串里面某个字符数组里面第一个出现的字符的位置。比如"Hello,World",[“a”,"e","i","o","u"],那e是在字符串第一个出现的字符,位置是1,返回1
  • 提示:zip函数
业务需求
  • 假设我们有一个名字列表,其中一些条目由单个字符构成。现在的任务是,将除去单字符条目之外的列表内容,放在一个逗号分隔的字符串里返回,且每个名字的首字母都要大写。
命令式解法
  • 命令式编程是按照"程序是一系列改变状态的命令"来建模的一种编程风格。传统的for循环是命令式风格的绝好例子:先确立初始状态,然后每次迭代都执行循环体中的一系列命令。

image.png

函数式解法
  • 函数式编程将程序描述为表达式和变换,以数学方程的形式建立模型,并且尽量避免可变的状态。函数式编程语言对问题的归类不同于命令式语言。如前面所用到的几种操作(filter、transform、convert),每一种都作为一个逻辑分类由不同的函数所代表,这些函数实现了低层次的变换,但依赖于开发者定义的高阶函数作为参数来调整其低层次运转机构的运作。

image.png

聊聊Swift的劣势-并行

image.png

对Swift的尝试改进

image.png

image.png

课后练习
  • parallelMap是非线程安全的,尝试改为线程安全
具有普遍意义的基本构造单元
  • 筛选(filter)
  • 映射(map)
  • 折叠/化约(foldLeft/reduce等)
并发式map

image.png

Swift面向对象的三大特性
  • 继承
  • 封装
  • 多态
Swift面向对象的基本单元
  • 枚举
  • 结构体
  • 协议
  • 扩展
面向对象概述
  • 从整体的功能上看Swift的枚举、结构体、类三者具有完全平等的地位
  • Swift的类、结构体、枚举中都可以定义(属性、方法、下标、构造体、嵌套类型)
  • 在Swift中,枚举和结构体是值类型,类是引用类型
类和结构体相似点
  • 定义属性用来存储值
  • 定义方法用于提供功能
  • 定义下标脚本用来允许使用下标语法访问值
  • 定义初始化器用于初始化状态
  • 可以被扩展来默认所没有的功能
  • 遵循协议来针对特定类型提供标准功能
类和结构体不同点
  • 继承允许一个类继承另一个类的特征
  • 类型转换允许你在运行检查和解释一个类实例的类型
  • 反初始化器允许一个类实例释放任何其所被分配的资源
  • 引用计数允许不止一个对类实例的引用
增强的枚举
枚举语法
  • 用enum关键字来定义一个枚举,然后将其所有的定义内容房子啊一个大括号({})中
  • 多个成员值可以出现在同一行中,要用逗号隔开
enum CompassPoint {
     case north
     case south
     case wast
     case west
}
  • 每个枚举都定义了一个全新的类型。正如Swift中其它的类型那样,它们的名称(例如:CompassPoint和Planet)需要首字母大写。给枚举类型起一个单数的而不是复数的名字,从而使得它们能够顾名思义
enum Planet {
  case mercury,venus,earth,mars,jupiter,saturn,uranus,neptune
}
使用Switch语句来匹配枚举值
  • 你可以用switch语句来匹配每一个单独的枚举值

image.png

遍历枚举的case
  • 对于某些枚举来说,如果能有一个集合包含了枚举的所有情况就好了。你可以通过在枚举名字后面写:CaseIterable来允许枚举被遍历。Swift会暴露一个包含对应枚举类型的左右情况集合名为allCases

image.png

关联值
  • 可以定义Swift枚举来存储任意给定类型的关联值,如果需要的话不同枚举成员关联值的类型可以不同

image.png

  • 可以定义Swift枚举来存储任意给定类型的关联值,如果需要的话不同枚举成员关联值的类型可以不同

image.png

image.png

image.png

原始值
  • 枚举成员可用相同类型的默认值预先填充(称为原始值)

image.png

预设原始值
  • 当你在操作存储整数或字符串原始值枚举的时候,你不必显示地给每一个成员都分配一个原始值。当你没有分配时,Swift将会自动为你分配值。

image.png

从原始值初始化
  • 如果你用原始值类型来定义一个枚举,那么枚举就会自动收到一个可以接受原始值类型的值的初始化器(叫做rawValue的形式参数)然后返回一个枚举成员或者nil.你可以使用这个初始化器来尝试创建一个枚举的新实例。

image.png

递归枚举
  • 递归枚举是拥有另一个枚举作为枚举成员关联值的枚举。当编译器操作递归枚举时必须插入间接寻址层。你可以在生命枚举成员之前使用indirect关键字来明确它是递归的。
  • 如说表达式(5+4)*2在乘法右侧有一个数但有其他表达式在乘法的左侧。

image.png

为类、结构体以及枚举添加属性

存储属性
  • 在其最简单的形式下,存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性(由var关键字引入)要么是常量存储属性(由let关键字引入)
常量结构体实例的存储属性
  • 如果你创建了一个结构体的实例并且把这个实例赋给常量,你不能修改这个实例的属性,即使声明为变量的属性。
延迟存储属性
  • 延迟存储属性的初始值在其第一次使用时才进行计算。你可以通过在其声明前标注lazy修饰语来表示一个延迟存储属性。
  • 如果被标记为lazy修饰符的属性同时被多个线程访问并且属性还没有初始化,则无法保证属性只初始化一次。

image.png

计算属性
  • 除了存储属性,类、结构图和枚举也能够定义计算属性,而它实际并不存储值。相反,它提供一个读取器和一个可选的设置器来间接得到和设置其它的属性和值。

image.png

简写setter
  • 如果一个计算属性的设置器没有为将要被设置的值定义一个名字,那么它将被默认命名为newValue。

image.png

简写getter
  • 如果整个getter的函数体是一个单一的表达式,那么getter隐式返回这个表达式

image.png

只读计算属性
  • 一个有读取器但是没有设置器的计算属性就是所谓的只读计算属性。只读计算属性返回一个值,也可以通过点语法访问,但是不能被修改为另一个值。
  • 你必须用var关键字定义计算属性(包括只读计算属性)为变量属性,因为他们的值不是固定的。let关键字只用于常量属性,用于明确哪些值一旦作为实例初始化就不能更改

image.png

属性观察者
  • willSet会在该值被存储之前被调用
  • didSet会在一个新值被存储后被调用
  • 如果你实现了一个willSet观察者,新的属性会以常量形式参数传递。你可以在你的willSet实现中为这个参数定义名字。如果你没有为它命名,那么它会使用默认的名字newValue。
  • 如果你实现了一个didSet观察者,一个包含属性值的常量形式参数将会被传递。你可以为它命名,也可以使用默认的形式参数名oldValue。如果你在属性自己的didSet观察者里给自己赋值,你赋值的新值就会取代刚刚设置的值。

image.png

全局和局部变量
  • 观察属性的能力同样对全局变量有效。全局变量是定义在任何函数、方法、闭包或者类型环境之外的变量。局部变量是定义在函数、方法或者闭包环境之中的变量。

image.png

类型属性
  • 使用static关键字来定义类型属性。对于类类型的计算类型属性,你可以用class关键字来允许子类重写父类的实现

image.png

实例方法
  • 实例方法是属于特定类实例、结构体实例或者枚举实例的函数。他们为这些实例提供功能性,要么通过提供访问和修改实例属性的方法,要么通过提供与实例目的相关的功能

image.png

实例方法-self
  • 每一个类的实例都隐含一个叫做self的属性,它完完全全与实例本身相等。你可以使用self属性来在当前实例当中调用它自身的方法。
  • 实际上,你不需要经常在代码中写self。如果你没有显示地写出self,Swift会在你于方法中使用已知属性或者方法的时候假定你是调用了当前实例中的属性或者方法。
  • 例外就是当一个实例方法的形式参数名与实例中某个属性拥有相同的名字的时候。在这种情况下,形式参数名具有优先权,并且调用属性的时候使用更加严谨的方式就很有必要了。你可以使用self属性来区分形式参数名和属性名。

image.png

在实例方法中修改属性
  • 结构体和枚举是值类型。默认情况下,值类型属性不能被自身的实例方法修改
  • 你可以选择在func关键字前放一个mutating关键字来指定放可以修改属性

image.png

在mutating方法中赋值给self
  • Mutating方法可以指定整个实例给隐含的self属性

image.png

枚举的mutating方法
  • 枚举的异变方法可以设置隐的self属性为相同的枚举里的不同成员。

image.png

类型方法
  • 通过在func关键字之前使用static关键字来明确一个类型方法。类同样可以使用class关键字来允许子类重写父类对类型方法的实现。

image.png

类结构体添加下标
  • 类、结构体和枚举可以定义下标,它可以作为访问集合、列表或序列成员元素的快捷方式。你可使用下标通过索引值来设置或检索值而不需要为设置和检索分别使用实例方法。
  • 可以为一个类型定义多个下标,并且下标会基于传入的索引值的类型选择合适的下标重载使用。下标没有限制单个维度,可以使用多个输入形参来定义下标以满足自定义类型的需求。
下标语法
  • 下标脚本允许你通过在实例名后面的方括号内写一个或多个值对该类的实例进行查询。它的语法类似于实例方法和计算属性。使用关键字subscribt来定义下标,并且指定一个或多个输入形式参数和返回类型,与实例方法一样。与实例方法不同的是,下标可以是读写也可以是只读的。

image.png

下标参数
  • 下标可以接受任意数量的输入形式参数,并且这些输入形式参数可以是任意类型。下标也可以返回任意类型。下标可以使用变量形式参数和可变形式参数,但是不能使用输入输出形式参数或提供默认形式参数值。

image.png

image.png

类型下标
  • 实例下标,如果上文描述的那样,你在对应类型的实例上调用下标。你同样也可以定义类型本身的下标。这类下标叫做类型下标。你可通过subscribt关键字前加static关键字来标记类型下标。在类里则使用class关键字,这样可以允许子类重写父类的下标实现。

image.png

类的初始化和反初始化
  • Swift为所有没有提供初始化器的结构体或类提供了一个默认的初始化器来给所有的属性提供了默认值。这个默认的初始化器只是简单地创建了一个所有属性都有默认值的新实例。

image.png

自定义初始化
  • 可以提供初始化形式参数作为初始化器的一部分,来定义初始化过程中的类型和值的名称。初始化形式参数与函数和方法的形式参数具有相同的功能和语法。

image.png

在初始化中分配常量属性
  • 在初始化的任意时刻,你都可以给常量属性赋值,只要它在初始化结束时设置了确定的值即可。一旦为常量属性赋值,它就不能再被修改了。

image.png

结构体的成员初始化器
  • 如果结构体类型中没有定义任何自定义初始化器,它会自动获得一个成员初始化器。不同于默认初始化器,结构体会接收成员初始化器即使它的存储属性没有默认值。
struct Size{
var width=0.0,height=0.0
}
let twoByTwo = Size(width:2.0,height:2.0)
值类型的初始化器委托
  • 初始化器可以调用其他初始化器来执行部分实例的初始化。这个过程,就是所谓的初始化器委托,避免了多个初始化器里冗余代码。

image.png

类的继承和初始化
  • 所有类的存储属性(包括从它的父类继承的所有属性)都必须在初始化期间分配初始值。
  • Swift为类类型定义了两种初始化器以确保所有的存储属性接收一个初始值。这些就是所谓的指定初始化器和便捷初始化器。
  • 指定初始化器是类的主要初始化器。指定的初始化器可以初始化所有那个类引用的属性并且调用合适的父类初始化器来继续这个初始化过程给父类链。
  • 类偏向于少量指定初始化器,并且一个类通常只有一个指定初始化器。指定初始化器是初始化开始并持续初始化过程到父类链的"传送"点。
  • 每个类至少得有一个指定初始化器。如同在初始化器的自动继承里描述的那样,在某些情况喜爱,这些需求通过从父类继承一个或多个指定初始化器来满足。
  • 便捷初始化器是次要的。你可以在相同的类里定义一个便捷初始化器来调用一个指指定的初始化器作为便捷初始化器来给指定初始化器设置默认形式参数。你也可以为具体的使用情况或输入的值类型定义一个便捷初始化器从而创建这个类的实例。
  • 如果你的类不需要便捷初始化器你可以不提供它。在为通用的初始化模式创建方式以节省时间或者类的初始化更加清晰明了的时候使用便捷初始化器。
指定初始化器和便捷初始化器
  • 用与值类型的简单初始化器相同的方式来写类的指定初始化器
  • 用convenience修饰符放到init关键字前定义便捷初始化器
类的初始化委托
  • 指定初始化器必须从它的直系父类调用指定初始化器
  • 便捷初始化器必须从相同的类里调用另一个初始化器
  • 便捷初始化器最终必须调用一个指定初始化器

image.png

两段式初始化
  • Swift的类初始化是一个两段式过程。在第一个阶段,每一个存储属性被引入类分配了一个初始值。一旦每个存储属性的初始状态确定,第二个阶段就开始了,每个类都有机会在新的实例准备使用之前来定制它的存储属性。
  • 两段式初始化过程的使用让初始化更加安全,同时在每个类的层级结构给与了完备的灵活性。两段式初始化过程可以防止属性值在初始化之前被访问,还可以防止属性值被另一个初始化器意外地赋予不同的值。
安全检查
  • 1、指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
  • 2、指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖。
  • 3、便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖。
  • 4、初始化器在第一个阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self作为值。

image.png

image.png

image.png

image.png

两段式初始化过程-阶段1
  • 指定或便捷初始化器在类中被调用
  • 为这个类的新实例分配内存,内存还没有被初始化
  • 这个类的指定初始化器确保所有由此类引入的存储属性都有一个值。现在这些存储属性的内存被初始化了
  • 指定初始化器上交父类的初始化器为其存储属性执行相同的任务
  • 这个调用父类初始化器的过程将沿着初始化链一直向上进行,直到到达初始化器链的最顶部
  • 一旦达了初始化器链的最顶部,在链顶部的类确保所有的存储属性都有一个值,此实例的内存被认为完全初始化了,此时第一阶段完成。

image.png

两段式初始化过程-阶段2
  • 从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例。能够访问self并且可以修改它的属性,调用它的实例方法等等。
  • 最终,链中任何便捷初始化器都有机会定制实例以及使用self

image.png

初始化器的继承和重写
  • 不像在OC中的子类,Swift的子类不会默认继承父类的初始化器。Swift的这种机制防止父类的简单初始化器被一个更专用的子类继承并被用来创建一个没有完全或错误初始化的新实例的情况发生。只有在特定情况下才会继承父类的初始化器。
  • 如果你想自定义子类来实现一个或多个和父类相同的初始化器,你可以在子类中为那些初始化器提供定制的实现。
  • 当你写的子类初始化器匹配父类指定初始化器的时候,你实际上可以重写那个初始化器。因此,在子类的初始化器定义之前你必须写override修饰符。如同默认初始化器所描述的那样,即使是自动提供的默认初始化器你也可以重写。
初始化器的自动继承
  • 如果你的子类没有定义任何指定初始化器,它会自动继承父类所有的指定初始化器
  • 如果你的子类提供了所有父类指定初始化器的实现--要么是通过规则1继承来的,要么通过在定义中提供自定义实现的--那么它自动继承所有的父类便捷初始化器。
可失败初始化器
  • 定义类、结构体或枚举初始化时可以失败在某些情况下会管大用。这个失败可能由以下几种方式触发,包括给初始化传入无效的形式参数值,或缺少某种外部所需的资源,又或是其他阻止初始化的情况。
  • 为了妥善处理这种可能失败的情况,在类、结构体或枚举中定义一个或多个可失败的初始化器。通过在init关键字后面添加问号(init?)来写。
必要初始化器
  • 在类的初始化器前添加required修饰符来表明所有该类的子类都必须实现该初始化器。
反初始化
  • 在类实例别释放的时候,反初始化器就会立即被调用。你可以是用deinit关键字来写反初始化器,就如同写初始化器要用init关键字一样。反初始化器只在类类型中有效。
  • 反初始化器会在实例被释放之前自动被调用。你不能自行调用反初始化器。父类的反初始化器可以被子类继承,并且子类的反初始化器实现结束之后父类的反初始化器会被调用。父类的反初始化器总会被调用,就算子类没有反初始化器。
  • 每个类当中只能有一个反初始化器。反初始化器不接收任何形式参数,并且不需要写圆括号。
deinit {
// perform the deinitialization
}
定义基类
  • 任何不从另一个类继承的类都是所谓的基类
  • Swift类不会从一个通用基类继承。你没有指定特定父类的类都会以基类的形式创建。

image.png

子类
  • 子类是基于现有类创建新类的行为。子类从现有的类继承了一些特征,你可以重新定义它们。你也可以为子类添加新的特征。
  • 为了表明子类有父类,要把子类写在父类的前面,用冒号分隔。

image.png

重写
  • 子类可以提供它自己的实例方法、类型方法、实例属性,类型属性或下标脚本的自定义实现,否则它将会从父类继承。这就是所谓的重写。
  • 要重写而不是继承一个特征,你需要在你的重写定义前面加上override关键字。这样做说明你打算提供一个重写而不是意外提供了一个相同定义。意外的重写可能导致意想不到的行为,并且任何没有使用override关键字的重写都会在编译时被诊断为错误。
访问父类的方法、属性和下标脚本
  • 你可以通过使用super前缀访问父类的方法、属性或下标脚本
  • 一个命名为someMethod()的重写方法可以通过super.someMethod()在重写方法的实现中调用父类版本的someMethod()方法
  • 一个命名为someProperty的重写属性可以通过super.someProperty在重写的getter或setter实现中访问父类版本的someProperty属性
  • 一个命名为someIndex的重写下标脚本可以使用super[someIndex]在重写的下标脚本实现中访问父类版本中相同的下标脚本
重写方法
  • 可以在子类中重写一个继承的实例或类型方法来提供定制的替代的方法实现

image.png

重写属性的getter和setter
  • 可以提供一个自定义的getter(和setter,如果合适的话)来重写任意继承的属性,无论在最开始继承的属性实现为储属性还是计算属性
重写属性的观察器
  • 可以使用属性重写来为继承的属性添加属性观察器。这就可以让你在继承属性的值改变时得到通知,无论这个属性最初如何实现
  • 不能给继承而来的常量存储属性或者只读的计算属性添加属性观察器。这些属性的值不能被设置,所以提供willSet或didSet实现作为重写的一部分也是不合适的
  • 不能为同一个属性同时提供重写的setter和重写的属性观察器。如果你想要监听属性值的改变,并且你已经为那个属性提供了一个自定义的setter,那么你从自定义的setter里就可以监听任意值的改变。

image.png

阻止重写
  • 可以通过标记为final来阻止一个方法、属性或者下标脚本被重写。通过在方法、属性或者下标脚本的关键字前写final修饰符(比如final var,final func,final class func,final subscript)
类型

image.png

类型检查
  • 使用类型检查操作符(is)来检查一个实例是否属于一个特定的子类。如果实例是该子类类型,类型检查操作符返回true,否则返回false.
向下类型转换
  • 某个类类型的常量或变量可能实际上在后台引用自一个子类的实例。当你遇到这种情况时你可以尝试使用类型转换操作符(as?或as!)将它向下转换至其子类类型。
  • 由于向下类型转换可能失败,类型转换操作符就有了两个不同形式。条件形式,as?,返回了一个你将要向下类型转换的值的可选项。强制形式,as!,则将向下类型转换和强制展开综合为一个步骤。
Any和AnyObject
  • Swift为不确定的类型提供了两种特殊的类型别名
  • AnyObject可以表示任何类类型的实例
  • Any可以表示任何类型,包括函数类型
嵌套类型
  • Swift中的类,结构体和枚举可以进行嵌套,即在某一类型的内部定义类型,这种类型嵌套在Java中称为内部类,在C#中称为嵌套类。
  • 嵌套类型的能够访问它外部的成员。
extension
  • 扩展为现有的类、结构体、枚举类型、或协议添加了新功能。这也包括了为无访问权限的源代码扩展类型的能力(即所谓的逆向建模)
  • 扩展和Objective-C中的category类似。(与Objective-C的分类不同的是,Swift的扩展没有名字。)
extension的能力
  • 添加计算实例属性和计算类型属性
  • 定义实例方法和类型方法
  • 提供新初始化器
  • 定义下标
  • 定义和使用新内嵌类型
  • 使现有的类型遵循某协议
  • 扩展可以向一个类型添加新的方法,但是不能重写已有的方法
extension计算属性
  • 扩展可以向已有的类型添加计算实例属性和计算类型属性

image.png

扩展初始化器
  • 扩展可向已有的类型添加新的初始化器。这允许你扩展其他类型以使初始化器接收你的自定义类型作为形式参数,或提供该类型的原始实现中未包含的额外初始化选项
  • 扩展能为类添加新的便捷初始化器,但是不能为类添加指定初始化器或反初始化器。指定初始化器和反初始化器必须由原来类的实现提供

image.png

扩展方法
  • 扩展可以为已有的类型添加新的实例方法和类型方法

image.png

扩展mutating方法
  • 扩展的实例方法仍可以修改(或异变)实例本身。结构体和枚举类型方法在修改self或本身的属性时必须标记实例方法为mutating,和原本实现的异变方法一样

image.png

扩展下标
  • 扩展能为已有的类型添加新的下标

image.png

扩展添加内嵌类型
  • 扩展可以为已有的类、结构体和枚举类型添加新的内嵌类型

image.png

协议

协议的语法
  • 自定义类型声明时,将协议名放在类型名的冒号之后来表示该类型采纳一个特定的协议。多个协议可以用逗号分开列出。
  • 若一个类拥有父类,将这个父类名放在其采纳的协议名之前,并用逗号分隔。

image.png

属性要求
  • 协议可以要求所有遵循该协议的类型提供特定名字和类型的实例属性或类型属性。协议并不会具体说明属性时储存型属性还是计算属性--它只具体要求属性有特定的名称和类型。协议同时要求一个属性必须明确时可读的或可读的和可写的
  • 若协议要求一个属性为可读和可写的,那么该属性要求不能用常量存储属性或只读计算属性来满足。若协议只要求属性为可读的,那么任何种类的属性都能满足这个要求,而且你的代码需要的话,该属性也可以是可写的

image.png

image.png

  • 在协议中定义类型属性时在前面添加static关键字。当类的实现使用class或static关键字前缀声明类型属性要求时,这个规则仍然适用。

image.png

方法要求
  • 协议可以要求采纳的类型实现指定的实例方法和类方法。这些方法作为协议定义的一部分,书写方式与正常实例和类方法的方式完全相同,但是不需要大括号和方法的主体。允许拥有参数的方法使用同样的规则。但在协议的定义中,方法参数不能定义默认值。
  • 正如类型属性要求的那样,当协议中定义类型方法时,你总要在其之前添加static关键字。即使在类实现时,类型方法要求使用class或static作为关键字前缀,前面的规则仍然适用
mutating方法要求

若你定义了一个协议的实例方法需求,想要异变任何采用了该协议的类型实例,只需在协议里方法的定义当中使用mutating关键字。这允许结构体和枚举类型能采用相应协议并满足方法要求。

初始化器要求
  • 协议可以要求遵循协议的类型实现指定的初始化器。和一般的初始化器一样,只用将初始化器写在协议的定义当中,只是不用写大括号也就是初始化器的实体
protocol SomeProtocol {
 init(someParameter: Int)
}
初始化器要求的类实现
  • 你可以通过实现指定初始化器或便捷初始化器来使遵循该协议的类满足协议的初始化器要求。在这两种情况喜爱,你都必须使用required关键字修饰初始化器的实现
class SomeClass: SomeProtocol{
  required init(someParameter: Int){
  // initializer implementation goes here
  }
}
  • 如果一个子类重写了父类指定的初始化器,并且遵循协议实现了初始化器的要求,那么就要为这个初始化器的实现添加required和override两个修饰符

image.png

将协议作为类型
  • 在函数、方法或者初始化器里作为形式参数类型或者返回类型
  • 作为常量、变量或者属性的类型
  • 作为数组、字典或者其他存储器的元素的类型
协议继承
  • 协议可以继承一个或者多个其他协议并且可以在它继承的基础之上添加更多要求。协议继承的语法与类继承的语法相似,只不过可以选择列出多个继承的协议,使用逗号分隔。

image.png

类专用的协议
  • 通过添加AnyObject关键字到协议的继承列表,你就可以限制协议只能被类类型采纳(并且不是结构体或者枚举)

image.png

协议组合
  • 可以使用协议组合来复合多个协议到一个要求里。协议组合行为就和你定义的临时局部协议一样拥有构成中所有协议的需求。协议组合不定义任何新的协议类型
  • 协议组合使用SomeProtocol&AnotherProtocol的形式。你可以列举任意数量的协议,用和符号连接(&).。除了协议列表,协议组合也能包含类类型,这允许你标明一个需要的父类。

image.png

可选协议要求
  • 你可以给协议定义可选要求,这些要求不需要强制遵循协议的类型实现。可选要求使用optional修饰符作为前缀放在协议的定义中。可选要求允许你的代码与Objective-C操作。协议和可选要求必须使用@objc标志标记。注意@objc协议只能被继承自Objective-C类或其他@objc类采纳。它们不能被结构体或者枚举采纳。
协议和扩展
在扩展里添加协议遵循
  • 你可以扩展一个已经存在的类型来采纳和遵循一个新的协议,就算是你无法访问现有类型的源代码也行。扩展可以添加新的属性、方法和下标到已经存在的类型,并且因此允许你添加协议需要的任何需要。

image.png

有条件地遵循协议
  • 泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你采纳协议的名字后面写泛型where分句。

image.png

使用扩展声明采纳协议
  • 如果一个类型已经遵循了协议的所有需求,但是没有声明采纳了这个协议,你可以通过一个空的扩展来让它采纳这个协议。

image.png

协议扩展
  • 协议可以通过扩展来提供方法和属性的实现以遵循类型。这就允许你在协议自身定义行为,而不是在每一个遵循或者全局函数里定义。

image.png

提供默认实现
  • 你可以使用协议扩展来给协议的任意方法或者计算属性要求提供默认实现。如果遵循类型给这个协议的要求提供了他自己的实现,那么它就会替代扩展中提供的默认实现。
给协议扩展添加限制
  • 当你定义一个协议扩展,你可以明确遵循类型必须在扩展的方法和属性可用之前满足的限制。在扩展协议名字后边使用where分句来写这些限制。

image.png

OOP(面向对象编程)
  • 几乎所有的编程语言都支持OOP,Java、Ruby等语言的设计理念中几乎将一切事物都看作对象,对象即中心、对象即真理。
OOP的缺陷

image.png

POP(面向协议编程)

image.png

集合类

image.png

image.png

OOP vs POP
  • OOP-主要关心对象是什么
  • POP-主要关心对象做什么
OOP

image.png

image.png

POP

image.png

image.png

POP

image.png

image.png

image.png

image.png

image.png

POP

image.png

image.png

image.png

改进

image.png

image.png