How can I cast an NSMutableArray to a Swift array of a specific type?

IosObjective CSwift

Ios Problem Overview


I am migrating my iOS project to Swift. I am doing this class by class. When I call Objective C methods from Swift, a lot of Objective C types are converted to their Swift counterparts.

In my case an Objective C NSMutableArray gets converted to Swift's Array<AnyObject>. Now here comes my problem. Within my Swift class, I get such an array back from an Objective C object. Now that I am in the Swift world, I would like to cast this array to a specific type instead of AnyObject, because I know for sure what kind of objects exist in this array.

The compiler won't let me do that! Let me simplify my problem by saying I want to cast to an array containing strings. This is what I tried:

var strings = myObjcObject.getStrings() as [String]

I get the following error from the compiler:

> 'String' is not identical to 'AnyObject'

I would have to agree with the compiler, since String is indeed not identical to AnyObject. But I don't see why that is a problem. I can downcast AnyObject to String if I want, right?

I also tried:

var strings = myObjcObject.getStrings() as? [String]

This seems to be a step in the right direction, but getStrings() returns an NSMutableArray so I get the following error:

> 'NSArray' is not a subtype of 'NSMutableArray'

Is there any way to do what I am trying to do here?

Ios Solutions


Solution 1 - Ios

You can make this work with a double downcast, first to NSArray, then to [String]:

var strings = myObjcObject.getStrings() as NSArray as [String]

Tested in a Playground with:

import Foundation

var objCMutableArray = NSMutableArray(array: ["a", "b", "c"])
var swiftArray = objCMutableArray as NSArray as [String]

Update:

In later versions of Swift (at least 1.2), the compiler will complain about as [String]. Instead you should use an if let with a conditional downcast as?:

import Foundation

var objCMutableArray = NSMutableArray(array: ["a", "b", "c"])
if let swiftArray = objCMutableArray as NSArray as? [String] {
    // Use swiftArray here
}

If you are absolutely sure that your NSMutableArray can be cast to [String], then you can use as! instead (but you probably shouldn't use this in most cases):

import Foundation

var objCMutableArray = NSMutableArray(array: ["a", "b", "c"])
var swiftArray = objCMutableArray as NSArray as! [String]

Solution 2 - Ios

compactMap is your friend in Swift 4.1 and above, as well as in Swift 3.3-3.4 for that matter. This means that you don't have any double or forced casting.

let mutableArray = NSMutableArray(array: ["a", "b", "c"])
let swiftArray: [String] = mutableArray.compactMap { $0 as? String }

In previous versions of Swift, 2.0-3.2 and 4.0, you'll want to use flatMap for this purpose. The usage is the same as compactMap:

let swiftArray: [String] = mutableArray.flatMap { $0 as? String }

Solution 3 - Ios

With Swift 1.2 the following will work:

let mutableArray = NSMutableArray(array: ["a", "b", "c"])
let swiftArray = NSArray(array: mutableArray) as? [String]

Solution 4 - Ios

let mutableArray = NSMutableArray()

mutableArray.add("Abc")
mutableArray.add("def")
mutableArray.add("ghi")

if let array = mutableArray as? [String] {
    print(array)    // ["Abc", "def", "ghi"]
}

Solution 5 - Ios

in Xcode 6.3 i used the following:

var mutableArray = NSMutableArray(array:"1", "2", "3")
let swiftArray = mutableArray as AnyObject as! [String]

Solution 6 - Ios

for swift 3

you may consider the following code

let array: [String] = nsMutableArrayObject.copy() as! [String]

Solution 7 - Ios

In my case compiler wanted me to write it like this to suppress all warnings and compilation problems, so not just that exclamation marks even if field imagesField is already declared with one, but also parentheses and "as!" to make it sure that nobody complains.

(imagesField!.images as! [UIImage]) 🤮

It made me quite uncomfortable... Swift could be nicer, its new language so... I made extension:

 public static func cast(_ object: Any) -> Self {
        return object as! Self
    }

Assigned it to Array:

extension Array: CSLang {
}

And now I can write the same statement like this with the same effect:

[UIImage].cast(imagesField.images)

Like it or not, this is my way, less question and exclamation marks, better. I made also unit test:

func testCast() {
    let array: NSMutableArray? = NSMutableArray()
    array?.add("test string 1")
    array?.add("test string 2")
    let stringArray: [String] = [String].cast(array)
    XCTAssertEqual("test string 2", stringArray[1])
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTom van ZummerenView Question on Stackoverflow
Solution 1 - IosMike SView Answer on Stackoverflow
Solution 2 - IosSimon RiceView Answer on Stackoverflow
Solution 3 - Iosaverage JoeView Answer on Stackoverflow
Solution 4 - IosLeo DabusView Answer on Stackoverflow
Solution 5 - IosNazariy VlizloView Answer on Stackoverflow
Solution 6 - IosAmr AngryView Answer on Stackoverflow
Solution 7 - IosRenetikView Answer on Stackoverflow