本文介绍了为什么Self和self有时在静态函数中引用不同的类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Recently I have been developing multiple heavily protocol-oriented application frameworks with Swift and noticed a few (seemingly) odd behaviors with static functions in protocol extensions, specifically where the extension functions are invoked from metatypes.

我最初发现这些行为的方式是对一个错误进行故障排除,该错误中对象的类型以一种看似不可能的方式改变了.我找到问题所在,并最终确定这是因为在静态函数中,Selfself可能具有不同的类型(请注意:我将其称为"Big S Self"和"Little S self"分别).我将通过一个在操场上鞭打的东西来演示这个简单的例子:

The way I initially discovered these behaviors was in troubleshooting a bug where the type of an object changed in a seemingly impossible way. I traced the problem down and eventually determined that it is because in a static function, Self and self can potentially hold different types (note: I've taken to calling these "Big S Self" and "Little s self" respectively). I'll demonstrate this with a bare bones example from something I whipped up in a Playground:

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"

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

It can be seen that when static functions are invoked from a metatype, the result may differ if that metatype is assigned to a variable with a declared type of a parent class's metatype.

我的问题是Self如何知道它是什么类型?那么self如何知道它是什么类型?对于我来说,为什么self仍然可以在静态函数中访问,这并没有意义,因为首先没有实例.我本以为应该只使用Self,但是现在我不是这样了,因为事实证明Selfself在某些情况下会产生不同的结果.

My question is how does Self know what type it is? How then does self know what type it is? It didn't make sense to me why self was even accessible in a static function anyway, since there is no instance in the first place. I would have thought that one should use Self exclusively, but now I'm thinking this isn't the case since Self and self have proven to produce different results in some scenarios.

此外,是否有任何原因省略Selfself时使用self的类型,如ambiguousName()函数中的return语句return getName()一样?

Additionally, is there any reason why self's type is used when either Self or self is omitted, as in the return statement return getName() in the ambiguousName() function?

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

For me, I think the weirdest part is when type(of: self) returns SomeBaseClass.Type when called from the child.littleName() function invocation. Shouldn't the "dynamic type" still be of SomeChildClass?

推荐答案

TL; DR

协议扩展中的Self的值由一组复杂的因素决定.在静态级别使用self或在实例级别使用type(of: self)代替Self几乎总是可取的.这样可以确保您始终使用调用该方法的动态类型,以防止出现奇怪的意外情况.

TL;DR

The value of Self in a protocol extension is determined by a complex set of factors. It's almost always preferable to use self at static level, or type(of: self) at instance level in place of Self. This ensures that you're always working with the dynamic type that the method is called on, preventing weird surprises.

首先让我们简化一下示例.

First of all let's simplify your example down a bit.

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的新实例.

We can see that using Self will return a new instance of A, whereas using self will return a new instance of B.

要了解为什么会这样,我们需要准确了解Swift如何调用协议扩展方法.

To understand just why this is the case, we need to understand exactly how Swift calls protocol extension methods.

从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 {

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

4 invisible arguments are generated by the compiler – one for a pointer for the return, one for the protocol witness table of the conforming type, and two swift.type* arguments to represent self and Self.

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

This therefore means that different metatypes can be passed to represent self or Self.

查看此方法的调用方式:

Looking at how this method is called:

  // 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,那么这实际上很有意义.因此SelfA.self,而self仍然是B.self.

We can see that A's metatype is getting passed in for Self, and B's metatype (stored in t) is getting passed in for self. This actually makes quite a lot of sense if you consider that the return type of createWithBigSelf() if called on a value of type A.Type will be A. Thus Self is A.self, while self remains B.self.

然后,作为一般规则,Self的类型由调用该方法的事物的静态类型确定. (因此,在您的情况下,当您呼叫bigName()时,Self.getName()正在呼叫SomeBaseClass.self上的getName().)

As a general rule then, the type of Self is determined by the static type of the thing that the method is called on. (Therefore in your case when you call bigName(), Self.getName() is calling getName() on SomeBaseClass.self).

这也适用于实例方法,例如:

This also holds for instance methods, for example:

// ...

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

使用A.selfSelfB的实例self调用方法.

The methods are called with a Self of A.self, and a self that's an instance of B.

当您开始使用存在性时,事情变得更复杂了(请参阅关于他们的WWDC精彩演讲).如果直接调用扩展方法(即它们不是协议要求),则对于实例方法,Self的值由装箱时的值的静态类型确定在现有容器中,例如:

Things get much more complicated when you start working with existentials (see this great WWDC talk on them). If you're calling the extension methods directly (i.e they aren't protocol requirements), then for instance methods, the value of Self is determined by the static type of the value when you box it in the existential container, for example:

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,从而导致上面显示的有些令人惊讶的行为.

What happens is that the existential container also stores the metatype of the value (along with the value buffer and protocol and value witness tables), which is taken from its static type at the time of boxing. This metatype is then used for Self, leading to the somewhat surprising behaviour demonstrated above.

对于具有元类型的存在性(例如P.Type),存在性容器仅将元类型与协议见证表一起存储.然后,当P扩展名中的静态方法调用不是协议要求时,该元类型将同时用于 Selfself.

With metatype existentials (e.g P.Type), the existential container just stores the metatype along with the protocol witness table. This metatype is then used for both Self and self in a call to a static method in a P extension, when that method isn't a protocol requirement.

协议要求实现的方法将通过协议见证表动态分配给符合该协议的类型.在这种情况下,Self的值将替换为直接符合协议的类型(尽管我不太确定为什么编译器会这样做).

Methods that are implementations of protocol requirements will be dispatched to dynamically via the protocol witness table for the type conforming to that protocol. In this case, the value of Self is replaced by the type that directly conforms to the protocol (although I'm not entirely sure why the compiler does this).

例如:

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

在两种情况下,通过A的协议见证表动态调度对testBigSelf()的调用,以符合P(B (与P保持一致).因此SelfA.self.实例方法完全是一样的故事.

In both cases, the call to testBigSelf() is dispatched dynamically via A's protocol witness table for conformance to P (B doesn't get its own protocol witness table for P conformance). Therefore Self is A.self. It's exactly the same story with instance methods.

这是最常见的通用函数,它们通过协议见证表*动态分配协议需求.例如:

This most commonly comes up in generic functions, which dispatch protocol requirements dynamically via the protocol witness table*. For example:

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

传入AB的实例都没有关系–通过A的PWT调度testBigSelf()是否符合P,因此SelfA.self

It doesn't matter whether an instance of A or B is passed in – testBigSelf() is dispatched via A's PWT for conformance to P, therefore Self is A.self.

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

(* Although the compiler can optimise by generating specialised versions of generic functions, this doesn't change the observed behaviour.)

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

For the most part, the type of Self is determined by the static type of whatever the method is called on. The value of self is simply the value itself that the method is called on (a metatype for a static method, an instance for an instance method), passed in as an implicit parameter.

我们发现的全部内容是selfSelf&协议扩展中的type(of: self)是:

The full breakdown of what we discovered is that the values of self, Self & type(of: self) in protocol extensions are:

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

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

  • self: The metatype value which the method is called on (therefore must be dynamic). Existential metatypes don't make a difference.

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

Self: The metatype value for the static type of the metatype that the method is called on (i.e when called on a given T.Type where T : P, Self is T.self). When the method is called on an existential metatype P.Type, and isn't a protocol requirement, Self is equivalent to self (i.e is dynamic). When the method is a protocol requirement, Self is equivalent to the metatype value of the type that directly conforms to P.

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

type(of: self): The dynamic metatype of the metatype self. Not that useful.

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

Instance scope (non-static methods / computed properties)

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

  • self: The instance that the method is called on. No surprises here.

Self:调用该方法的实例的 static 类型的元类型值(即,在给定的T上调用时,其中T : P,是T.self).当在 existential P上调用时,如果该方法不是协议要求,则这是实例装箱时的静态类型.当方法的协议要求时,Self等同于直接符合P的类型的元类型值.

Self: The metatype value for the static type of the instance that the method is called on (i.e when called on a given T where T : P, Self is T.self). When called on an existential P, when the method isn't a protocol requirement, this is the static type of the instance when it was boxed. When the method is a protocol requirement, Self is equivalent to the metatype value of the type that directly conforms to P.

type(of: self):调用该方法的实例的 dynamic 元类型值.存在并不重要.

type(of: self): The dynamic metatype value for the instance that the method is called on. Existentials don't make a difference.

由于决定Self的值的因素非常复杂,在大多数情况下,我建议使用selftype(of: self)代替.这样,被咬的机会就少得多.

Due to the sheer complexity of factors that determine what the value of Self is, in most cases I would recommend using self and type(of: self) instead. That way there's far less chance of being bitten.

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

That's just the way it is – getName() is merely syntactic sugar for self.getName(). It would be inconsistent with instance methods if were syntactic sugar for Self.getName(), as in instance methods Self is a metatype, whereas self is the actual instance – and it's much more common to be accessing other instance members, rather than type members from a given instance method.

是的,这也使我感到困惑.我希望child的动态类型是SomeChildClass.Type而不是SomeBaseClass.Type.实际上,我什至可以说它可能是一个错误(可以在错误中提交报告. swift.org 查看Swift团队的工作).尽管在任何情况下,元类型的元类型都没有用,所以它的实际值是无关紧要的.

Yeah, that puzzles me too. I would expect the dynamic type of child to be SomeChildClass.Type rather than SomeBaseClass.Type. In fact, I'd even go so far as it say it might be a bug (feel free to file a report at bugs.swift.org to see what the Swift team make of it). Although in any case, the metatype of a metatype is pretty useless, so it's actual value is fairly inconsequential.

这篇关于为什么Self和self有时在静态函数中引用不同的类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 06:43