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

时间:2016-09-29 20:41:32

标签: ios swift macos generics

我试图实现一个简单的多委托情况:

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
}
  

错误:使用&#39; ControllerSubscriber&#39;作为符合协议的具体类型的任何对象&#39;不受支持。

我的问题是:

  • 这个错误究竟意味着什么?
  • 我试图做的事情失败的基本概念是什么?
  • 为什么这&#34;不支持&#34;?

当然,我该如何解决这个问题?从实际的解决方案来看,不是解决方法。

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

this related question,但请注意答案仅提供解决方法,无解释或解决方案。

1 个答案:

答案 0 :(得分: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?]()
}

但是,它缺少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技巧。经过多次实验(最后找到了this question的第四个答案,我得到了以下工作:

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相同,但是Swift 可以实际执行任何操作。

虽然这里有很多细微之处,只有allObjects没有类型擦除,你的可靠objectEnumerator只返回Any?,这是一个愚蠢的动物,可以从中获取任何东西。另请注意

let iDoSubscribe = IWillSubscribe()

是有帮助的。起初我试过

controller.subscribers.add(IWillSubscribe())

实际上向count的{​​{1}}添加了一些内容,但是在任何迭代尝试都没有进行(正如人们应该期望的subscribers引用在其他任何地方都没有引用)

一个非常晚的答案已经太长了,只是为了证明这仍然是仍然一个问题,即使使用Swift 3.也许这会在this Jira ticket解决后变得更好。