Swift extension 和 protocol 使用优化

Swift extension

Extensions in Swift can:

  1. Add computed instance properties and computed type properties
  2. Define instance methods and type methods
  3. Provide new initializers
  4. Define subscripts
  5. Define and use new nested types
  6. Make an existing type conform to a protocol

1 - 4, 统一表示可以添加方法, 属性等
5, 可以定义一些嵌套的类型
6, 给当前类型实现一些新的协议

下面这部分代码包含了 1 - 6 的具体实现, 具体就不说了

struct A {}

struct B {
    func run() {
        print("run")
    }
}

protocol C {
    func ccc()
}

extension A: C {
    static var b = B()
    var bb: B {
        get {
            Self.b
        }
        set {
            Self.b = newValue
        }
    }
    
    func run() {
        Self.b.run()
    }
    
    static func run() {
        b.run()
    }
    
    init(b: B) {
        Self.b = b
    }
    
    struct AA {}
    func ccc() {
        
    }
}

print(A.b.run())

Swift extension 包含泛型的类型 where 判断 T 是否实现某个协议

struct K: Print {
    func print() {
        Swift.print("KKKKKK")
    }
}

struct KK {}

struct B<T> {
    var t: T
    init(t: T) {
        self.t = t
    }
}

extension B where T: Print {
    func printT() {
        self.t.print()
    }
}


let b = B(t: KK())
let b1 = B(t: K())
b1.printT()

如上代码, 只有传入的 T 实现了 Print 协议, B才包含 printT() 方法.
当传入的是 KK() 时, 则无法调用 printT()
这里判断 T 是否实现某个协议使用的是 :

Swift extension 包含泛型的类型 where 判断 T 是否是某个类型

struct B<T> {
    var t: T
    init(t: T) {
        self.t = t
    }
}

class CC {}
class CCC: CC {}

extension B where T == CC {
    func printT() {
        print("CCC")
    }
}

let b2 = B(t: CC())
b2.printT()

判断 T 是否是某个类型用 ==, == 判断的是绝对的类型, T == CC, 即使你传入的是该类型的子类也不可以, let b2 = B(t: CCC()), b2 是没有 printT() 方法的.

protocol extension

  1. 添加协议中方法的默认实现
  2. 添加一些新的方法或者属性并实现, 和其他的扩展相比, 不可以添加新的协议的继承, 不可以定义一些嵌套的类型.
  3. 给这个协议添加一些约束, 如下代码, 只有当泛型 Element 实现了 Equatable 协议, 才给 Collection 协议, 添加 allEqual() 方法
extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}
  1. 关于 where 后面加的是 == 类型判断的是使用, 和上面的 extension 中介绍的相同
    主要是协议中定义的泛型的类型约束, 可以使用 : 来进行协议的约束, 使用 == 来进行具体类型的约束

extension 关于静态派发

先定义两个协议 A1、A2,都定义了 protocolRun 方法,在 extension 中提供了默认实现,并且在 extension 中额外给两个协议增加了 extensionRun 方法。

protocol A1 {
    func protocolRun()
}

extension A1 {
    func protocolRun() {
        print("protocolRun A1")
    }
    
    func extensionRun() {
        print("extensionRun A1")
    }
}

protocol A2 {
    func protocolRun()
}

extension A2 {
    func protocolRun() {
        print("protocolRun A2")
    }
    
    func extensionRun() {
        print("extensionRun A2")
    }
}

如下,让 AA 实现了协议 A1、A2,但是由于 A1,A2 都定义了协议 protocolRun,即使在 extension 中提供默认实现,编译器还是会报错,必须实现 protocolRun 方法,因为 AA,无法确定改用哪个默认实现。

class AA: A1, A2 {

}

// 编译报错
// Type 'AA' does not conform to protocol 'A1'
// Type 'AA' does not conform to protocol 'A2'

接下来,AA 实现了 protocolRun,编译运行,没有问题。

class AA: A1, A2 {
    func protocolRun() {
        print("protocolRun AA")
    }
}

let aa = AA()
aa.protocolRun()
// 打印
// protocolRun AA

接下来,我们调用 extensionRun 方法,编译直接报错 Ambiguous use of 'extensionRun()', 为什么这里只有在调用的时候才报编译错误那,它和 protocolRun 的区别是什么?

class AA: A1, A2 {
    func protocolRun() {
        print("protocolRun AA")
    }
}

let aa = AA()
aa.extensionRun() // Ambiguous use of 'extensionRun()'

根据上面的经验,给 AA 也提供了 extensionRun 的实现,ok, 看起来没问题了。

class AA: A1, A2 {
    func protocolRun() {
        print("protocolRun AA")
    }
    
    func extensionRun() {
        print("extensionRun AA")
    }
}

let aa = AA()
aa.extensionRun()
// 打印
// extensionRun AA

但根据上面 extensionRun 和 protocolRun 两个方法编译时表现的不同,它们总是有差异的。
协议动态派发和静态派发:
swift protocol 定义的方法走的是表派发,即动态派发,调用方法时先去协议表中查找方法地址。
extension 添加的方法走的是静态派发,静态派发在编译时,调用方法的位置直接执行方法地址。

protocol Drawable {}

struct Point: Drawable {}
struct Line: Drawable {}

struct Pair {
    init(_ a: Drawable, _ b: Drawable) {
        self.first = a
        self.second = b
    }
    var first: Drawable
    var second: Drawable
}

Generic Code 和 protocol 的区别
泛型可以优化编译,在编译过程中直接转化为静态派发

// 方法 1

func pFunc(a: Drawable) {
    
}

// 方法2

func gFunc<T: Drawable>(b: T) {
    
}

方法2在编译的时候,就会吧 T 替换成具体的类型, 例如:

gFunc(b: Line())

会被编译成下面这样,这样就去除了 protocol 表的使用

func gFunc<T: Line>(b: Line) {
    
}

所以泛型在编译的过程中,会根据实际的调用生成不同的方法

protocol 是如何存放到数组中的?
existential container
前三个值留个 valueBuffer
小于3个值,直接存放到 valueBuffer
大于3个值的时候,存放一个指针指向堆 alloc

https://developer.apple.com/videos/play/wwdc2016/416/?time=1474
https://devstreaming-cdn.apple.com/videos/wwdc/2016/416k7f0xkmz28rvlvwb/416/416_understanding_swift_performance.pdf?dl=1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。