一尘不染

为什么要在协议定义中包含功能而不是在扩展中定义功能?

swift

采用以下协议和扩展名:

protocol ProtocolA {
    func myFunc()
}

extension ProtocolA {
    func myFunc() {
        print("Default ProtocolA implementation.")
    }
}

这与将函数完全排除在协议定义之外有什么区别(例如):

protocol ProtocolB { }

extension ProtocolB {
    func myFunc() {
        print("Default ProtocolB implementation.")
    }
}

我发现了一个区别。如果我定义了一个覆盖默认实现的结构,则仅当将函数放在定义之外时,我才能将其强制转换为协议并调用协议的实现:

struct A: ProtocolA {
    func myFunc() {
        print("Struct A's implementation.")
    }
}

struct B: ProtocolB {
    func myFunc() {
        print("Struct B's implementation.")
    }
}

A().myFunc()                   // "Struct A's implementation."
(A() as ProtocolA).myFunc()    // "Struct A's implementation."

B().myFunc()                   // "Struct B's implementation."
(B() as ProtocolB).myFunc()    // "Default protocol implementation."

换句话说,如果您像in中那样 从协议定义中删除
函数,ProtocolB则可以通过将对象强制转换为协议来访问默认实现。另一方面,如果将函数保留在协议定义中,则无法转换为协议以获取默认协议行为。

将功能定义排除在协议之外似乎在行为方面具有最大的灵活性。

不利之处是什么?如果从协议定义中删除该功能,将会损失什么?您完全失去任何功能吗?


阅读 221

收藏
2020-07-07

共1个答案

一尘不染

将函数声明为协议定义的一部分将指示编译器在调用函数时使用动态调度,因为编译器希望实现协议的类型能够为该函数提供实现。这称为method requirement。现在,如果类型未定义方法,则运行时会将方法调用解析为协议扩展中声明的方法。

但是,在协议扩展中声明该函数
告诉编译器他不需要使用动态调度,而是使用静态调度,该方法速度更快,但是对于多态性来说工作并不很好,因为该协议即使符合协议的类型也实现了该方法,也会调用扩展实现。

为了说明上述内容,让我们考虑以下代码:

protocol Shape {
    func draw()
}

extension Shape {
    func draw(){
        print("This is a Shape")
    }
}

struct Circle: Shape {
    func draw() {
        print("This is a Circle")
    }
}

struct Square: Shape {
    func draw() {
        print("This is a Square")
    }
}

let shapes: [Shape] = [Circle(), Square()]

for shape in shapes {
    shape.draw()
}

上面的代码将有输出

This is a Circle 
This is a Square

这是因为draw()是a method requirement,这意味着在draw()调用时,运行时将draw ()在元素的实际类型内(在这种情况下,在Circle和内)搜索实现Square

现在,如果我们不声明draw为方法要求,则意味着我们不在协议声明中提及它

protocol Shape {
}

这样,编译器将不再使用动态分配,而直接进入协议扩展中定义的实现。因此,代码将打印:

This is a Shape
This is a Shape

而且,如果我们将数组的元素强制转换为我们期望的类型,那么我们就会得到重载的行为。这将打印This is a Circle

if let circle = shapes[0] as? Circle {
    circle.draw()
}

因为编译器现在可以判断的第一个元素shapesCircle,并且由于Circle具有一个draw()方法,因此它将调用该元素。

这是Swift处理抽象类的方法:它为您提供了一种方法,可以指定符合该协议的类型的期望值,同时允许这些方法的默认实现。

2020-07-07