Assigning an array of structs to an array of protocols - swift

Let's say I have the following:
protocol MyProtocol {
}
struct MyStruct: MyProtocol {
}
var s1 = MyStruct()
var s2 = MyStruct()
var s3 = MyStruct()
var structArray = [s1, s2, s3]
When I try to assign this array of structs to an array of protocols (that each struct in structArray conforms to):
var protocolArray:[MyProtocol] = structArray
I get this error: Cannot convert array of type '[MyStruct]' to specified type '[MyProtocol]'
I would expect that since each object in the array conforms to the protocol it would be ok to say that "an array of structs that conform to some protocol" is assignable to something that expects "an array of anything that conforms to that protocol". But maybe this doesn't apply when the type is "an array of " vs just "thing", if that makes any sense.
For example, this is valid:
var p1:MyProtocol = s1
Because s1 conforms to MyProtocol. But if you use arrays then it doesn't seem to hold anymore.
Incidentally, this seems to work too:
var p1Array:[MyProtocol] = [s1, s2, s3]
Presumably because the type of the array is determined to be [MyProtocol] and isn't predetermined by some previous variable (like in my example above).
So anyways, all this to ask: What's the best way around this? How can I assign an array of structs (that conform to some protocol) to another array whose type is just "an array of things that conform that protocol".
I'm fairly new to Swift so I may be missing something trivial.

I usually just map the array to the type I need:
var protocolArray: [MyProtocol] = structArray.map { $0 as MyProtocol }
When you do that, you can actually get rid of the type annotation, so that the expression as a whole isn't actually that much longer:
var protocolArray = structArray.map { $0 as MyProtocol }
Swift won't automatically convert between array types, even if they are compatible. You have to be explicit about it one way or another.

Related

Is there any way in Swift to define a collection which holds elements with different data types?

In my specific case, I want something like:
var collectionWithDifferentTypes: [ObservableObject] = []
var elementOfTypeAWhichConformsToObservableObject = TypeA()
var elementOfTypeBWhichConformsToObservableObject = TypeB()
collectionWithDifferentTypes.append(elementOfTypeAWhichConformsToObservableObject)
collectionWithDifferentTypes.append(elementOfTypeBWhichConformsToObservableObject)
But letting arrays conform to ObservableObject is not possible. As the docs state, arrays, sets, and dictionaries can only contain elements of the same type. Is there any way in swift to have a collection similar to the one I've described above?
The reason you are getting this error is that ObservableObject specifies an associated type ObjectWillChangePublisher that has to be defined in classes that conform to the protocol. There's an annoying trait of Swift that any protocol that specifies an associated type can't be used as a generic parameter since the runtime needs to know how the associated type is defined in order to effectively use it.
In order to use such a protocol as a generic type, you have to do what the error message specifies and use it as a generic constraint. That means that wherever you are defining the array has to be made into a generic context using ObservableObject as a constraint.
(class field)
class SomeClass<T: ObservableObject> {
var myArray: [T] = []
}
(function variable)
func doAThing<T: ObservableObject>() {
var myArray: [T] = []
}
(See this article for a more in-depth explanation on what this error means.)
Of course, there's always the nuclear option of just defining the array as [Any].
Two ways I can think of. If you subclass TypeB with TypeA then you could use var collectionWithDifferentTypes: [TypeA] = [] or if they both conformed the same protocol. No need for the subclassing. Just use var collectionWithDifferentTypes: [protocolName] = []

Why shouldn't I make my custom struct with sequence object?

I want my struct to be created with sequence object using type inference. like:
var words: Buffer = ["generics", "ftw"] // type should be Buffer<String>
so I created a struct like this
struct Buffer<Element> {
init<S>(_ s: S) where Element == S.Element, S : Sequence {
}}
I just wrote code like above for testing generic init.
the code works when I create a struct like this:
var words = Buffer(["generics", "ftw"])
but I can't create a struct like this:
var words: Buffer = ["generics", "ftw"]
or
var words: Buffer<String> = ["generics", "ftw"]
the compiler complains me:
contextual type 'Buffer' cannot be used with array literal
I think I gave enough information to compiler as:
hey compiler, you'll gonna receive a Sequence. and the Element I mentioned is Sequence's Element.
what am I missing?
To use the syntax sugar, you have to adopt the ExpressibleByArrayLiteral protocol, which will make your struct able to be initialized using an array literal.
extension Buffer: ExpressibleByArrayLiteral {
typealias ArrayLiteralElement = Element
init(arrayLiteral: Element...) {
self.init(arrayLiteral)
}
}
After adding this extension, code like
var words: Buffer = ["generics", "ftw"]
or
var words: Buffer<String> = ["generics", "ftw"]
will compile.
Note that this does not mean that words is an array, although it is initialized with an array literal. If you assign the words variable to a variable that is of the Array type, your code will not compile, and vice versa. Also, note that if you don't infer the type Buffer in the variable declaration, it will be inferred as Array, so you must include Buffer in the variable decleration.

What is the type of a Swift Type?

I want to pass an array of types to a function - but I'm not sure what is the type of a Swift Type.
Let's say I have two classes:
MyClass and AnotherClass.
I want an array like this [MyClass, AnotherClass].
What will be the type of the array?
It would be of AnyClass type which is base for all object types.
func checkClassTypes(types: [AnyClass]) {
types.forEach { type in
// Your custom classes don't necessarily have to subclass from NSObject.
print(type is NSObject.Type)
}
}
checkClassTypes([MyClass.self, AnotherClass.self])
If you need to contain only class types, AnyClass will works:
class MyClass {
//...
}
class AnotherClass {
//...
}
let classTypes: [AnyClass] = [MyClass.self, AnotherClass.self]
If you want to include some value types, you may need to use Any or Any.Type:
let types: [Any] = [Int.self, String.self, MyClass.self, AnotherClass.self]
let anyTypes: [Any.Type] = [Int.self, String.self, MyClass.self, AnotherClass.self]
But all three types, AnyClass, Any or Any.Type, are hard to use. You will soon find it is very hard just to instantiate a class in the array.
I'm not sure I understand your question about Swift Types correctly, but Swift doesn't specify a Type as being a certain kind of variable. Swift defines that variables in code can have a certain type, which can be a float, a string, integer, etc. When it is said that Swift is type safe and infers a type, this means that Swift requires you to be clear on the type of values that you store in your variables (type safety) and that Swift will try to infer what type a variable is when you specify a value (type inference).
The type of your variable can also be more complex, like an enum, a struct or a class. Swift even sees an array or other collections as types. In this sense a Type could be defined as a piece of memory in which a constant or a variable (or a collection of these) is stored. A type can be different things. The great thing about having a Type as a constant/variable in which I store information, is that I can now defer the type of a variable until runtime, rather than that I have to specify it in my code. Or I can specify a structure of a generic type and later I can define different kinds of variables with the same structure, but of different types. Please, read for a more detailed understanding of dealing with types the Swift programming language from Apple. It is free and can be downloaded in the iBooks Store.
Now coming back to your second question of an array of [MyClass, AnotherClass] is kind of like deferring the types of your array until later. You can initially define your array as an array of AnyObjects or more swift-like AnyClass, like
myArray = [AnyClass]()
Later you can put different types of variables/objects in that array, like a Double, a String, or an object with a specific class.
You should be careful about building such an array, however. This array is not type safe and you haven't specified the type of object at a certain index. To prevent xcode having trouble with your code and your program from crashing, you should always type cast the individual objects in your array. For instance like
if let myObject = myArray[2] as? myClass { do something }
If you don't typecast your array both xcode and your program will do weird things.
I hope this answered your question.
Kind regards,
MacUserT
I'm guessing that you really want to create an array of instances of the two classes rather than an array of the class definitions. If so, you will be creating an array of Any or AnyObject, not AnyClass.
class MyClass {
}
class OtherClass {
}
let m = MyClass()
let o = OtherClass()
let array1 = [m, o] as [Any]
// or
let array2 = [m, o] as [AnyObject]

How to assign a value to [any]

When I set c to a
var a: [Any]
var c: Array<PostCategory>
error shown:
cannot convert value of type 'Array' to expected argument type
[Any]
how to solve the problem?
The error message is a bit misleading but try initializing the array before assigning it:
var c: Array<PostCategory> = []
...or...
var c = Array<PostCategory>()
I bet your PostCategory is a struct. Apparently struct arrays aren't convertible to an Any array. This is weird because all types conforms to the Any protocol.
If you change the PostCategory to a class instead, it should work fine. You might need to create a new initializer for the class though, since classes doesn't give you the same default initializer as a struct does.

Swift [1,2] conforms to AnyObject but [Enum.a, Enum.b] does not

I'm in AppDelegate, trying to pass a reply to a WatchKit Extension Request. I cannot use an array of enums as the value in a Dictionary whose values are typed as AnyObject. Experimenting in a Playground shows this:
enum E : Int {
case a = 0
case b
}
var x : AnyObject = [0, 1] // OK
var y : AnyObject = [E.a, E.b] // [E] is not convertible to AnyObject
Of course I can work around this by converting my enums to strings or numbers, but why is this a type error in Swift?
AnyObject exists for compatibility with Objective-C. You can only put objects into an [AnyObject] array that Objective-C can interpret. Swift enums are not compatible with Objective-C, so you have to convert them to something that is.
var x: AnyObject = [0, 1] works because Swift automatically handles the translation of Int into the type NSNumber which Objective-C can handle. Unfortunately, there is no such automatic conversion for Swift enums, so you are left to do something like:
var y: AnyObject = [E.a.rawValue, E.b.rawValue]
This assumes that your enum has an underlying type that Objective-C can handle, like String or Int.
Another example of something that doesn't work is an optional.
var a: Int? = 17
var b: AnyObject = [a] // '[Int?]' is not convertible to 'AnyObject'
See Working with Cocoa Data Types for more information.