June 24, 2014
Understanding Swift Array Immutability
Update, July 9: Apple has made big changes to Swift’s array implementation in the latest Xcode beta – happily, the descriptions below no longer apply.
Swift has an interesting definition of immutable when it comes to arrays. Both Swift and Objective-C have both mutable and immutable arrays, but each language in fact both allows and limits your ability to change an immutable array in different ways.
let names = ["Thomas", "Percy", "Rosie", "Daisy"] // immutable!
> [Thomas, Percy, Rosie, Daisy]
names[0] = "Edward" // immutable, adj., unable to be changed
> [Edward, Percy, Rosie, Daisy]
// ?!?!
(Im)mutability in Objective-C
The Foundation frameworks in Objective-C provide both mutable (NSMutableArray
) and immutable (NSArray
) classes for arrays. Here the immutability of NSArray
works as you might expect: once an NSArray
variable has been declared, it can’t be modified in any way – items can’t be added, removed, or changed.
If you need a modified version of the array you’ve declared, you can re-assign a modified value back to your variable:
NSArray *names = @[@"Shiny", @"Tiny", @"Don", @"Buddy"];
> [Shiny, Tiny, Don, Buddy]
names = [names sortedArrayUsingSelector:@selector(compare:)];
> [Buddy, Don, Shiny, Tiny]
but trying to reassign values of the array directly gets you a compiler error:
[names setObject:@"Gilbert" atIndexedSubscript:0];
> No visible @interface for 'NSArray' declares the selector 'setObject:atIndexedSubscript:'
Constants in Swift
Immutability in Swift comes from a slightly different direction. At its most basic level, Swift makes a distinction between variables (declared with var
) and constants (variables that have been instead declared with let
). The Swift compiler essentially locks the initial value given to a constant, preventing you from writing code that changes the value of a constant after it has been set:
let treeHeight: Int = 10
> 10
treeHeight = treeHeight + 2
> error: cannot assign to 'let' value 'treeHeight'
> treeHeight = treeHeight + 2
> ~~~~~~~~~~ ^
Constant structs and dictionaries are similarly constrained – once you’ve declared them, their initial values are locked in place and guaranteed to be constant throughout the rest of their persistence:
let size: CGSize = CGSizeMake(30, 20)
size.width *= 2 // error
> (can't change struct properties)
let aToZ = [ "a": "Apatosaurus", "b": "Brachiosaurus", "c": "Corythosaurus"]
aToZ["d"] = "Deinonychus" // error
> (can't add new keyed values)
aToZ["a"] = "Allosaurus" // error
> (or change existing ones)
Constant arrays, shifting contents
Wanted: t-shirt that reads SWIFT COLLECTION IMMUTABILITY IS A LIE.
— Dave Peck (@dangerdave) June 12, 2014
When it comes to declaring a Swift array as a constant, however, those constraints are loosened considerably. Even though arrays are declared as a struct
, “constant” arrays are only constrained in that you can’t change the size of the array (or reassign a new array to the constant) – the existing contents of the array are fair game for manipulation:
let aToZ = ["Apatosaurus", "Brachiosaurus", "Corythosaurus"]
> [Apatosaurus, Brachiosaurus, Corythosaurus]
aToZ[0] = "Allosaurus" // ok!
> [Allosaurus, Brachiosaurus, Corythosaurus]
aToZ.sort() { $0 > $1 } // ok!! (really!!)
> [Corythosaurus, Brachiosaurus, Allosaurus]
aToZ += "Deinonychus" // error
> (can't increase size of array)
let dino = aToZ.removeLast() // error
> (can't decrease size of array)
The Swift Programming Language explains this behavior:
You are still not allowed to perform any action that has the potential to change the size of an immutable array, but you are allowed to set a new value for an existing index in the array. This enables Swift’s
Array
type to provide optimal performance for array operations when the size of an array is fixed.
So constant arrays in Swift stand in a class category place of their own – immutable in one sense, but still open to changes and manipulations that may be unexpected for a programmer unfamiliar with this distinction. I’ll take a look at a strange side-effect of this behavior in my next post, about array copying ambiguity.