Can't create an Array of types conforming to a Protocol in Swift
Let's say, if we could put an instance of Thing
into array foos
, what will happen?
protocol Foo {
associatedtype BazType
func bar(x:BazType) -> BazType
}
class Thing: Foo {
func bar(x: Int) -> Int {
return x.successor()
}
}
class AnotherThing: Foo {
func bar(x: String) -> String {
return x
}
}
var foos: [Foo] = [Thing()]
Because AnotherThing
conforms to Foo
too, so we can put it into foos
also.
foos.append(AnotherThing())
Now we grab a foo
from foos
randomly.
let foo = foos[Int(arc4random_uniform(UInt32(foos.count - 1)))]
and I'm going to call method bar
, can you tell me that I should send a string or an integer to bar
?
foo.bar("foo")
or foo.bar(1)
Swift can't.
So it can only be used as a generic constraint.
What scenario requires a protocol like this?
Example:
class MyClass<T: Foo> {
let fooThing: T?
init(fooThing: T? = nil) {
self.fooThing = fooThing
}
func myMethod() {
let thing = fooThing as? Thing // ok
thing?.bar(1) // fine
let anotherThing = fooThing as? AnotherThing // no problem
anotherThing?.bar("foo") // you can do it
// but you can't downcast it to types which doesn't conform to Foo
let string = fooThing as? String // this is an error
}
}
Swift array of struct types conforming to protocol
I found the problem. My protocol was inheriting from RawOptionSetType
. Not sure why that caused an issue, but commenting that inheritance out made it work. Weird.
Can't create array of views conforming to same protocol?
Here is possible approach - the main thing is that your protocol type should not contain generics.
Tested with Xcode 12.1 / iOS 14.1
Note: original style preserved intentionally
protocol viewTest { // << here !!
var name: String {get set}
func getView() -> AnyView // << here !!
}
extension viewTest where Self: View {
func getView() -> AnyView {
AnyView(self)
}
}
struct v1: View, viewTest {
var name: String = "v1"
var body: some View {
Text("Name: \(name)")
}
}
struct v2: View, viewTest {
var name: String = "v2"
var body: some View {
Text("2nd view name: \(name)")
}
}
struct testView: View{
var arr: [viewTest] // << here !!
@State private var index: Int = 0
var body: some View {
VStack {
if(index >= 0) {
self.arr[index].getView() // << here !!
Button("Next", action: {
if(index < arr.count-1) {
index += 1
}
})
} else {
Text("no views")
}
}
}
}
struct testView_Previews: PreviewProvider {
static var previews: some View {
testView(arr: [
v1(),
v2()
])
}
}
How would I use an array of generics with a type parameter conforming to a protocol?
To see what's wrong with your scenario, forget about trying to declare the type of this array, and try to actually make such an array out of actual objects:
protocol MyProtocol {}
struct MyStruct<T: MyProtocol> {
let myProp: T
}
struct S1 : MyProtocol {}
struct S2 : MyProtocol {}
let myStruct1 = MyStruct(myProp: S1())
let myStruct2 = MyStruct(myProp: S2())
let array = [myStruct1, myStruct2] // error
The compiler kicks back: "heterogeneous collection literal could only be inferred to '[Any]'". And that sums it up. The types of myStruct1
and myStruct2
have nothing in common, so you cannot make an array of them.
That is why you are not able to declare the array to be of a type that will embrace both of them. There is no such type. The types of myStruct1
and myStruct2
, namely MyStruct<S1>
and MyStruct<S2>
, are unrelated.
I know that they look related, because the word "MyProtocol" in the original declaration appears to provide some sort commonality. But the word "MyProtocol" does not designate a type; it designates a constraint on the actual type, saying that whatever this one type is, it must be an adopter of MyProtocol. S1 and S2 are two different types, and so MyStruct<S1>
and MyStruct<S2>
are two different types. You can't put them together in an array. The fact that both S1 and S2 happen to adopt MyProtocol is irrelevant.
Part of the difficulty may be that you think that two generic types are somehow related because their parameterized types are related. That is not the case. The classic example is a class and its subclass:
class Cat {}
class Kitten: Cat {}
struct Animal<T: Cat> {}
let cat = Animal<Cat>()
let kitten = Animal<Kitten>()
let array2 = [cat, kitten] // error
We get the same compile error. Again, you might imagine that you can put cat
and kitten
together in an array because Kitten is a subclass of Cat. But that is not true. Animal<Cat>
and Animal<Kitten>
are unrelated types.
Array of protocol type
What you need to do is to use type erasure, much like AnyHashable
does in the Swift Standard Library.
You can't do:
var a: [Hashable] = [5, "Yo"]
// error: protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements
What you have to do is to use the type-erased type AnyHashable
:
var a: [AnyHashable] = [AnyHashable(5), AnyHashable("Yo")]
a[0].hashValue // => shows 5 in a playground
So your solution would be to first split the protocol in smaller parts and promote Equatable
to Hashable
(to reuse AnyHashable
)
protocol Conditionable {
var condition: Condition? { get set }
}
protocol Executable {
func execute() -> SKAction
}
protocol Commandable: Hashable, Executable, Conditionable {}
Then create an AnyCommandable
struct, like this:
struct AnyCommandable: Commandable, Equatable {
var exeBase: Executable
var condBase: Conditionable
var eqBase: AnyHashable
init<T: Commandable>(_ commandable: T) where T : Equatable {
self.condBase = commandable
self.exeBase = commandable
self.eqBase = AnyHashable(commandable)
}
var condition: Condition? {
get {
return condBase.condition
}
set {
condBase.condition = condition
}
}
var hashValue: Int {
return eqBase.hashValue
}
func execute() -> SKAction {
return exeBase.execute()
}
public static func ==(lhs: AnyCommandable, rhs: AnyCommandable) -> Bool {
return lhs.eqBase == rhs.eqBase
}
}
And then you can use it like this:
var a = FunctionCommand()
a.commands = [AnyCommandable(MoveCommand()), AnyCommandable(FunctionCommand())]
And you can easily access properties of commands
, because AnyCommandable
implements Commandable
a.commands[0].condition
You need to remember to now add Hashable
and Equatable
to all your commands.
I used those implementations for testing:
struct MoveCommand: Commandable {
var movingVector: CGVector!
var condition: Condition?
func execute() -> SKAction {
return SKAction()
}
var hashValue: Int {
return Int(movingVector.dx) * Int(movingVector.dy)
}
public static func ==(lhs: MoveCommand, rhs: MoveCommand) -> Bool {
return lhs.movingVector == rhs.movingVector
}
}
struct FunctionCommand: Commandable {
var commands = [AnyCommandable]()
var condition: Condition?
func execute() -> SKAction {
return SKAction.group(commands.map { $0.execute() })
}
var hashValue: Int {
return commands.count
}
public static func ==(lhs: FunctionCommand, rhs: FunctionCommand) -> Bool {
return lhs.commands == rhs.commands
}
}
Array of enum types conforming to a protocol with an associated type
I want to display a menu listing all the possible options.
So you want Strings, not the cases themselves. That's absolutely doable. First, start by saying what you really want the type to do in the form of a protocol:
protocol CaseNamed {
static var caseNames: [String]
}
If you had that, you could build what you want:
var enums: [CaseNamed.Type] = [A.self, B.self]
enums.flatMap { $0.caseNames }
(I call this "wish driven development." I wish I had a type that could....)
Now you just need to conform types to CaseNamed by implementing caseNames
. Luckily that's easy if the type also happens to conform to CaseIterable:
extension CaseNamed where Self: CaseIterable {
static var caseNames: [String] {
self.allCases.map { "\($0)" }
}
}
But you can have CaseNamed types that don't conform to CaseIterable. CaseIterable isn't a requirement. It's just nice if you have it. Here's the full code:
protocol CaseNamed {
static var caseNames: [String] { get }
}
enum A: String, CaseIterable, CaseNamed {
case a1, a2, a3
}
enum B: String, CaseIterable, CaseNamed {
case b1, b2, b3
}
extension CaseNamed where Self: CaseIterable {
static var caseNames: [String] {
self.allCases.map { "\($0)" }
}
}
var enums: [CaseNamed.Type] = [A.self, B.self]
enums.flatMap { $0.caseNames }
Now of course you might also want this CaseNamed protocol to do other things, so you can add those other things. But you need to think in terms of the calling code and what it fundamentally needs to do its job.
How to define an array of objects conforming to a protocol?
The problem is about using the generics counterpart for protocols, type aliases.
It sounds weird, but if you define a type alias, you cannot use the protocol as a type, which means you cannot declare a variable of that protocol type, a function parameter, etc. And you cannot use it as the generic object of an array.
As the error say, the only usage you can make of it is as a generic constraint (like in class Test<T:ProtocolWithAlias>
).
To prove that, just remove the typealias from your protocol (note, this is just to prove, it's not a solution):
protocol MyProtocol {
var abc: Int { get }
}
and modify the rest of your sample code accordingly:
class XYZ: MyProtocol {
var abc: Int { return 32 }
}
var list = [MyProtocol]()
You'll notice that it works.
You are probably more interested in how to solve this problem. I can't think of any elegant solution, just the following 2:
- remove the typealias from the protocol and replace
T
withAnyObject
(ugly solution!!) - turn the protocol into a class (but that's not a solution that works in all cases)
but as you may argue, I don't like any of them. The only suggestion I can provide is to rethink of your design and figure out if you can use a different way (i.e. not using typealiased protocol) to achieve the same result.
Related Topics
Swift: Uicollectionview Selecting Cell Indexpath Issues
How to Open Url in Safari and the Get Back to the App Under Uitests in Xcode 7
Xcode Error: Missing Required Module 'Firebase'
"Nsurl" Is Not Implicitly Convertible to "Url"; Did You Mean to Use "As" to Explicitly Convert
Swift: Overriding Didset Results in a Recursion
Check If Variable Is a Block/Function/Callable in Swift
Scenekit Object Between Two Points
Closure Containing a Declaration Cannot Be Used with Function Builder 'Viewbuilder'
How to Recognize Continuous Touch in Swift
Check Availability in Switch Statement
How to Insert an Image Inline Uilabel in iOS 8 Using Swift
Difference Between Switch Cases "@Unknown Default" and "Default" in Swift 5
Swiftui: How to Iterate Over an Array of Bindable Objects
How to Fix ' *Pod* Does Not Support Provisioning Profiles' in Azure Devops Build Agent