Search code examples
swiftgenericstypesswift-protocolstype-constraints

Use protocol in swift's generic with type constraint


I write a custom storage, that should supports only objects that conform some protocol:

protocol MyBaseProtocol {
    func baseMethod()
}
class Stor <ElementType : MyBaseProtocol>  {
    var storage = [ElementType]()
    
    func addElement(_ element: ElementType) {
        storage.append(element)
    }
}

Next I've created a child protocol and want to store only objects what conform the child protocol:

protocol MyProtocol : MyBaseProtocol {
    func method()
}
var otherStorage = Stor<MyProtocol>() //compilation error
class C1 : MyProtocol {
    func method() {
    }
    func baseMethod() {
    }
}
class S1 : MyProtocol {
    func method() {
    }
    func baseMethod() {
    }
}
otherStorage.addElement(C1())
otherStorage.addElement(S1())

I've got an error:

Value of protocol type 'MyProtocol' cannot conform to 'MyBaseProtocol'; only struct/enum/class types can conform to protocols

How I can create an instance of Stor that can store only objects that conform MyBaseProtocol?


Solution

  • You are running into the issue of protocols not conforming to themselves. You can resolve this by creating a concrete type conforming to MyProtocol and converting all your conforming types to that before storing them in your Store.

    class AnyMyProtocol: MyProtocol {
        private let _baseMethod: () -> ()
        private let _method: () -> ()
    
        init(base: MyProtocol) {
            _baseMethod = base.baseMethod
            _method = base.method
        }
    
        func baseMethod() {
            _baseMethod()
        }
    
        func method() {
            _method()
        }
    }
    
    extension MyProtocol {
        var erased: AnyMyProtocol {
            AnyMyProtocol(base: self)
        }
    }
    
    var otherStorage = Store<AnyMyProtocol>()
    
    class C1 : MyProtocol {
        func method() {}
        func baseMethod() {}
    }
    
    struct S1 : MyProtocol {
        func method() {}
        func baseMethod() {}
    }
    
    enum MyEnum: MyProtocol {
        case some
    
        func method() {}
        func baseMethod() {}
    }
    
    otherStorage.addElement(C1().erased)
    otherStorage.addElement(S1().erased)
    otherStorage.addElement(MyEnum.some.erased)