Scripting in Swift: Casting, Any, and implicit mutability

When I first started coding in Swift, I was surprised by the number of times that Xcode’s helpful uncle would pop up and tell me that I had to add a ! or ? to my code. Although I had read about these cast operators, it has taken a while for their use and significance to sink in.

Use them correctly, and your code should be bombproof, handling whatever is thrown at it; get them wrong, and your app will keep crashing for apparently inexplicable reasons. They’re one of Swift’s great strengths, but also an Achilles heel.

The great majority of the time that you are writing scripts, you’ll work within strong typing, and there should be no problems. The user enters a string into a text box, you retrieve it, and process it as a string.

Then, you’ll have to work with a function that could fail, and return nil instead of the value you’re expecting. You can put it inside a try … catch … structure, or handle the optional result robustly, perhaps using if let var = function() { } else { }

These all come to a head when you have to work with a variable of type Any. To do almost anything with an Any requires that you cast it to a more useful type, as you can’t even get a string version using the non-existent Any.description.

My case was in trying to handle xattrs, which are returned simply as Data. You can cast them as NSData
data2 = data1 as NSData
and then show data2.description, or try to get a UTF-8 string from them
if let string2 = String.init(data: data1, encoding: .utf8) { }
and note that the former is guaranteed, so can use as, but the latter could return nil, which has to be handled properly.

But if they’re a binary property list, for example, all those will succeed in doing is giving a tantalising glimpse at some of the contents. To convert amorphous Data to an accessible property list, you need the function PropertyListSerialization.propertyList() which returns a dreaded Any, and throws.

If you know that property list is going to be a dictionary, you can try casting it as, say, an NSDictionary
if let theDict = PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? NSDictionary { }
and this is where you must have a very clear concept of what happens in casting, and the difference between as, as! and as?

Using as! will fail in the event that the cast doesn’t work, and that will crash your app. Using as? will not fail, but in the event that the cast cannot be performed, it will return nil. If you don’t handle that nil case, then your app will crash.

Chances are, though, the property list could equally well be an NSArray, NSString, NSNumber, or something else. To handle that, you need a switch to handle the cases:
var theStr = "Not a binary Plist\n"
if PropertyListSerialization.propertyList(data, isValidFor: PropertyListSerialization.PropertyListFormat.binary) {
do {
let thePlist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil)
switch thePlist {
case let thePPlist as NSArray:
theStr = "Binary Plist NSArray:\n" + thePPlist.description
case let thePPdict as NSDictionary:
theStr = "Binary Plist NSDictionary:\n" + thePPdict.description

// and so on
theStr = "Binary Plist format not known.\n"
} } catch { theStr = "Not a binary Plist\n" }


Another seemingly minor issue which can cause more serious failures is that of implicit mutability. Swift has two variations of collections and other types, those that are mutable, and those that remain immutable. The Swift manual recommends that you create immutable collections wherever the collection and its contents do not change, and that is good practice.

There are times, though, when this can trip you up. Declare an array as a constant using let, and that implict immutability will break when that array is passed to a function which expects a mutable. Such implicit mutability may propagate:
let data1 = try theSourceURL.extendedAttribute(forName: item)
let data2 = data1 as NSData

and you’ll find that data1 and data2 are seen as being constants, and immutable. That can make downstream actions fail, in which case you may have to declare either or both as var rather than using let. Otherwise this can result in some fairly obscure bugs.