Ответ 1
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 вызывает методы расширения протокола.
Глядя на ИК, подпись для 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
и 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 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
of A.self
и self
которые являются экземпляром B
экзистенциалам
Все становится намного сложнее, когда вы начинаете работать с экзистенциалами (см. Этот великолепный доклад 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()
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
(например, P.Type
) экзистенциальный контейнер просто хранит метатип вместе с таблицей-свидетелем протокола. Этот метатип затем используется как для Self
и self
в вызове статического метода в 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
(B
не получает свою собственную таблицу следящего протокола для P
соответствия). Поэтому " Self
- это " A.self
. Это точно такая же история с методами экземпляра.
Это чаще всего встречается в универсальных функциях, которые динамически распределяют требования к протоколу через таблицу свидетелей протокола *. Например:
func foo<T : P>(t: T) {
t.testBigSelf() // dispatch dynamically via A PWT for conformance to P.
}
foo(t: A()) // A
foo(t: B()) // A
Не имеет значения, является ли экземпляр A
или B
передается в - testBigSelf()
отправляется с помощью A
PWT на соответствие P
, поэтому Self
это A.self
.
(* Хотя компилятор может оптимизировать, генерируя специализированные версии универсальных функций, это не меняет наблюдаемого поведения.)
Заключение
По большей части тип Self
определяется статическим типом того, к чему вызывается метод. Значение self
- это просто само значение, к которому вызывается метод (метатип для статического метода, экземпляр для метода экземпляра), передаваемое как неявный параметр.
Полное расстройство того, что мы обнаружили, заключается в том, что значения self
, Self
& type(of: self)
в расширениях протокола:
-
Статическая область (
static
методы/вычисляемые свойства)-
self
: значение метатипа, для которого вызывается метод (поэтому оно должно быть динамическим). Существующие метатипы не имеют значения. -
Self
: значение метатипа для статического типа метатипа, к которому вызывается метод (т.T.Type
При вызове для данного типаT.Type
гдеT: P
,Self
- этоT.self
). Когда метод вызывается для экзистенциальногоP.Type
и не является требованием протокола,Self
эквивалентенself
(т.е. является динамическим). Когда метод является требованием протокола,Self
эквивалентен значению метатипа типа, который непосредственно соответствуетP
-
type(of: self)
: динамический метатип метатипаself
. Не так полезно.
-
-
Область действия экземпляра (non-
static
методы/вычисляемые свойства)-
self
: экземпляр, на котором вызывается метод. Здесь нет сюрпризов. -
Self
: значение метатипа для статического типа экземпляра, к которому вызывается метод (т.T.self
При вызове для заданногоT
где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
при вызове изchild.littleName()
функцииchild.littleName()
.SomeChildClass
"динамический тип" не должен бытьSomeChildClass
?
Да, это меня тоже озадачивает. Я ожидаю, что динамический тип child
будет SomeChildClass.Type
а не SomeBaseClass.Type
. На самом деле, я бы даже сказал, что это может быть ошибка (не стесняйтесь подать отчет на bugs.swift.org, чтобы узнать, что команда Swift сделает из этого). Хотя в любом случае метатип метатипа довольно бесполезен, поэтому его фактическое значение довольно несущественно.