面向协议编程
面向协议编程(Protocol Oriented Programming,简称POP),是Swift的一种编程范式, Apple于2015年WWDC提出。 在Swift的标准库中,能见到大量POP的影子,同时,Swift也是一门面向对象的编程语言(Object Oriented Programming,简称OOP),在Swift开发中,OOP和POP是相辅相成的,任何一方并不能取代另一方,POP能弥补OOP一些设计上的不足。
面向对象的缺点
OOP的三大特性:封装、继承、多态,当多个类(比如A、B、C类)具有很多共性时,可以将这些共性抽取到一个父类中(比如D类),最后A、B、C类继承D类。
但有些问题,使用OOP并不能很好解决,比如如何将 A/B 两个控制器的公共方法 run 抽取出来?,基于OOP想到的一些解决方案:
- 将run方法放到另一个对象A中,然后 A/B 两个控制器拥有对象A属性,这样多了一些额外的依赖关系
- 将run方法增加到UIViewController分类中。这样做UIViewController会越来越臃肿,而且会影响它的其他所有子类
- 将run方法抽取到新的父类,采用多继承。这样做会增加程序设计复杂度,产生菱形继承等问题,需要开发者额外解决
面向协议给出的解决方案,A/B 都遵守共同的协议:
swift
protocol Runnable {
func run()
}
extension Runnable {
func run() {
print("run")
}
}
class A: UIViewController, Runnable {}
class B: UITableViewController, Runnable {}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
面向协议实例 添加前缀属性
比如我们要给String扩展一个计算出字符串含有多个数字的方法
swift
extension String{
var numberCount: Int{
var count = 0
for c in self where ("0"..."9").contains(c){
count += 1
}
return count
}
}
print("123456mmmmmm".numberCount)1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
这样做的一个缺点是如果跟其他的计算属性名字冲突时该怎么办?
- 我们可以像OC一样增加一个前缀,
- 也可以做成
"123456mmmmmm".m.numberCount这种方式来增加前缀。
- 第一次优化,添加 m 前缀 首先String需要增加 m 属性。M类里可以传入字符串并且添加计算属性。
swift
//添加M结构体,可以传入字符串
struct M {
var string: String
init(_ str: String) {
string = str
}
var numberCount: Int{
var count = 0
for c in string where ("0"..."9").contains(c){
count += 1
}
return count
}
}
// 分类增加 m 计算属性
extension String{
var m: M { return M(self)}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
添加 m 前缀之后,如果我们想对Array做扩展呢,又需要在 M 结构体中传入Array的参数。如果是想对其他类型做扩展呢?这样做不够通用,使用泛型对传入的类型做优化
- 第二次优化,使结构体进行通用使用泛型
swift
// 使用泛型接收多个类型
struct M<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
extension String{
var m: M<String> { return M(self)}
}
class Person{}
extension Person{
var m: M<Person> { M(self) }
}
// 对结构体进行扩展,限制调用的类型,比如给字符串扩展 `numberCount` 计算属性,只有字符串实例可以调用
extension M where Base == String{
var numberCount: Int{
var count = 0
for c in base where ("0"..."9").contains(c){
count += 1
}
return count
}
}
//对Person进行扩展,如果只想Person实例可以调用,使用Base == Person,使用 : 号Person及它的子类都可以调用
extension M where Base: Person{
func run() {
print("run")
}
}
print(welcome.m.numberCount)
Person().m.run()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
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
- 对类方法进行扩展
swift
// 扩展类属性
extension Person{
var m: M<Person> { M(self) }
static var m: M<Person>.Type {M<Person>.self}
}
// 扩展类方法
extension M where Base: Person{
func run() {
print("run")
}
static func run(){
print("static run")
}
}
// 调用
Person.m.run()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这样基本已经完成了,但是还有有问题,如果想让其他类型拥有 m 属性,也都要对他们进行扩展,让他们拥有 m 属性,这样写也有些冗余。 使用 协议再次优化
- 第三次优化,使用协议让每个遵守协议的人都拥有 m 属性
swift
// 前缀类型
struct M<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
// 利用协议扩展前缀属性
protocol MCompatible{}
extension MCompatible{
var m: M<Self> { M(self) }
static var m: M<Self>.Type {M<Self>.self}
}
// 给字符串扩展功能
extension String: MCompatible{} // 遵守协议让Srting拥有前缀属性
extension M where Base == String{
var numberCount: Int{
var count = 0
for c in base where ("0"..."9").contains(c){
count += 1
}
return count
}
}
// 其他类型想拥有前缀属性只要遵守协议即可
class Person{}
extension Person: MCompatible{}
struct Dog{}
extension Dog: MCompatible{}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
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
- 第四次优化 如果添加 mutating 方法,不能直接使用计算属性
swift
protocol MCompatible{}
extension MCompatible{
var m: M<Self> {
set{}
get{
M(self)
}
}
static var m: M<Self>.Type {
set{}
get{
M<Self>.self
}
}
}
extension String: MCompatible{}
extension M where Base == String{
mutating func test(){
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 让 NSString 遵守协议
swift
// String 和 NSString 都遵守 ExpressibleByStringLiteral 字面量协议,如果想让NSString 拥有方法
extension String: MCompatible {}
extension NSString: MCompatible {}
extension M where Base: ExpressibleByStringLiteral {
func numberCount() -> Int {
let string = base as! String
var count = 0
for c in string where ("0"..."9").contains(c) {
count += 1
}
return count
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
利用协议实现类型判断
创建一个协议,让数组遵守协议,判断是否是遵守了协议的类型,如果是则是数组类型
swift
protocol ArrayType {}
extension Array: ArrayType {}
extension NSArray: ArrayType {}
func isArrayType(_ type: Any.Type) -> Bool { type is ArrayType.Type }
isArrayType([Int].self)
isArrayType([Any].self)
isArrayType(NSArray.self)
isArrayType(NSMutableArray.self)1
2
3
4
5
6
7
8
2
3
4
5
6
7
8