Reputation: 45088
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 :(
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
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()
Upvotes: 8
Views: 6194
Reputation: 11435
Try this:
var myConst = MyConstructor()
var myOthConst = MyOtherConstructor()
var array:[AnyObject] = [
myConst,
myOthConst
]
for a in array
{
if a is MyConstructor
{
println("a is type of MyConstructor")
(a as! MyConstructor).myMethod()
}
else if a is MyOtherConstructor
{
println("a is type of MyOtherConstructor")
(a as! MyOtherConstructor).myMethod()
}
}
Another solution, although not that pretty...
Upvotes: 0
Reputation: 1169
If you really want to sotre classes only (that conforms to protocol) then you can do it the following way (in Swift 3):
If you want to create a new instance with the protocol type you have to add init() to protocol declaration:
protocol SomeProtocol: ConformsToOtherProtocolIfNeeded {
init(...) { ... }
func someFunction(...) { ... }
}
class Class1: SomeProtocol {
init(...) { ... }
func someFunction(...) { ... }
}
class Class2: SomeProtocol {
init(...) { ... }
func someFunction(...) { ... }
}
declare array (as above):
var someProtocols: Array<SomeProtocol.Type> = [
Class1.self,
Class2.self,
]
and if you want to use someFunction, you have to create an instance, because the elements in the array not an instance. Example:
for sp in someProtocols {
let instance = sp.init()
instance.someFunction()
}
If you want to compare the type of class, you also have to create an instance. (So you cannot use item of array directly (sp).)
Examples:
if type(of: instance) == type(of: otherInstance) { ... }
if instance is SomeProtocol { ... }
if instance is Class1 { ... }
Upvotes: 0
Reputation: 51911
"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()
Upvotes: 11
Reputation: 36295
Not sure if I got your question completely, but why not this way:
var array: [ControllerConstructorProtocol] = [MyConstructor(), MyOtherConstructor()]
Upvotes: 0