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

Mic*_*rre 12 swift swift-protocols swift3

最近,我一直在使用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"
Run Code Online (Sandbox Code Playgroud)

可以看出,当从元类型调用静态函数时,如果将元类型分配给具有父类的元类型的声明类型的变量,则结果可能不同.

我的问题是如何Self知道它是什么类型的?怎么self知道它是什么类型的?这对我来说没有意义,为什么self甚至可以在静态函数中访问,因为首先没有实例.我本以为应该Self专门使用,但现在我认为情况并非如此,Self并且self已证明在某些情况下会产生不同的结果.

另外,有没有任何理由的理由self的类型时使用两种Selfself省略,作为return语句return getName()ambiguousName()功能?

对我来说,我认为最奇怪的部分是从函数调用中调用时的type(of: self)返回.不应该是"动态类型" 吗?SomeBaseClass.Typechild.littleName()SomeChildClass

Ham*_*ish 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
Run Code Online (Sandbox Code Playgroud)

我们可以看到using Self将返回一个新实例A,而using 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 {
Run Code Online (Sandbox Code Playgroud)

(签名createWithLittleSelf()几乎相同.)

编译器生成4个不可见的参数 - 一个用于返回的指针,一个用于符合类型的协议见证表,两个swift.type*参数用于表示selfSelf.

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

看看如何调用此方法:

  // 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.
  )
Run Code Online (Sandbox Code Playgroud)

我们可以看到,它A的元类型正在传入Self,并且其元B类型(存储在t)中传入了self.如果您认为在类型createWithBigSelf()值上调用if 的返回类型A.Type将是非常有意义的话,这实际上非常有意义A.因此SelfIS 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
Run Code Online (Sandbox Code Playgroud)

该方法被称为带SelfA.self,和self这的一个实例B.


Existentials

当你开始使用存在时,事情会变得复杂得多(参见这篇关于它们的伟大的WWDC谈话).如果您直接调用扩展方法(即它们不是协议要求),那么对于实例方法,Self当您在存在容器中将其置入时,值的静态类型确定值,例如:

let b: A = B()
let p: P = b // metatype of b stored as A.self.

print(p.createWithBigSelf()) // A()
print(p.createWithLittleSelf()) // B()
Run Code Online (Sandbox Code Playgroud)

let b = B()
let p: P = b // metatype of b stored as B.self.

print(p.createWithBigSelf()) // B()
print(p.createWithLittleSelf()) // B()
Run Code Online (Sandbox Code Playgroud)

发生的事情是存在容器还存储值的元类型(以及值缓冲区和协议和值见证表),它是在装箱时从其静态类型中获取的.然后使用该元型Self,导致上面说明的有些令人惊讶的行为.

对于元类型存在(例如P.Type),存在容器只存储元类型以及协议见证表.然后,该元类型被用于两个 Selfself在一个静态方法的调用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
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,调用testBigSelf()都是通过A协议见证表动态调度的,以便符合P(B 不会获得自己的协议见证表以确保P一致性).因此SelfA.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
Run Code Online (Sandbox Code Playgroud)

无论是传入的实例A还是B传入的实例- testBigSelf()都是通过APWT发送的P,因为它Self是一致的A.self.

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


结论

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

什么,我们发现了完整的故障是的值self,Selftype(of: self)在协议扩展是:

  • 静态范围(static方法/计算属性)

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

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

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

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

    • self:调用该方法的实例.这里没有惊喜.

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

    • type(of: self):调用该方法的实例的动态元类型值.存在并没有什么不同.

由于决定价值的因素的复杂性Self,在大多数情况下我会建议使用selftype(of: self)不是.这样,被咬的可能性就大大降低.


回答你的其他问题

另外,有没有任何理由的理由self的类型时使用两种Selfself省略,作为return语句return getName()ambiguousName()功能?

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

对我来说,我认为最奇怪的部分是从函数调用中调用时的type(of: self)返回.不应该是"动态类型" 吗?SomeBaseClass.Typechild.littleName()SomeChildClass

是的,这也让我很困惑.我期望的动态类型childSomeChildClass.Type不是SomeBaseClass.Type.事实上,我甚至会说它可能是一个错误(请随时在bugs.swift.org上提交一份报告,看看Swift团队对它做了什么).虽然在任何情况下,元类型的元类型都是无用的,但它的实际值是相当无关紧要的.

  • 关于您在IR中挖掘的内容的重要信息.这是我一直在寻找的东西.我确实在Jira中提交了一个错误,供Swift团队查看.我会看看他们是否会提出任何回应.希望有一个具体的答案.如果/当我发现时,我会在此发表评论,以便您可以将其附加到您已经非常有用的答案中.再次感谢您为此回复付出的所有努力! (2认同)