Это довольно интересно. Короче говоря (хорошо, может быть, не это коротко) — это преднамеренная сторона effect of #12174, что позволяет использовать методы расширения протокола, которые возвращают Self
, чтобы удовлетворить требования протокола для нефинальных классов, а это означает, что теперь вы можете сказать это в 4.1:
protocol P {
init()
static func f() -> Self
}
extension P {
static func f() -> Self {
return self.init()
}
}
class C : P {
required init() {}
}
В Swift 4.0.3 вы получите запутанную ошибку в реализации расширения f()
, говоря:
Метод «f()
» в неконечном классе «C» должен возвращать Self
, чтобы соответствовать протоколу «P
»
Как это относится к вашему примеру? Что ж, рассмотрим этот несколько похожий пример:
class C {}
class D : C {}
protocol P {
func copy() -> Self
}
extension P where Self == C {
func copy() -> C {
return C()
}
}
extension C : P {}
let d: P = D()
print(d.copy()) // C (assuming we could actually compile it)
Если бы Swift позволил реализации расширения протокола copy()
удовлетворить требование, мы создали бы экземпляры C
даже при вызове экземпляра D
, нарушив контракт протокола. Поэтому Swift 4.1 делает соответствие незаконным (чтобы сделать соответствие в первом примере допустимым), и делает это независимо от того, есть ли в игре Self
возвратов.
Что мы на самом деле хотим выразить с помощью расширения, так это то, что Self
должен быть или наследоваться от C
, что заставляет нас рассматривать случай, когда подкласс использует соответствие.
В вашем примере это будет выглядеть так:
protocol Typographable {
func setTypography(_ typography: Typography)
}
extension UILabel: Typographable {}
extension Typographable where Self : UILabel {
func setTypography(_ typography: Typography) {
self.font = typography.font
self.textColor = typography.textColor
self.textAlignment = typography.textAlignment
self.numberOfLines = typography.numberOfLines
}
}
который, как говорит Мартин, отлично компилируется в Swift 4.1. Хотя, как говорит Мартин, это можно переписать гораздо проще:
protocol Typographable {
func setTypography(_ typography: Typography)
}
extension UILabel : Typographable {
func setTypography(_ typography: Typography) {
self.font = typography.font
self.textColor = typography.textColor
self.textAlignment = typography.textAlignment
self.numberOfLines = typography.numberOfLines
}
}
Если говорить более подробно, то #12174 позволяет распространять неявный Self
параметр через следящие (соответствующие реализации) переходники. Это достигается путем добавления универсального заполнителя к этому преобразователь, ограниченному соответствующим классом.
Итак, для такого соответствия:
class C {}
protocol P {
func foo()
}
extension P {
func foo() {}
}
extension C : P {}
В Swift 4.0.3 таблица-свидетель протокола C
(у меня есть небольшой бред о PWT здесь, который может быть полезен для их понимания ) содержит запись в преобразователь с подписью:
(C) -> Void
(обратите внимание, что в отрывке, на который я ссылаюсь, я пропускаю детали наличия переходников и просто говорю, что PWT содержит запись о реализации, используемой для удовлетворения требования. Семантика, по большей части, одна и та же хотя)
Однако в Swift 4.1 подпись преобразователь теперь выглядит так:
<Self : C>(Self) -> Void
Почему? Потому что это позволяет нам распространять информацию о типе для Self
, что позволяет нам сохранить динамический тип экземпляра для создания в первом примере (и, таким образом, сделать его допустимым).
Теперь для расширения, которое выглядит так:
extension P where Self == C {
func foo() {}
}
есть несоответствие между сигнатурой реализации расширения, (C) -> Void
, и сигнатурой преобразователя, <Self : C>(Self) -> Void
. Таким образом, компилятор отклоняет соответствие (возможно, это слишком строго, поскольку Self
является подтипом C
, и мы могли бы применить здесь контравариантность, но это текущее поведение).
Однако, если у нас есть расширение:
extension P where Self : C {
func foo() {}
}
все снова в порядке, так как обе подписи теперь <Self : C>(Self) -> Void
.
Одна интересная вещь, которую следует отметить в отношении #12174, заключается в том, что при требования содержат связанные типы. Итак, это работает:
class C {}
protocol P {
associatedtype T
func foo() -> T
}
extension P where Self == C {
func foo() {} // T is inferred to be Void for C.
}
extension C : P {}
Но вам, вероятно, не следует прибегать к таким ужасным обходным путям. Просто измените ограничение расширения протокола на where Self : C
.
person
Hamish
schedule
12.04.2018
extension UILabel: Typographable { ... }
- person Martin R   schedule 12.04.2018XXLabel
, у которого другая реализацияsetTypography
. если я используюextension UILabel: Typographable { ... }
, мне нужно написать что-то вродеif self is XXLabel
, что не элегантно. - person JIE WANG   schedule 12.04.2018extension Typographable where Self: UILabel { ... }
заставил бы его скомпилировать - person Martin R   schedule 12.04.2018XXLabel
наследует отUILabel
? В этом случае обратите внимание, что при согласованииUILabel
сTypographable
динамические отправки наsetTypography
(при вызове значения, типизированного протоколом) будут отправляться на расширениеUILabel
, а не на расширениеTypographable
. Таким образом, ваш подход не будет полностью работать и в Swift 4.0.3. - person Hamish   schedule 13.04.2018