Swift родовое принуждение непонимание

я использую сигналы библиотека.

допустим, я определил протокол BaseProtocol и ChildClass что соответствует BaseProtocol.

protocol BaseProtocol {}
class ChildClass: BaseProtocol {}

теперь я хочу хранить сигналы, такие как:

var signals: Array<Signal<BaseProtocol>> = []
let signalOfChild = Signal<ChildClass>()
signals.append(signalOfChild)

я получаю ошибку:

Swift generic error

но я могу написать следующие строки без какой-либо ошибки компилятора:

var arrays = Array<Array<BaseProtocol>>()
let arrayOfChild = Array<ChildClass>()
arrays.append(arrayOfChild)

enter image description here

Итак, в чем разница между общим массивом Swift и общий сигнал?

1 ответов


разница в том, что ArraySet и Dictionary) получите специальное обращение от компилятора, позволяющее ковариацию (я вхожу в это немного более подробно в этом Q & A).

однако произвольные общие типы инвариант, что означает X<T> является полностью несвязанным типом с X<U> если T != U - любое другое отношение ввода между T и U (например, подтипирование) не имеет значения. Распространяться ваше дело,Signal<ChildClass> и Signal<BaseProtocol> являются несвязанными типами, хотя ChildClass является подтипом BaseProtocol (см. Также это Q & A).

одной из причин этого является то, что он полностью нарушит общие ссылочные типы, которые определяют контравариантные вещи (такие как параметры функций и задатчики свойств) по отношению к T.

например, если бы вы реализовали Signal as:

class Signal<T> {

    var t: T

    init(t: T) {
        self.t = t
    }
}

если вы получили возможность скажи:

let signalInt = Signal(t: 5)
let signalAny: Signal<Any> = signalInt

вы могли бы тогда сказать:

signalAny.t = "wassup" // assigning a String to a Signal<Int>'s `t` property.

что это полностью неправильно, так как вы не можете назначить String до Int собственность.

причина, по которой такие вещи безопасны для Array - это тип значения – таким образом, когда вы:

let intArray = [2, 3, 4]

var anyArray : [Any] = intArray
anyArray.append("wassup")

нет проблем, как anyArray это скопировать of intArray – таким образом, контравариантность из append(_:) не проблема.

однако это не может быть применено к произвольным типам общих значений, поскольку типы значений могут содержать любое количество общих ссылочных типов, что ведет нас назад по опасному пути разрешения незаконной операции для общих ссылочных типов, которые определяют контравариантные вещи.


как говорит Роб в его ответе решение для ссылочных типов, если вам нужно поддерживать ссылку на тот же базовый экземпляр, заключается в использовании тип-ластик.

если мы рассмотрим пример:

protocol BaseProtocol {}
class ChildClass: BaseProtocol {}
class AnotherChild : BaseProtocol {}

class Signal<T> {
    var t: T

    init(t: T) {
        self.t = t
    }
}

let childSignal = Signal(t: ChildClass())
let anotherSignal = Signal(t: AnotherChild())

тип-ластик, который обертывает любой Signal<T> случае T соответствует BaseProtocol может выглядеть так:

struct AnyBaseProtocolSignal {
    private let _t: () -> BaseProtocol

    var t: BaseProtocol { return _t() }

    init<T : BaseProtocol>(_ base: Signal<T>) {
        _t = { base.t }
    }
}

// ...

let signals = [AnyBaseProtocolSignal(childSignal), AnyBaseProtocolSignal(anotherSignal)]

это теперь позволяет нам говорить о разнородных видах Signal здесь T это какой-то тип, что соответствует BaseProtocol.

однако одна проблема с этой оберткой заключается в том, что мы ограничены разговорами в терминах BaseProtocol. Что, если бы мы ... --39--> и нужен стиралка для Signal случаи, когда T соответствует AnotherProtocol?

одним из решений этого является передача transform функция для типа-ластика, позволяющая нам выполнять произвольный апкаст.

struct AnySignal<T> {
    private let _t: () -> T

    var t: T { return _t() }

    init<U>(_ base: Signal<U>, transform: @escaping (U) -> T) {
        _t = { transform(base.t) }
    }
}

теперь мы можем говорить в терминах гетерогенных типов Signal здесь T это какой - то тип, который конвертируется в какой-то U, который указывается при создании типа-ластик.

let signals: [AnySignal<BaseProtocol>] = [
    AnySignal(childSignal, transform: {  }),
    AnySignal(anotherSignal, transform: {  })
    // or AnySignal(childSignal, transform: {  as BaseProtocol })
    // to be explicit.
]

однако, прохождение того же самого transform функция для каждого инициализатора немного громоздкая.

в Swift 3.1 (доступно с Xcode 8.3 beta) вы можете снять эту нагрузку с вызывающего абонента, определив свой собственный инициализатор специально для BaseProtocol в продолжение:

extension AnySignal where T == BaseProtocol {

    init<U : BaseProtocol>(_ base: Signal<U>) {
        self.init(base, transform: {  })
    }
}

(и повторите для любых других протоколов, которые вы хотите преобразовать в)

теперь вы можете просто сказать:

let signals: [AnySignal<BaseProtocol>] = [
    AnySignal(childSignal),
    AnySignal(anotherSignal)
]

(вы можете фактически удалить явную аннотацию типа для массива здесь, и компилятор сделает вывод, что это [AnySignal<BaseProtocol>] - но если вы собираетесь разрешить больше инициализаторов удобства, я бы оставил его явным)


раствор для типов значений и ссылочных типов, где вы хотите конкретно создать новый экземпляр, чтобы выполнить преобразование с Signal<T> (где T соответствует BaseProtocol) к Signal<BaseProtocol>.

в Swift 3.1 вы можете сделать это, определив инициализатор (удобство) в расширении для Signal типы, где T == BaseProtocol:

extension Signal where T == BaseProtocol {
    convenience init<T : BaseProtocol>(other: Signal<T>) {
        self.init(t: other.t)
    }
}

// ...    

let signals: [Signal<BaseProtocol>] = [
    Signal(other: childSignal),
    Signal(other: anotherSignal)
]

Pre Swift 3.1, это может быть достигнуто с помощью метода экземпляра:

extension Signal where T : BaseProtocol {
    func asBaseProtocol() -> Signal<BaseProtocol> {
        return Signal<BaseProtocol>(t: t)
    }
}

// ...

let signals: [Signal<BaseProtocol>] = [
    childSignal.asBaseProtocol(),
    anotherSignal.asBaseProtocol()
]

процедура в обоих случаях будет аналогичной для struct.