Using as a concrete type conforming to protocol AnyObject is not supported
Asked Answered
C

6

37

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?

Cholecystitis answered 27/9, 2015 at 12:34 Comment(12)
Where is the error actually at? And what's wrong with NSHashTable?Telega
In your protocol declaration, if you change AnyObject to class, it should work fine. Don't ask me to explain the difference though.Telega
@Telega I tried public protocol MyDelegate : class before, it does not workCholecystitis
What is the error that that gives?Telega
@Telega the same error in this question. NSHashTable is not generic, I don't want thatCholecystitis
i.imgur.com/B0swn6w.pngTelega
@Telega still get that error. I'm on Xcode 7.0 and Swift 2. Wonder if that is fixed in Swift 2.1Cholecystitis
Let us continue this discussion in chat.Cholecystitis
Has anyone come up with a solution to this yet? Suffering from the exact same problem...Coppock
This might be helpful, but they don't provide an exact solution either forums.developer.apple.com/thread/17942Coppock
Inheriting from class and AnyObject should be the same thingMucin
It's not fixed yet. Here's the jira ticket describing the same problem: bugs.swift.org/browse/SR-1176Sandoval
M
11

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.

Methodism answered 14/11, 2015 at 5:32 Comment(1)
object is AnyObject is always true.Heterograft
H
18

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: Setting a value that conforms to T, but is not an AnyObject, fails.

Heliogravure answered 28/4, 2016 at 11:27 Comment(4)
Workaround 2 works nicely here, though of course it should not be needed! Great work.Toomin
Workaround 2 saved me from a massive rewrite. Thanks @HeliogravureBozen
Cool guy! How did you discover Workaround2? It works really fine. And also Workaround1 just like a magic!Epiphany
Get warning Conditional cast from 'T?' to 'AnyObject' always succeeds on line _value = newValue as? AnyObject. What should we do about that?Selfcontent
M
11

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.

Methodism answered 14/11, 2015 at 5:32 Comment(1)
object is AnyObject is always true.Heterograft
M
5

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

Mucin answered 10/11, 2015 at 3:45 Comment(1)
Your suggested WeakContainer can only store values of type MyDelegate. The question is how to implement this container so that it can be reused for other protocols as well.Heliogravure
T
3

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()

}
Trivial answered 9/4, 2017 at 8:42 Comment(0)
F
1

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:

  1. 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 { ... }

  2. Define MyDelegate to be 'class bound' as protocol MyDelegate : class { ... }

  3. Annotate MyDelegate with @obj which, essentially, makes it 'class bound'

  4. 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.

Foliated answered 28/4, 2016 at 14:5 Comment(1)
As to option 2: Doesn't class have the same effect as AnyObject here? It didn't seem to make a difference when I tried it. As to option 4: As far as I understand, inheritance from AnyObject is the only way to make the generic type class-bound. Could you provide example implementations for options 2 and 4?Heliogravure
D
0

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
            }
        }
    }
}
Delusive answered 2/5, 2016 at 17:43 Comment(1)
this does not compile with swift 3Cladding

© 2022 - 2024 — McMap. All rights reserved.