Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.2k views
in Technique[技术] by (71.8m points)

swift - Declare array of classes that conform to a protocol

Lets say I have created this protocol and a couple of classes

import UIKit

protocol ControllerConstructorProtocol {
    class func construct() -> UIViewController?
}

class MyConstructor: ControllerConstructorProtocol {
    class func construct() -> UIViewController? {
        return UIViewController()
    }
}

class MyOtherConstructor: ControllerConstructorProtocol {
    class func construct() -> UIViewController? {
        return UITableViewController(style: .Grouped)
    }
}

Now I want to declare an array that contains classes of objects that will conform to such protocol. How can I declare it? Ideally I would like the compiler to check the array is correctly filled (at compile time) rather than checking it myself at (run time) with the as operator.

This is what I have tried without success :(

  1. This leads to a compile error:

    'Any Object does not have a member named 'construct'

    var array = [
        MyConstructor.self,
        MyOtherConstructor.self,
    ]
    
    var controller = array[0].construct() // << ERROR here
    
  2. Writing this is even worse, since the class itself does not conform to the protocol (their instances do)

    Type 'MyConstructor.Type' does not conform to protocol 'ControllerConstructorProtocol'

    var array: Array<ControllerConstructorProtocol> = [
        MyConstructor.self, // << ERROR here
        MyOtherConstructor.self,
    ]
    

EDIT 2016/04/23: In Swift 2.2 (Xcode 7.3) it is possible to write @rintaro's original idea :)

let array: Array<ControllerConstructorProtocol.Type> = [
    MyConstructor.self,
    MyOtherConstructor.self,
]
let viewController = array[0].construct()
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

"array of classes that conform to a protocol" can be declared like Array<TheProtocol.Type>.

You can:

var array: Array<ControllerConstructorProtocol.Type> = [
    MyConstructor.self,
    MyOtherConstructor.self,
]

But...,

    array[0].construct()
//  ^ error: accessing members of protocol type value 'ControllerConstructorProtocol.Type' is unimplemented

Calling method on the item is "unimplemented".

As of now, you have to declare the protocol as @objc, and call the method via AnyClass. Moreover, for some reasons, we cannot directly cast array[0] to AnyClass, instead, we have to cast it to Any, then AnyClass.

@objc protocol ControllerConstructorProtocol {
    class func construct() -> UIViewController?
}

var array: Array<ControllerConstructorProtocol.Type> = [
    MyConstructor.self,
    MyOtherConstructor.self,
]

let vc = (array[0] as Any as AnyClass).construct()

Note: Casting problem was fixed in Swift 1.2 / Xcode 6.3. But "unimplemented" is "unimplmented" :(


Just random ideas:

It's depends on your actual use-case, but in this particular case, array of ()-> UIViewController? closures is sufficient:

var array: [() -> UIViewController?] = [
    MyConstructor.construct,
    MyOtherConstructor.construct,
]

let vc = array[0]()

If you have several methods, you might want to use type-erased wrapper of the protocol.

protocol ControllerConstructorProtocol {
    class func construct() -> UIViewController?
    class func whoami() -> String
}

struct ControllerConstructorWrapper {
    private let _construct: () -> UIViewController?
    private let _whoami: () -> String
    init<T: ControllerConstructorProtocol>(_ t:T.Type) {
        _construct = { t.construct() }
        _whoami = { t.whoami() }
    }
    func construct() -> UIViewController? { return _construct() }
    func whoami() -> String { return _whoami() }
}

var array: [ControllerConstructorWrapper] = [
    ControllerConstructorWrapper(MyConstructor),
    ControllerConstructorWrapper(MyOtherConstructor),
]

let who = array[0].whoami()
let vc = array[0].construct()

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...