最近,我一直在使用Swift开发多个面向协议的应用程序框架,并注意到协议扩展中有一些(看似)奇怪的静态函数行为,特别是从元类型调用扩展函数的地方。
我最初发现这些行为的方法是对一个错误进行故障排除,其中一个对象的类型以一种看似不可能的方式发生了变化。我追溯了问题,并最终确定这是因为在静态函数中,Self
和self
可以保留不同的类型(注意:我已经采用了调用这些" Big S Self"和" Little s self"分别)。我将通过我在游乐场中掀起的一些简单的例子证明这一点:
class SomeBaseClass: SomeProtocol {}
class SomeChildClass: SomeBaseClass {}
protocol SomeProtocol {}
extension SomeProtocol {
static private func getName() -> String {
return "\(self): \(type(of: self))"
}
static func ambiguousName() -> String {
return getName()
}
static func littleName() -> String {
return self.getName()
}
static func bigName() -> String {
return Self.getName()
}
}
let child: SomeBaseClass.Type = SomeChildClass.self // SomeChildClass.Type
print(child.ambiguousName()) // "SomeChildClass: SomeBaseClass.Type\n"
print(child.littleName()) // "SomeChildClass: SomeBaseClass.Type\n"
print(child.bigName()) // "SomeBaseClass: SomeBaseClass.Type\n"
print(SomeChildClass.ambiguousName()) // "SomeChildClass: SomeChildClass.Type\n"
print(SomeChildClass.littleName()) // "SomeChildClass: SomeChildClass.Type\n"
print(SomeChildClass.bigName()) // "SomeChildClass: SomeChildClass.Type\n"
print(SomeBaseClass.ambiguousName()) // "SomeBaseClass: SomeBaseClass.Type\n"
print(SomeBaseClass.littleName()) // "SomeBaseClass: SomeBaseClass.Type\n"
print(SomeBaseClass.bigName()) // "SomeBaseClass: SomeBaseClass.Type\n"
可以看出,当从元类型调用静态函数时,如果将元类型分配给具有父类的元类型的声明类型的变量,则结果可能不同。
我的问题是Self
如何知道它是什么类型的?那么self
如何知道它是什么类型的呢?对我来说,为什么self
甚至可以在静态函数中访问也没有意义,因为首先没有实例。我原以为一个人应该专门使用Self
,但现在我认为情况并非如此,因为Self
和self
已被证明会产生不同的结果一些场景。
此外,在省略self
或Self
时使用self
的类型是否有任何理由,如在返回语句return getName()
中ambiguousName()
函数?
对我来说,我认为最奇怪的部分是type(of: self)
从SomeBaseClass.Type
函数调用调用时返回child.littleName()
。不应该是"动态类型"仍然是SomeChildClass
?
答案 0 :(得分:10)
协议扩展中Self
的值由一组复杂的因子决定。在静态级别使用self
或在实例级别使用type(of: self)
代替Self
几乎总是更可取。这可以确保您始终使用调用该方法的动态类型,从而避免出现奇怪的意外。
首先让我们简化你的例子。
protocol P {
init()
}
extension P {
static func createWithBigSelf() -> Self {
return Self()
}
static func createWithLittleSelf() -> Self {
return self.init()
}
}
class A : P {
required init() {}
}
class B : A {}
let t: A.Type = B.self
print(t.createWithBigSelf()) // A
print(t.createWithLittleSelf()) // B
我们可以看到使用Self
将返回A
的新实例,而使用self
将返回B
的新实例。
为了理解为什么会出现这种情况,我们需要准确理解Swift如何调用协议扩展方法。
查看IR,createWithBigSelf()
的签名是:
define hidden void @static (extension in main):main.P.createWithBigSelf () -> A (
%swift.opaque* noalias nocapture sret, // opaque pointer to where return should be stored
%swift.type* %Self, // the metatype to be used as Self.
i8** %Self.P, // protocol witness table for the metatype.
%swift.type* // the actual metatype the method is called on (self).
) #0 {
(createWithLittleSelf()
的签名几乎相同。)
编译器生成4个不可见参数 - 一个用于返回指针,一个用于符合类型的协议见证表,两个swift.type*
参数用于表示self
和{{1 }}
因此,这意味着可以传递不同的元类型来表示Self
或self
。
了解如何调用此方法:
Self
我们可以看到 // get metatype for B (B.self).
%3 = call %swift.type* @type metadata accessor for main.B() #4
// store this to to t, which is of type A.Type.
store %swift.type* %3, %swift.type** @main.t : main.A.Type, align 8
// load the metatype from t.
%4 = load %swift.type*, %swift.type** @main.t : main.A.Type, align 8
// get A's metatype.
%5 = call %swift.type* @type metadata accessor for main.A() #4
// call P.createWithBigSelf() with the following parameters...
call void @static (extension in main):main.P.createWithBigSelf () -> A(
%swift.opaque* noalias nocapture sret bitcast ( // the address to store
%C4main1A** @main.freshA : main.A to %swift.opaque* // the return value (freshA)
),
%swift.type* %5, // The metatype for A – this is to be used for Self.
i8** getelementptr inbounds ( // The protocol witness table for A conforming to P.
[1 x i8*],
[1 x i8*]* @protocol witness table for main.A : main.P in main, i32 0, i32 0
),
%swift.type* %4 // The metatype stored at t (B.self) – this is to be used for self.
)
的元类型传入了A
和Self
的元类型(存储在B
中)正在传递t
。如果您认为self
的返回类型(如果在createWithBigSelf()
类型的值上调用)将为A.Type
,那么这实际上非常有意义。因此A
为Self
,而A.self
仍为self
。
作为一般规则,B.self
的类型由调用该方法的东西的静态类型决定。 (因此,在您拨打Self
的情况下,bigName()
正在Self.getName()
上呼叫getName()
。
这也适用于例如方法,例如:
SomeBaseClass.self
使用// ...
extension P {
func createWithBigSelf() -> Self {
return Self()
}
func createWithLittleSelf() -> Self {
return type(of: self).init()
}
}
// ...
let b: A = B()
print(b.createWithBigSelf()) // A
print(b.createWithLittleSelf()) // B
Self
调用方法,A.self
调用self
实例。
当你开始使用存在时,事情变得更加复杂<(> {3}})。如果您直接调用扩展方法(即它们不是协议要求),那么对于实例方法,B
的值由静态类型确定在存在容器中将其装箱时的值,例如:
Self
的
let b: A = B()
let p: P = b // metatype of b stored as A.self.
print(p.createWithBigSelf()) // A()
print(p.createWithLittleSelf()) // B()
存在的容器还会存储值的元类型(以及值缓冲区和协议以及值见证表),这是从装箱时的静态类型中获取的。然后将此元类型用于let b = B()
let p: P = b // metatype of b stored as B.self.
print(p.createWithBigSelf()) // B()
print(p.createWithLittleSelf()) // B()
,从而导致上面演示的一些令人惊讶的行为。
对于元类型存在(例如Self
),存在容器只存储元类型和协议见证表。然后,在P.Type
扩展中调用静态方法时,此元类型将用于 Self
和self
,此时该方法不是#ta; ta协议要求。
协议要求实现的方法将通过协议见证表动态调度,以符合该协议的类型。在这种情况下,P
的值被直接符合协议的类型所取代(尽管我并不完全确定编译器执行此操作的原因)。
例如:
Self
在这两种情况下,都会通过protocol P {
static func testBigSelf()
}
extension P {
static func testBigSelf() {
print(Self.self)
}
}
class A : P {}
class B : A {}
let t: P.Type = A.self // box in existential P.Type
t.testBigSelf() // A
let t1: P.Type = B.self
t1.testBigSelf() // A
的协议见证表动态调度对testBigSelf()
的调用,以确保符合A
P
{{3} } B
一致性)。因此P
为Self
。它与实例方法完全相同。
这通常出现在通用函数中,它通过协议见证表*动态调度协议要求。例如:
A.self
传递func foo<T : P>(t: T) {
t.testBigSelf() // dispatch dynamically via A's PWT for conformance to P.
}
foo(t: A()) // A
foo(t: B()) // A
或A
的实例是否无关紧要 - B
是通过testBigSelf()
的PWT调度的,以确保一致性至A
,P
为Self
。
(*虽然编译器可以通过生成通用函数的专用版本进行优化,但这并不会改变观察到的行为。)
在大多数情况下,A.self
的类型由静态类型决定,无论调用哪种方法。 Self
的值只是调用该方法的值本身(静态方法的元类型,实例方法的实例),作为隐式参数传入。 / p>
我们发现的完整细分是self
,self
&amp;的价值。协议扩展中的Self
是:
静态范围(type(of: self)
方法/计算属性)
static
:调用方法的元类型值(因此必须是动态的)。存在的元类型没有什么区别。
self
:调用该方法的元类型的静态类型的元类型值(即在给定的Self
上调用时T.Type
1}},T : P
是Self
)。当在存在元类型T.self
上调用该方法时,并且不是协议要求,P.Type
等同于Self
(即动态)。当方法是协议要求时,self
等同于直接符合Self
的类型的元类型值。
P
:元类型type(of: self)
的动态元类型。没那么有用。
实例范围(非self
方法/计算属性)
static
:调用该方法的实例。这里没什么惊喜。
self
:调用该方法的实例的静态类型的元类型值(即在给定Self
上调用时T
1}},T : P
是Self
)。在存在 T.self
上调用时,当该方法不是协议要求时,这是实例在装箱时的静态类型。当方法是协议要求时,P
等同于直接符合Self
的类型的元类型值。
P
:调用该方法的实例的动态元类型值。存在感不会有所作为。
由于确定type(of: self)
的价值的因素非常复杂,因此在大多数情况下,我建议您使用Self
和self
。这样,被咬的可能性就大大降低。
此外,在省略
type(of: self)
或self
时使用Self
的类型是否有任何理由,如在返回语句self
中return getName()
函数?
这就是它的方式 - ambiguousName()
仅仅是getName()
的语法糖。如果是self.getName()
的语法糖,它将与实例方法不一致,因为实例方法Self.getName()
是元类型,而Self
是实际的实例 - 和访问其他实例成员更常见,而不是从给定的实例方法中键入成员。
是的,这也困扰着我。我希望对我来说,我认为最奇怪的部分是
self
从type(of: self)
函数调用调用时返回SomeBaseClass.Type
。不应该是&#34;动态类型&#34;仍然是child.littleName()
?
SomeChildClass
的动态类型为child
而不是SomeChildClass.Type
。事实上,我甚至说它可能是一个错误(可以在this great WWDC talk on them提交报告,看看Swift团队对它做了什么)。虽然在任何情况下,元类型的元类型都是无用的,但它的实际值是相当无关紧要的。