不支持使用协议作为符合'AnyObject'的具体类型

tom*_*dey 10 generics macos ios swift

我正在尝试实现一个简单的多委托情况:

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
}
Run Code Online (Sandbox Code Playgroud)

错误:不支持使用'ControllerSubscriber'作为符合协议'AnyObject'的具体类型.

我的问题是:

  • 这个错误究竟意味着什么?
  • 我试图做的事情的基本概念是什么?
  • 为什么这"不受支持"?

当然,我该如何解决这个问题?从实际的解决方案而不是解决方案.

我很难理解Swift的泛型系统.我似乎经常遇到这样看似简单的情况.我只是想把一个符合协议的东西放到另一个东西:(.我想知道我的想法出错了所以我可以解决它,而不必再看到这些错误.

这个相关的问题,但请注意答案只给出解决方法,没有解释或解决方案.

Pat*_*tru 3

将这个问题归咎于 Swift 可能不太公平。关于类型的推理似乎是我们首先必须习惯的一些元艺术(除非您在过去 30 年里一直担任 C++ 标准委员会的职务:-)。

事实证明,您的问题与您选择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?]()
}
Run Code Online (Sandbox Code Playgroud)

然而,它缺乏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()
Run Code Online (Sandbox Code Playgroud)

但这既不实用也不可读。在 Java 7 之前,这将是一种可接受的解决方案(因为它是唯一的解决方案),但即使在 Java 8 中(在 Swift 中更是如此),我们也希望将通知逻辑封装到协议中Subscribable作为默认实现,但是那将是另一篇文章。

由于您选择实现subscribersNSHashTable(这里可能有 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()
Run Code Online (Sandbox Code Playgroud)

这与您的原始版本几乎相同(有一些证据)。看起来 Objective-C与 Swift@protocol不太一样protocol,但 Swift实际上两者都可以

不过,这其中有很多微妙之处,只有allObjects在没有类型擦除的情况下才有效,您的信任objectEnumerator只会返回Any?,这是一种从中获取任何东西的愚蠢动物。另请注意

let iDoSubscribe = IWillSubscribe()
Run Code Online (Sandbox Code Playgroud)

是有帮助的。起初我尝试过

controller.subscribers.add(IWillSubscribe())
Run Code Online (Sandbox Code Playgroud)

它实际上在 of 中添加了一些东西countsubscribers但没有任何迭代尝试(正如人们应该期望的那样)weak没有在其他任何地方引用的引用中期望的那样)。

一个很晚的答案已经太长了,只是为了证明这仍然是一个问题,即使使用 Swift 3。也许一旦这个 Jira 票证得到解决,情况会变得更好。