为什么Self和self有时会在静态函数中引用不同的类型?

时间:2017-02-04 07:42:29

标签: swift swift3 swift-protocols

最近,我一直在使用Swift开发多个面向协议的应用程序框架,并注意到协议扩展中有一些(看似)奇怪的静态函数行为,特别是从元类型调用扩展函数的地方。

我最初发现这些行为的方法是对一个错误进行故障排除,其中一个对象的类型以一种看似不可能的方式发生了变化。我追溯了问题,并最终确定这是因为在静态函数中,Selfself可以保留不同的类型(注意:我已经采用了调用这些" 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,但现在我认为情况并非如此,因为Selfself已被证明会产生不同的结果一些场景。

此外,在省略selfSelf时使用self的类型是否有任何理由,如在返回语句return getName()ambiguousName()函数?

对我来说,我认为最奇怪的部分是type(of: self)SomeBaseClass.Type函数调用调用时返回child.littleName()。不应该是"动态类型"仍然是SomeChildClass

1 个答案:

答案 0 :(得分:10)

TL; DR

协议扩展中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 }}

因此,这意味着可以传递不同的元类型来表示Selfself

了解如何调用此方法:

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. )的元类型传入了ASelf的元类型(存储在B中)正在传递t。如果您认为self的返回类型(如果在createWithBigSelf()类型的值上调用)将为A.Type,那么这实际上非常有意义。因此ASelf,而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实例。

Existentials

当你开始使用存在时,事情变得更加复杂<(> {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扩展中调用静态方法时,此元类型将用于 Selfself,此时该方法不是#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一致性)。因此PSelf。它与实例方法完全相同。

这通常出现在通用函数中,它通过协议见证表*动态调度协议要求。例如:

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调度的,以确保一致性至APSelf

(*虽然编译器可以通过生成通用函数的专用版本进行优化,但这并不会改变观察到的行为。)

结论

在大多数情况下,A.self的类型由静态类型决定,无论调用哪种方法。 Self的值只是调用该方法的值本身(静态方法的元类型,实例方法的实例),作为隐式参数传入。 / p>

我们发现的完整细分是selfself&amp;的价值。协议扩展中的Self是:

  • 静态范围(type(of: self)方法/计算属性)

    • static:调用方法的元类型值(因此必须是动态的)。存在的元类型没有什么区别。

    • self:调用该方法的元类型的静态类型的元类型值(即在给定的Self上调用时T.Type 1}},T : PSelf)。当在存在元类型T.self上调用该方法时,并且不是协议要求,P.Type等同于Self(即动态)。当方法协议要求时,self等同于直接符合Self的类型的元类型值。

    • P:元类型type(of: self)的动态元类型。没那么有用。

  • 实例范围(非self方法/计算属性)

    • static:调用该方法的实例。这里没什么惊喜。

    • self:调用该方法的实例的静态类型的元类型值(即在给定Self上调用时T 1}},T : PSelf)。在存在 T.self上调用时,当该方法不是协议要求时,这是实例在装箱时的静态类型。当方法协议要求时,P等同于直接符合Self的类型的元类型值。

    • P:调用该方法的实例的动态元类型值。存在感不会有所作为。

由于确定type(of: self)的价值的因素非常复杂,因此在大多数情况下,我建议您使用Selfself。这样,被咬的可能性就大大降低。

回答您的其他问题

  

此外,在省略type(of: self)self时使用Self的类型是否有任何理由,如在返回语句selfreturn getName()函数?

这就是它的方式 - ambiguousName()仅仅是getName()的语法糖。如果是self.getName()的语法糖,它将与实例方法不一致,因为实例方法Self.getName()是元类型,而Self是实际的实例 - 和访问其他实例成员更常见,而不是从给定的实例方法中键入成员。

  

对我来说,我认为最奇怪的部分是selftype(of: self)函数调用调用时返回SomeBaseClass.Type。不应该是&#34;动态类型&#34;仍然是child.littleName()

是的,这也困扰着我。我希望SomeChildClass的动态类型为child而不是SomeChildClass.Type。事实上,我甚至说它可能是一个错误(可以在this great WWDC talk on them提交报告,看看Swift团队对它做了什么)。虽然在任何情况下,元类型的元类型都是无用的,但它的实际值是相当无关紧要的。