Использование протокола как конкретного типа, соответствующего "AnyObject", не поддерживается

Я пытаюсь реализовать простую ситуацию с несколькими делегатами:

protocol Subscribable: class {
    associatedtype Subscriber: AnyObject
    var subscribers: NSHashTable<Subscriber> { get }
}

protocol ControllerSubscriber: class {
    func controllerDidSomething()
}

class Controller: Subscribable {
    typealias Subscriber = ControllerSubscriber
    var subscribers = NSHashTable<Subscriber>.weakObjects()  // Error
}

Ошибка: использование "ControllerSubscriber" в качестве конкретного типа, соответствующего протоколу "AnyObject", не поддерживается.

Мой вопрос:

  • Что означает эта ошибка?
  • Каковы основные понятия, к которым я пытаюсь выполнить, не удается?
  • Почему это "не поддерживается"?

И, конечно, как мне обойти это? В смысле реального решения не работает.

Мне так сложно понять систему Swift generics. Кажется, я постоянно сталкиваюсь с такими непростыми ситуациями. Я просто хочу поставить вещь, соответствующую протоколу, в другую вещь:( Я хотел бы знать, где мое мышление идет не так, поэтому я могу исправить это и никогда больше не видеть эти ошибки.

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

Ответ 1

Вероятно, несправедливо обвинять эту проблему в Swift. Рассуждение о типах кажется некоторым мета-искусством, с которым нам сначала придется привыкнуть (если вы не сидите в комитете по стандартам С++ за последние 30 лет, то есть:-).

Оказывается, ваша проблема связана с вашим выбором NSHashTable в качестве структуры данных для хранения subscribers. Следующее будет скомпилировано с минимальными изменениями:

protocol Subscribable: class {
    associatedtype Subscriber
    var subscribers: [Subscriber?] { get }
}

protocol ControllerSubscriber: class {
    func controllerDidSomething()
}

class Controller: Subscribable {
    typealias Subscriber = ControllerSubscriber
    var subscribers = [Subscriber?]()
}

однако, он испытывает недостаток в семантике weak и пока не очень полезен. Список subscribers отображается как свойство и должен управляться непосредственно клиентом. Кроме того, каждая реализация Subscribable должна реализовать свой собственный механизм уведомления, и вряд ли существует какая-либо логика, которая будет централизована этим подходом. Технически вы можете использовать его следующим образом:

class Controller: Subscribable {
    typealias Subscriber = ControllerSubscriber
    var subscribers = [Subscriber?]()

    func notify() {
        for case let subscriber? in subscribers {
            subscriber.controllerDidSomething()
        }
    }
}

var controller = Controller()

class IWillSubscribe : ControllerSubscriber {
    func controllerDidSomething() {
        print("I got something")
    }
}

controller.subscribers.append(IWillSubscribe())
controller.notify()

но это не очень практично и не очень удобочитаемо. Это было бы приемлемым решением (поскольку оно было единственным) вплоть до Java 7, но даже в Java 8 (а тем более в Swift) мы хотели бы инкапсулировать логику уведомлений в протокол Subscribable как по умолчанию но это будет другой пост.

Поскольку вы выбрали реализацию subscribers как NSHashTable (возможно, существует причина ARC для желающих получить слабые ссылки здесь), кажется, что есть некоторые трюки Objective-C. После много экспериментов (и, наконец, найдя четвертый ответ на этот вопрос, я получил следующее:

protocol Subscribable: class {
    associatedtype Subscriber : AnyObject
    var subscribers: NSHashTable<Subscriber> { get }
}

@objc protocol ControllerSubscriber: class {
    func controllerDidSomething()
}

class Controller: Subscribable {
    typealias Subscriber = ControllerSubscriber
    var subscribers = NSHashTable<Subscriber>.weakObjects()

    func notify() {
        for subscriber in subscribers.allObjects {
            subscriber.controllerDidSomething()
        }
    }
}

var controller = Controller()

class IWillSubscribe : ControllerSubscriber {
    func controllerDidSomething() {
        print("I got something")
    }
}

let iDoSubscribe = IWillSubscribe()
controller.subscribers.add(iDoSubscribe)
controller.notify()

который практически идентичен вашему оригиналу (с некоторыми доказательствами вокруг него). Похоже, что Objective-C @protocol не совсем такие же, как Swift protocol s, но Swift действительно может это сделать.

В этом довольно много тонкости, но только allObjects работает без стирания типа, ваш верный objectEnumerator возвращает Any?, и это глупое животное, чтобы получить что-либо. Также обратите внимание, что

let iDoSubscribe = IWillSubscribe()

является инструментальным. Сначала я попробовал

controller.subscribers.add(IWillSubscribe())

который фактически добавил что-то в count из subscribers, но ушел с любой попыткой итерации (как следует ожидать от ссылки weak, которая не упоминается нигде).

Очень поздний ответ, который уже слишком длинный, просто чтобы доказать, что это все еще проблема, даже с Swift 3. Возможно, это улучшится после этот билет Jira разрешен.