У меня есть следующие протоколы:
protocol ProtoAInput {
func funcA()
}
protocol ProtoA {
var input: ProtoAInput { get }
}
protocol ProtoBInput {
func funcB()
}
protocol ProtoB {
var input: ProtoBInput { get }
}
Я хочу, чтобы мой StructC
соответствовал обоим протоколам ProtoA
и ProtoB
только с одним свойством input
. Что само по себе является просто ссылкой на self
, так как StructC
также реализует ProtoAInput
и ProtoBInput
в отдельном расширении.
struct StructC: ProtoA, ProtoB {
var input: ProtoAInput & ProtoBInput { return self }
}
extension StructC: ProtoAInput {
func funcA() { print("funcA") }
}
extension StructC: ProtoBInput {
func funcB() { print("funcB") }
}
let s = StructC()
s.funcA()
s.funcB()
Компилятор Swift 5.3 не может собрать этот код со следующими ошибками:
Type 'StructC' does not conform to protocol 'ProtoA'
Type 'StructC' does not conform to protocol 'ProtoB'
Существуют ли какие-либо правила компиляции, которые этот код нарушает? Я не понимаю, почему я не могу иметь здесь переменную input
, которая одновременно соответствует обоим протоколам.
Да, я читал про композицию протоколов, но все же не могу понять, почему такой код не поддерживается, есть ли в нем какая-то неопределенность, с которой компилятор не справляется?
struct StructC: ProtoA, ProtoB { var input: ProtoAInput & ProtoBInput { return self } }
Вместо вышеуказанного вам нужно что-то вроде следующего
extension ProtoA where Self == StructC {
var input: ProtoAInput { self }
}
extension ProtoB where Self == StructC {
var input: ProtoBInput { self }
}
struct StructC: ProtoA, ProtoB {
}
Протестировано и работает с Xcode 12.1 / iOS 14.1
Это SR-522 (протокольные функции не могут иметь ковариантный возврат). Swift просто не поддерживает это. Если для протокола требуется тип, то это именно тот тип, который требуется. То же самое верно и для подклассов:
class ProtoAInput {}
class ProtoAInputSub: ProtoAInput {}
protocol ProtoA {
var input: ProtoAInput { get } // <=== requires superclass
}
struct StructA: ProtoA {
var input: ProtoAInputSub // <=== So can't use a subclass here
}
Теоретически компилятор может поддерживать это, если требуемым свойством является только get
. Если вы добавили set
, это не сработает. Но это не работает сегодня ни для того, ни для другого.
Я подозреваю, что поддержка этого будет сложной из-за макетов экзистенциальных контейнеров. Вы не можете просто поставить P & Q
в те же места, что и Q
(с точки зрения реализации Swift, а не с точки зрения теории типов). Смещения в таблице-свидетеле будут неправильными. Кажется, что это можно реализовать, но я понимаю, почему это было бы сложно, не жертвуя производительностью за счет дополнительной косвенности.
Есть много вещей, которые могла бы поддерживать система типов Swift, но сегодня она просто не поддерживает. Во многих случаях это не потому, что это невозможно или команда Swift отвергла это. Они просто не поддерживаются компилятором.
Протоколы так не работают, читайте docs.swift.org/swift-book/LanguageGuide/Protocols.html.