Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use generic struct with protocol

It seems that Swift generics work fine, as long as I don't try to combine them in any practical way. I'm using Swift 4.1, and I would like to create a generic array containing only weak references. I can define this as WeakList<T>. So far so well. But: I would like to use a protocol for T. Swift says nope..

import Foundation

protocol Observer: class {
    func stateChanged(sender: SomeClass, newState: Int)
}

struct WeakList<T> where T: AnyObject {
    struct Ptr {
        weak var p: T?
    }
    private var storage: [Ptr] = []
    var aliveObjects: [T] {
        var result: [T] = []
        for ptr in storage {
            if let p = ptr.p {
                result.append(p)
            }
        }
        return result
    }
    mutating func add(_ obj: T) {
        storage.append(Ptr(p: obj))
    }
    // Let's ignore for a moment that this implementation leaks memory badly.
}

class SomeClass {
    var someVar: WeakList<Observer> = WeakList<Observer>()
    // Error: WeakList requires that 'Observer' be a class type

    var thisIsOk: WeakList<NSObject> = WeakList<NSObject>()
}

(this is not my original code but a minimal verifyable example that contains enough details so that no one can say "just remove the AnyObject constraint from the structure")

I guess what I'm trying to do is just not possible. Or is it? It's just frustrating how 4 out of 5 times when I try to do something with Swift generics, I later learn that what I am trying to do is just not possible. (I can implement the same thing in Objective-C easily, by the way.)

  • I tried changing the class constraint to an AnyObject constraint => doesn't work either.
  • I tried to change the AnyObject constraint to a class constraint => doesn't even compile.
  • And changing it to protocol Observer where Self: NSObject doesn't change anything. NSObject is a class type, Observer is an NSObject. It should follow that Observer is a class type. The "is a" relationship doesn't seem to be transitive here.
like image 235
Michael Avatar asked Nov 22 '25 00:11

Michael


1 Answers

With current implementation you cannot inherit the protocol from AnyObject. What you can do is to create a Type Eraser for your protocol and use that instead. Now your type eraser can be inherited from AnyObject.

Something like this:

protocol Observer {
    func stateChanged(sender: SomeClass, newState: Int)
}

class AnyObserver: NSObject, Observer {
    private let observer: Observer

    init(observer: Observer) {
        self.observer = observer
    }

    func stateChanged(sender: SomeClass, newState: Int) {
        observer.stateChanged(sender: sender, newState: newState)
    }
}

struct WeakList<T> where T: AnyObject {
    struct Ptr {
        weak var p: T?
    }
    private var storage: [Ptr] = []
    var aliveObjects: [T] {
        var result: [T] = []
        for ptr in storage {
            if let p = ptr.p {
                result.append(p)
            }
        }
        return result
    }
    mutating func add(_ obj: T) {
        storage.append(Ptr(p: obj))
    }
    // Let's ignore for a moment that this implementation leaks memory badly.
}

class SomeClass {
    var someVar: WeakList<AnyObserver> = WeakList<AnyObserver>()

    var thisIsOk: WeakList<NSObject> = WeakList<NSObject>()
}
like image 178
Mukesh Avatar answered Nov 24 '25 15:11

Mukesh



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!