AnyType error in Swift array - arrays

Trying to make and shuffle a card deck in Swift, made up of Card objects in an array called cardDeck. Then I want to shuffle it. Relevant code:
var cardDeck = [card]()
for ind in 1 ... 4
{
for ind2 in 1 ... 13
{
cardDeck.append(card(number: ind2, color: ind))
}
}
cardDeck = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(cardDeck)
"Cannot assign value of type [anyobject] to logic.card" ("logic" is the overall class name, and Card is another class within it)
Anyone know whats wrong? I guess the array is not an AnyObject type of array since I declared it as containing cards, right?

The problem is that arrayByShufflingObjectsInArray takes an [AnyObject] and returns an [AnyObject].
Therefore its going to completely throw away your [Card] type information that you provide as an input, and thus give you an error about not being able to convert types when you try to assign the output back to your original array. I suspect this is due to the method being implemented in Objective-C, not Swift.
A more Swifty version of the method would look like this:
func arrayByShufflingObjectsInArray<T:AnyObject>(array:[T]) -> [T] {
...
}
Through using generics, you can preserve the type information that you pass in to begin with, returning the same type that you inputted. In fact, you can write your own extension of GKRandomSource in order to do just that:
extension GKRandomSource {
func arrayOfSameTypeByShufflingObjectsInArray<T:AnyObject>(array:[T]) -> [T] {
return arrayByShufflingObjectsInArray(array) as! [T]
}
}
(Feel free to come up with a more catchy name for the method)
The force downcast is used as an ugly solution to the problem – which is why I recommend creating an extension, rather than using this directly. It cannot crash, as the output array is guaranteed to contain objects of the same type as the input (as the array you pass in can only contain a single type).
You can now use it like so:
cardDeck = GKRandomSource.sharedRandom().arrayOfSameTypeByShufflingObjectsInArray(cardDeck)

Related

Declaring array with specific type but arbitrary number of dimensions

I am curious if there is any way in Swift to declare a multi-dimensional array that has a specific type(for this example we'll say Float) but isn't confined to a fixed number of dimensions(E.g., let arr: Array<Array<Float>>)?
I'd like to create a function that accepts a multi-dimensional array and currently the only way I'm aware of is:
func testFunction(arr: [Any]) {}
This is problematic though as it doesn't specify the actual scalar type of the array. As the type Any implies, this function can accept anything that is stuck inside of an Array. Any ideas?
Not with just an Array. Each array needs to know what type it contains. To contain an arbitrary number of dimensions, each array would need to be able to hold either a Float or an Array.
You could possible do something custom with an enum that has two cases.
enum MultidimensionalArray<Element> {
case array([MultidimensionalArray<Element>])
case values([Element])
}
but you'll probably have add a bunch more methods to it based on how you want to use ie.
How about generics
func testFunction<T>(arr: [T]) { }
T is a specific type and can be anything even
let foo = [[[[Float]]]]()
testFunction(arr: foo)
I found a way that makes it pretty simple to do what I'm after with some generic constraints and extensions.
protocol TypedNDArrayProtocol {}
// Make the type you are trying enforce conform to our protocol
extension Float: TypedNDArrayProtocol {}
// Now make Array conform to the same protocol while constraining
// it to only elements that *also* conform to this protocol
extension Array: TypedNDArrayProtocol where Element: TypedNDArrayProtocol {}
func testFunction(arr: Array<TypedNDArrayProtocol>) {}
This creates a sort of recursive conformance where the Array is allowed to accept either another Array, or a Float. If it is an Array, that Array can again only accept another Array or a Float and so on. This way you can use an arbitrary number of dimensions, but it must eventually terminate with a specific type; in this case a Float.
// This will work
testFunction(arr: [1.0, 2.0])
// This will also work
testFunction(arr: [[[3.0], [4.0]], [[5.0], [6.0]]])
// This will NOT work
testFunction(arr: [[[3], [4]], [[5], [6]]])
// Neither will this
testFunction(arr: [[["3"], ["4"]], [["5"], ["6"]]])

Swift: AnyObject as? Array casting fail if the array is empty (not nil)

This is the case: Suppose that I get a variable as an AnyObject or Any. Then, I must to cast the variable to know if it's an array with objects of a specific type.
func myFuction(receivedObject: AnyObject) {
if let validDogs = receivedObject as? [Dog] {
print("Received object is an array of dogs")
// Do something with valid dogs
}
if let validCats = receivedObject as? [Cat] {
print("Received object is an array of cats")
// Do something with valid cats
}
}
This code works if the received object is not an empty array (not nil), but fails if the received object is an empty array because my log prints this two messages:
"Received object is an array of dogs"
"Received object is an array of cats"
Which suggest that for an empty array the cast fails. So, is there a way to fix that?
Code like this strongly suggests a deep problem in your type design, and should be resolved by getting rid of the AnyObject. It is very rare that passing AnyObject is the correct tool.
You've said something very important here:
This code works if the received object is not an empty array (not nil)
An empty array is not the same thing as nil. If you pass nil, that's an Optional. Optional<[Cat]> is not the same thing as [Cat] and you should not expect it to consistently as? cast, particularly if it's nil. If this came from ObjC, that nil is bridged to an actual Obj-C nil (which is just the value 0), and the runtime has literally nothing to work with.
You said that you receive both log lines, though. That suggests both as? casts are succeeding, not failing. If that's the case, I assume this is an NSArray, and an empty NSArray can legitimately be as? cast to an array of anything (NSArray has no element type internally). So the above is expected.
If it is really is optional, then to the question of "how do I determine that it's an optional and then unwrap it and then work out if it's [Cat]," the answer is "stop; you've gone too far with AnyObject." Start by redesigning this so you don't need that, which generally means figuring out your types earlier.
The correct way to do what you're trying to do is generally with overloads, not as? casting:
func myFuction(receivedObject: [Dog]) {
print("Received object is an array of dogs")
// Do something with valid dogs
}
func myFuction(receivedObject: [Cat]) {
print("Received object is an array of cats")
// Do something with valid cats
}
You cannot just pass AnyObject to the above functions. You need to know the types of the things you're working with. (Your comment that you're stripping the types with as! AnyObject suggests that you do know the types already, and are actively throwing them away and trying to later recover them. If this is the case, the above code is exactly the right thing. Don't throw away the types.)
While there are some corner cases where you cannot know those types (because you literally are accepting "any object at all"), in the vast majority of cases failure to know your types suggests a design problem.
#RobNapier's answer is correct from a good programming point of view - I see no reason whatever to be doing what you are attempting, but it shows up something interesting.
Firstly, you cannot call your function with an array of anything - arrays, as structs, do not conform to AnyObject (classes do). However, you call the function by a forced cast - as anything can be cast to AnyObject (presumably by wrapping the struct in a degenerate class), the call compiles.
Secondly, your question has the inference the wrong way round - you say "Which suggest that for an empty array the cast fails." - not so, the cast succeeds in both cases...
Let's see what actually gets passed in:
class Pet { var name: String { return "" } }
class Dog: Pet { override var name: String { return "Fido" } }
class Cat: Pet { override var name: String { return "Cfor" } }
func myFuction(receivedObject: AnyObject) {
print("myFuction called with \(receivedObject)") // ***
if let validDogs = receivedObject as? [Dog] {
print("Received object is an array of dogs")
// Do something with valid dogs
}
if let validCats = receivedObject as? [Cat] {
print("Received object is an array of cats")
// Do something with valid cats
}
}
var a: [Cat] = [Cat()]
//myFuction(receivedObject: a) // Argument type '[Cat]' does not conform to expected type 'AnyObject'
myFuction(receivedObject: a as! AnyObject) // Forced cast from '[Cat]' to 'AnyObject' always succeeds; did you mean to use 'as'?
a = []
myFuction(receivedObject: a as! AnyObject) // Forced cast from '[Cat]' to 'AnyObject' always succeeds; did you mean to use 'as'?
Output:
myFuction called with (
"__lldb_expr_97.Cat"
)
Received object is an array of cats
myFuction called with (
)
Received object is an array of dogs
Received object is an array of cats
So, when it's called with a non-empty array, the type is inferred from the members and the cast only succeeds if the members are of the right type. An empty array has no members, and thus could equally well be [Cat] or [Dog] or indeed [String].
Try
let b = [Cat(), Dog()]
myFuction(receivedObject: b as! AnyObject)
This prints
myFuction called with (
"__lldb_expr_107.Cat",
"__lldb_expr_107.Dog"
)
(and does not print a cast "success")
The long and the short of it is
A) the force as! AnyObject effectively throws away the array type and passes something resembling a tuple of elements which your let _ = as? is able to reassemble into an array from the types of the elements.
B) re-read #RobNapier's answer - that's the way to go!

Is there a way to override Array to String casting in Swift?

I'm playing around with Swift trying to make it look more "dynamically typed" – just for fun, no production value expected.
Now I'm stuck with overwriting behavior of converting builtin types to String.
For example, I'd like to see this output for Array:
let nums = [1, 2, 3]
print(nums) // "I'm an array"
So far I tried to
make an extension to NSArray (not compiles)
implement CustomStringConvertible (not compiles)
make an extension to Array (compiles, changes nothing)
Seems like I'm on the wrong path:
extension Array: CustomStringConvertible {
public var description: String { return "An array" }
}
gives the warning:
Conformance of 'Array' to protocol 'CustomStringConvertible' was already stated in the type's module 'Swift'
Is this doable in Swift?
This does not work because Array overrides description. If array did not override it then it would print "An array". The class method 'wins' over the extension.
extension Array {
public var description: String { return "An array" }
}
You could create a Wrapper class for your array. It's a workaround but doesn't override array's description itself.
class ArrayWrapper<T> : CustomStringConvertible{
var array : Array<T> = Array<T>()
var description: String { return "An array" }
}
You could then use it like this.
var array = ArrayWrapper<Int>()
array.array = [1,2,3]
print(array) //prints "An Array"
print(array.array) //still prints "[1, 2, 3]"
Seems like it is not possible to neither subclass nor overwrite a builtin Array type.
We could use a wrapper though (credits to #Yannick) and implement a ArrayLiteralConvertible protocol, so we could use square brackets for initialization.
struct Array<T> {
let array: [T]
}
extension Array: ArrayLiteralConvertible {
init(arrayLiteral elements: T...) {
self.array = elements
}
}
extension Array: CustomStringConvertible {
var description: String { return "An array" }
}
let array = [1,2,3]
print(array) // "An array\n"
Swift does not allow you to provide a protocol override for a type that already declares that protocol:
Imagine that you have a set of integers and you want to override the default implementation of CustomStringConvertible for a Set
The simplest answer to all of this is: you can't, and that's a feature not a bug. Set isn't your type and you don't get to change its already-defined behavior like this.
The capability to intercept and alter the behavior of a type to do something different to what that type is originally designed to do is powerful, but fraught with downsides. In this case, you've chosen a fairly benign thing to alter, but in other cases doing this could do all sorts of damage to assumed invariants of a type by changing its program-wide behavior.
The best way to alter a type to do something different like this is to wrap it in your own low-cost struct. Unfortunately this does mean writing a fair amount of boilerplate for forwarding – though less and less these days as we gain features like synthesized conformances and dynamic member lookup. Hopefully someday we'll get features that do make easier to create a newtype with customized behavior.
https://forums.swift.org/t/whats-the-best-way-to-override-customstringconvertible-for-a-collection/24844

Swift 2 - Check Type of empty Array (Introspection)

I'm currently working on introspection in Swift 2 and have Problems getting the specific type for an Array (in this example an Array<String>).
var prop = obj.valueForKey("strings")!
if prop is Array<String> {
println("true")
}
if prop is Array<Int> {
println("true")
}
Output is:
true
true
while it should be
true
false
Is there a way to find out the type for the members of the Array? For example, if I daclared the Array as Array<String> I want to get String or at least be able to check if it is.
MirrorType also did not lead to any success on that by now.
There are 2 ways to achieve what you want:
if prop.dynamicType == Array<Int>.self (or [Int].self) which is better than if prop.dynamicType == [Int]().dynamicType { because [Int]() creates an unused instance of "array of integers".
Typically, when you check if an array is specific-typed, you plan to use it
in a certain way (as a result, you will likely cast your array to
[Int]). Having that said, I recommend using if let arrayOfInts =
prop as? Array<Int> {. Using this construct, you will check for
type compatibility and prepare your array to be treated in a special way (using the casted arrayOfInts reference).
Anyway, it's up to you to decide what to do.
Perhaps what you want is the type of each individual item inside the Array rather than the type of the Array itself? If you are using collection types in Swift, all the items stored in the Array (or Dictionary) are of the same type (except if you declare the Array as Array for example to break the rules... which is not usually necessary, or wanted).
By declaring an Array with its initially values you are automatically telling the compiler what type they are. If you do something like this:
let obj = [1,2,3]
var property = obj[0]
if property is String {
print("true")
}
if property is Int {
print("true")
}
The compiler will already tell you that property is String always fails, and there is actually no need to do that test (because we already know that it will always fail).
If you are working with Objective-C APIs and types on the other hand, there may be occasions where you will need to test for type, this is a good example of testing for type in an Objective-C collection that has items of different types:
let userDefaults = NSUserDefaults.standardUserDefaults()
let lastRefreshDate: AnyObject? = userDefaults.objectForKey("LastRefreshDate")
if let date = lastRefreshDate as? NSDate {
print("\(date.timeIntervalSinceReferenceDate)")
}
Hope this helps.

Cannot pass any array type to a function which takes [Any] as parameter

I have an array of type [String]
let names = ["Joffrey", "Cersei", "Mountain", "Hound"]
I have a function which takes an array of [Any] type.
func printItems(items: [Any]){
for item in items {
print(item)
}
}
Now when I call the function with names as parameters,
printItems(names)
I get an error Cannot invoke 'printItems' with an argument list of type '([String])'.
Any is just a typealias for a protocol which all types implicitly conform to.
Thoughts?
This is a surprising limitation of Swift. You can't cast an array to type [Any] so you can't pass it to a function taking type [Any]. You can use map to cast each item of the array:
printItems(names.map {$0 as Any})
But, the right way to do this in Swift is to use Generics:
func printItems<T>(items: [T]) {
for item in items {
print(item)
}
}
let names = ["Joffrey", "Cersei", "Mountain", "Hound"]
let numbers = [3.1416, 2.71818, 1.4142, 1.618034]
printItems(names) // This now works
printItems(numbers) // This works too
While every type conforms Any, this is not the same as it being a universal implicit superclass that all types inherit from.
When you cast a type to a protocol, you create a new value with a different structure. So for a string to be of type Any, it needs to be physically transformed from the String representation:
sizeof(String) // 24 bytes (on 64-bit, anyway)
to the Any representation:
sizeof(Any) // 32 bytes, includes some meta data
// about what the type really is
Since value types are held directly in the array, the array would be a very different shape so under the hood the compiler would have to do the equivalent of this:
names.map { $0 as Any } // create a new array, with the Any versions
Swift could perhaps automate this process for you (it does if you pass a single variable into a function that takes Any). But personally I’m glad it doesn’t, I’d rather this be more explicit – suppose your array was huge, this would be a lot of processing happening implicitly under the hood.
This is different from when you have an array of reference types, all of which are pointers to the actual data and so all the same size, and which need no transformation when upcasting:
class C { }
class D: C { }
let d = D()
let c: C = d
unsafeBitCast(d, UnsafePointer<Void>.self) // these value will
unsafeBitCast(c, UnsafePointer<Void>.self) // be the same
So saying “this array of [D] is really an array of [C]” is just a matter of the compiler agreeing the types can be substituted, no data transformation needs to take place:
// so this works fine,
// no runtime transformation needed:
func f(cs: [C]) { }
let ds = [D(),D()]
f(ds)
But protocols still are different from superclass references when used with classes:
protocol P { }
extension C: P { }
sizeofValue(C()) // 8 bytes (just a pointer)
sizeofValue(C() as P) // 40 bytes
func g(ps: [P]) { }
g(ds) // won’t compile, needs transformation
You need to explicitly put in your let declaration that you
are declaring an Any array and not a specific type array.
With your current declaration, Swift will evaluate it to [String] array.
and not as [Any] array.
To fix your problem just do the following:
let names : [Any] = ["Joffrey", "Cersei", "Mountain", "Hound"]
If you want to retain your let declaration, use AnyObject
in your printItems function and it will be accepted by Swift.
func printItems(items: [AnyObject]){
for item in items {
print(item)
}
}

Resources