I'm using Swift 2 and using WeakContainer as a way to store a set of weak objects, much like NSHashTable.weakObjectsHashTable()
struct WeakContainer<T: AnyObject> {
weak var value: T?
}
public protocol MyDelegate : AnyObject {
}
Then in my ViewController, I declare
public var delegates = [WeakContainer<MyDelegate>]
But it is error
Using MyDelegate as a concrete type conforming to protocol AnyObject is not supported
I see that the error is that WeakContainer
has value
member declared as weak
, so T
is expected to be object. But I also declare MyDelegate
as AnyObject
, too. How to get around this?
I had the same idea to create weak container with generics.
As result I created wrapper for NSHashTable
and did some workaround for your compiler error.
class WeakSet<ObjectType>: SequenceType {
var count: Int {
return weakStorage.count
}
private let weakStorage = NSHashTable.weakObjectsHashTable()
func addObject(object: ObjectType) {
guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
weakStorage.addObject(object as? AnyObject)
}
func removeObject(object: ObjectType) {
guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
weakStorage.removeObject(object as? AnyObject)
}
func removeAllObjects() {
weakStorage.removeAllObjects()
}
func containsObject(object: ObjectType) -> Bool {
guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
return weakStorage.containsObject(object as? AnyObject)
}
func generate() -> AnyGenerator<ObjectType> {
let enumerator = weakStorage.objectEnumerator()
return anyGenerator {
return enumerator.nextObject() as! ObjectType?
}
}
}
Usage:
protocol MyDelegate : AnyObject {
func doWork()
}
class MyClass: AnyObject, MyDelegate {
fun doWork() {
// Do delegated work.
}
}
var delegates = WeakSet<MyDelegate>()
delegates.addObject(MyClass())
for delegate in delegates {
delegate.doWork()
}
It's not the best solution, because WeakSet
can be initialized with any type, and if this type doesn't conform to AnyObject
protocol then app will crash. But I don't see any better solution right now.
I ran into the same problem when I tried to implement weak containers. As @plivesey points out in a comment above, this seems to be a bug in Swift 2.2 / Xcode 7.3, but it is expected to work.
However, the problem does not occur for some Foundation protocols. For example, this compiles:
let container = WeakContainer<NSCacheDelegate>()
I found out that this works for protocols marked with the @objc
attribute. You can use this as a workaround:
Workaround 1
@objc
public protocol MyDelegate : AnyObject { }
let container = WeakContainer<MyDelegate>() // No compiler error
As this can lead to other problems (some types cannot be represented in Objective-C), here is an alternative approach:
Workaround 2
Drop the AnyObject
requirement from the container, and cast the value to AnyObject
internally.
struct WeakContainer<T> {
private weak var _value:AnyObject?
var value: T? {
get {
return _value as? T
}
set {
_value = newValue as? AnyObject
}
}
}
protocol MyDelegate : AnyObject { }
var container = WeakContainer<MyDelegate>() // No compiler error
Caveat: Saving values that conform to T
but are not AnyObject
s fails.
Why are you trying to use generics? I would suggest doing the following:
import Foundation
import UIKit
protocol MyDelegate : AnyObject {
}
class WeakContainer : AnyObject {
weak var value: MyDelegate?
}
class ViewController: UIViewController {
var delegates = [WeakContainer]()
}
There is also NSValue
's nonretainedObject
Your issue is that WeakContainer
requires its generic type T
to be a subtype of AnyObject
- a protocol
declaration is not a subtype of AnyObject
. You have four options:
Instead of declaring WeakContainer<MyDelegate>
replace it with something that actually implements MyDelegate
. The Swift-y approach for this is to use the AnyX
pattern: struct AnyMyDelegate : MyDelegate { ... }
Define MyDelegate
to be 'class bound' as protocol MyDelegate : class { ... }
Annotate MyDelegate
with @obj
which, essentially, makes it 'class bound'
Reformulate WeakContainer
to not require its generic type to inherit from AnyObject
. You'll be hard pressed to make this work because you need a property declared as weak var
and there are limitation as to what types are accepted by weak var
- which are AnyObject
essentially.
If your Protocol can be marked as @obj then you can use code below
protocol Observerable {
associatedtype P : AnyObject
var delegates: NSHashTable<P> { get }
}
@objc protocol MyProtocol {
func someFunc()
}
class SomeClass : Observerable {
var delegates = NSHashTable<MyProtocol>.weakObjects()
}
Here is my implementation of WeakSet in pure Swift (without NSHashTable).
internal struct WeakBox<T: AnyObject> {
internal private(set) weak var value: T?
private var pointer: UnsafePointer<Void>
internal init(_ value: T) {
self.value = value
self.pointer = unsafeAddressOf(value)
}
}
extension WeakBox: Hashable {
var hashValue: Int {
return self.pointer.hashValue
}
}
extension WeakBox: Equatable {}
func ==<T>(lhs: WeakBox<T>, rhs: WeakBox<T>) -> Bool {
return lhs.pointer == rhs.pointer
}
public struct WeakSet<Element>: SequenceType {
private var boxes = Set<WeakBox<AnyObject>>()
public mutating func insert(member: Element) {
guard let object = member as? AnyObject else {
fatalError("WeakSet's member (\(member)) must conform to AnyObject protocol.")
}
self.boxes.insert(WeakBox(object))
}
public mutating func remove(member: Element) {
guard let object = member as? AnyObject else {
fatalError("WeakSet's member (\(member)) must conform to AnyObject protocol.")
}
self.boxes.remove(WeakBox(object))
}
public mutating func removeAll() {
self.boxes.removeAll()
}
public func contains(member: Element) -> Bool {
guard let object = member as? AnyObject else {
fatalError("WeakSet's member (\(member)) must conform to AnyObject protocol.")
}
return self.boxes.contains(WeakBox(object))
}
public func generate() -> AnyGenerator<Element> {
var generator = self.boxes.generate()
return AnyGenerator {
while(true) {
guard let box = generator.next() else {
return nil
}
guard let element = box.value else {
continue
}
return element as? Element
}
}
}
}