type A requires that type B be a class type swift

2020-03-09 08:57发布

the following code gives me a compile error

'WeakReference' requires that 'ServiceDelegate' be a class type

protocol ServiceDelegate: AnyObject {
    func doIt()
}

class SomeClass() {
    // compile error occurs in this line
    private var observers = [WeakReference<ServiceDelegate>]()
}

WeakReference code:

final class WeakReference<T: AnyObject> {

    private(set) weak var value: T?

    init(value: T?) {
        self.value = value
    }
}

How can I fix this error? Delegate should be declared correctly as per this site.

What I have tried so far:

  • Changing the delegate protocol conformance from AnyObject to class does not solve the problem.
  • Try the above code in a clean playground.

标签: swift swift4
5条回答
forever°为你锁心
2楼-- · 2020-03-09 09:11

The problem is that WeakReference<ServiceDelegate> is wrong at line

private var observers = [WeakReference<ServiceDelegate>]()

You have to use a concrete class instead of protocol inside <>

You have two possible solutions:

  1. Create a concrete class and use it:
class ServiceClass: ServiceDelegate {
//...
}
private var observers = [WeakReference<ServiceClass>]()
  1. Or use a protocol. I mean this:

     final class WeakReference<T: AnyObject> {
    
         private(set) weak var value: T?
    
         init(value: T?) {
             self.value = value
         }
     }
    
     protocol SomeContainer: AnyObject { }
     extension WeakReference: SomeContainer { }
    

and use this way:

 private var observers = [SomeContainer]()

Note

Using this way:

class SomeClass<T: ServiceDelegate> {
    private var observers = [WeakReference<T>]()
}

You just move the problem to another part of the code.

查看更多
爱情/是我丢掉的垃圾
3楼-- · 2020-03-09 09:14

I had similar problem and ended up keeping generic WeakReference, but removing type constraint:

struct WeakReference<T> {
    private weak var storage: AnyObject?
    var value: T? {
        get { return storage.map { $0 as! T } }
        set {
            storage = newValue.map { $0 as AnyObject }
        }
    }

    init(value: T?) {
        self.value = value
    }
}

This works for classes, Objective-C protocols and Swift protocols:

protocol P: class {}
@objc protocol Q {}

class Z: P, Q {}

var z = Z()
var rz = WeakReference<Z>(value: z)
var rp = WeakReference<P>(value: z)
var rq = WeakReference<Q>(value: z)
assert(rz.value === z)
assert(rp.value === z)
assert(rq.value === z)
z = Z()
assert(rz.value === nil)
assert(rp.value === nil)
assert(rq.value === nil)

Unfortunately it compiles for other things too:

protocol R {}
struct S: R {}
var rr = WeakReference<R>(value: S())
print("rr =", rr.value as Any) // nil
var rs = WeakReference<S>(value: S())
print("rs =", rs.value as Any) // nil

In Swift anything can be casted to AnyObject, but for value types that means boxing - new instance is allocated and immediately lost, so it always produces nil.

This can be used to implement an assertion that casting to AnyObject preserves identity:

struct WeakReference<T> {
    private weak var storage: AnyObject?
    var value: T? {
        get { return storage.map { $0 as! T } }
        set {
            storage = newValue.map {
                let asObject = $0 as AnyObject
                assert(asObject === $0 as AnyObject)
                return asObject
            }
        }
    }

    init(value: T?) {
        self.value = value
    }
}

Alternative approach would be to use https://github.com/wickwirew/Runtime to validate kind of T.self.

查看更多
smile是对你的礼貌
4楼-- · 2020-03-09 09:23

You can't have a WeakReference<ServiceDelegate>. ServiceDelegate itself is not an AnyObject, it just requires that anything that conforms to it be an AnyObject.

You would need to make SomeClass generic and use the generic type as the type for the WeakReference:

class SomeClass<T: ServiceDelegate> {
    private var observers = [WeakReference<T>]()
}

If the generic on SomeClass is too constricting and you want to be able to have instances of multiple unrelated classes as observers then I would do it by abandoning the generic parameter on WeakReference:

final class WeakServiceDelegate {
    private(set) weak var value: ServiceDelegate?

    init(value: ServiceDelegate?) {
        self.value = value
    }
}

class SomeClass {
    private var observers = [WeakServiceDelegate]()
}

Alternatively you could make WeakReference conditionally conform to ServiceDelegate:

extension WeakReference: ServiceDelegate where T: ServiceDelegate {
    func doIt() {
        value?.doIt()
    }
}

And then use an array of ServiceDelegate in SomeClass:

class SomeClass {
    private var observers = [ServiceDelegate]()

    func addObserver<T: ServiceDelegate>(_ observer: T) {
        observers.append(WeakReference(value: observer))
    }
}
查看更多
beautiful°
5楼-- · 2020-03-09 09:27

As you see, ServiceDelegate is a protocol, not a class type. Even if all types which can conform to ServiceDelegate are class types, ServiceDelegate itself is not a class type. It is the fact of the pure Swift protocols currently.

Try @obc, Objective-C protocols are a bit different:

@objc protocol ServiceDelegate {
    func doIt()
}

You may want to exclude Objective-C something and to make some pure Swift classes conform to ServiceDelegate, but I cannot find other ways around.

查看更多
Animai°情兽
6楼-- · 2020-03-09 09:32

create plain protocol

public protocol AnyWeakValue {
    var anyValue: Any? { get }
}

inherit associatedtype protocol from AnyWeakValue

public protocol WeakValue: AnyWeakValue {
    associatedtype ValueType
    var value: ValueType? { get }
}

extension WeakValue {
    public var anyValue: Any? { return value }
}

create class Weak inherit WeakValue

open class Weak<Value: AnyObject>: WeakValue {
    public init(value: Value?) { self.value = value }
    open private(set) weak var value: Value?
}

using example

private var _delegates: [AnyWeakValue] = []
public var delegates: [SomeProtocol] {
    return _delegates.compactMap({$0.anyValue as? SomeProtocol})
}

public func register<Delegate>(_ delegate: Delegate) where Delegate: SomeProtocol {
    let weak: Weak<Delegate> = Weak.init(value: delegate)
    _delegates.append(weak)
}
查看更多
登录 后发表回答