Facets of Swift, Part 3: Values and References

Tammo Freese
Swift Programming
Published in
10 min readJul 21, 2014

Facets of Swift is a series of articles on various aspects of the Swift programming language. This third article is about values and references in Swift. Originally I had planned to write about functions. I chose to write about values and references first as there are a few gotchas related to values in Swift that confuse even seasoned Objective-C developers.

Pass by Value

In C and Objective-C, values are passed everywhere, not references. What does the following code snippet do?

void foo(int i) {
i++;
}
// ...
int x = 1;
foo(x);
printf("%d\n", x);

It will print “1”, as foo gets passed the value of x in the parameter i. So foo has no way of changing what’s stored in the variable x. When we try to convert the code directly to Swift, we stumble over a compiler error:

func foo(i: Int) {
i++ // COMPILER ERROR
}
// ...
var x = 1
foo(x)
println(x)

In Swift, function parameters are constant by default. The function declaration above is shorthand for

func foo(let i: Int) {
i++ // COMPILER ERROR
}

It’s a good thing that the default changed that way: In C, especially beginners could be fooled too easily into thinking that the first example would return “2”, not “1”.

If we want to modify the value of i in the function body in Swift, we can define i as a variable:

func foo(var i: Int) {
i++
}
// ...
var x = 1
foo(x)
println(x)

Keep in mind that this still does not allow us to modify the value of the variable x that was passed in. Just as the C version, the code would now print “1”. What if we would like to change the value of the variable x?

Pass by Reference

In C, we would pass a pointer instead:

void foo(int *i) {
(*i)++;
}
// ...
int x = 1;
foo(&x);
printf("%d\n", x);

Now foo gets passed the address &x (a reference to x), and can dereference that address using (*i) to change the value. The code will now print “2”. Keep in mind that from a technical perspective, this is still a pass by value: We pass the pointer as value.

In Swift, we use the keyword inout to tell the compiler that the variable is an in-out parameter:

func foo(inout i: Int) {
i++
}
// ...
var x = 1
foo(&x)
println(x)

Compared to the C code, the Swift version looks a bit cleaner: While we still have to use & to pass a variable as an in-out parameter, we don’t need to dereference the parameter i to change the value. Inside the function, i feels like a variable, not a pointer.

In Swift, we use nothing or let to make a parameter constant, var to make it variable, and inout to make it an in-out parameter.

Constant Parameters in C

So the default in Swift is to have constant parameters. Can we have those in C as well? We can. But as it’s C, the story is a bit more complicated. Let’s start with this example:

void foo(CGSize s) {
NSLog(@"%@", NSStringFromCGSize(s));
}
// ...
CGSize size = {1, 1};
foo(size);

It will print “{1, 1}”. Imagine CGSize would be a big structure, so we want to be able to pass it to foo via a pointer, but without worrying about foo changing it. We can achieve that by using const:

void foo(CGSize const *s) {
NSLog(@"%@", NSStringFromCGSize(*s));
}
// ...
CGSize size = {1, 1};
foo(&size);

If we try to modify what s points to, we get a compiler error:

void foo(CGSize const *s) {
(*s).width = 0 // COMPILER ERROR
}

The placement of const is crucial here. If we write const after the pointer, the code would compile:

void foo(CGSize *const s) {
(*s).width = 0
}

To understand what’s going on, it helps to start reading from the variable name backwards. Still, covering all cases is quite a long list:

  • CGSize s: s is a CGSize (which can be modified in the function). This is comparable to var s: CGSize in Swift.
  • CGSize const s: s is a const CGSize, so it can’t be modified in the function. This is comparable to s: CGSize or let s: CGSize in Swift.
  • CGSize *s: s is a pointer to a CGSize, so both the pointer s and the CGSize structure pointed to can be modified inside the function.
  • CGSize *const s: s is a const pointer to a CGSize, so the pointer s can’t be modified inside the function, the CGSize structure pointed to can. This is roughly comparable to inout s: CGSize in Swift, as Swift hides the pointer from us, so we can’t modify it, but we can modify the value pointed to.
  • CGSize const *s: s is a pointer to a const CGSize, so the pointer s can be modified, the CGSize structure pointed to can’t.
  • CGSize const *const s: s is a const pointer to a const CGSize, so both the pointer s and the CGSize structure pointed to can’t be modified.

If you wondered about it, in all cases above we could replace CGSize const with its equivalent const CGSize.

We identified six cases, for three of which we don’t have a comparable construct in Swift. And that’s great:

  • CGSize *s would force us to expose the pointer that Swift hides so well.
  • CGSize const *s and CGSize const *const s can be used for performace optimizations in C: Instead of passing a copy of the struct’s value, we pass a pointer that does not allow the modification of the content. In Swift, we leave that optimization to the compiler, instead of polluting our code with it.

Now that we covered how constants work for structures and pointers, let’s have a look at what’s different for objects.

Objects

In Objective-C, we mostly work with objects which are subclasses of NSObject. Whenever we pass an object, or store it in a variable, we reference it using a pointer:

void foo(NSMutableString *s) {
}
// …
NSMutableString *string = [NSMutableString new];
foo(string);
NSLog(@”%@”, string);

An object variable should be able to hold an object of the respective class, or any subclass of it. Objects of different classes can have different sizes, so we can’t know the size. Therefore the compiler does not allow us to store the structure of an object in a variable directly, we have to use a pointer:

NSObject object1; // COMPILER ERROR
NSObject *object2; // OK

More importantly, we can’t use const to prevent changes to an object:

void foo(NSMutableString const *s) {
[s appendString:@"OH NOES"]; // No compiler error here!
}
// ...
NSMutableString *string = [NSMutableString new];
foo(string);
NSLog(@"%@", string); // Prints "OH NOES"

We can make the pointer a constant, but as we’ve seen above for structures, that does not prevent us from changing the object pointed to, only from changing the pointer:

void foo(NSMutableString *const s) {
[s appendString:@"OH NOES"];
}
// …
NSMutableString *string = [NSMutableString new];
foo(string);
NSLog(@"%@", string); // Prints "OH NOES"

In Swift, classes are reference types by default, so there is no need to write pointers:

func foo(s: NSMutableString) {
s.appendString(“OH NOES”)
}
// ...
var string = NSMutableString()
foo(string)
println(string) // Prints "OH NOES"

Swift has the additional advantage that s is required to be non-nil (for details, see the first Facets of Swift article on optionals). The three cases for parameters in Swift cover all we need for objects:

  • s: NSObject? is comparable to NSObject *const s in Objective-C. If we would like the parameter to be non-nil, we would use s: NSObject.
  • var s: NSObject? is comparable to NSObject *s in Objective-C. If we would like the parameter to be non-nil, we would use var s: NSObject.
  • inout s: NSObject? is comparable to NSObject **s, which is the short form of NSObject * __autoreleasing *s in Objective-C.

The last case shows how much cruft is removed in Swift. Bottom line: In Swift, objects are automatically passed by reference, which leads to better readable code, especially for inout parameters.

Gotcha: Focus on Values

When we look at Objective-C code, it almost always defines classes. For example, strings, arrays and dictionaries are objects of classes. That means that whenever we pass an object around in Objective-C, we use a pointer which needs memory management by ARC. There are only a few exceptions where values are used instead of objects, most notable primitive types like BOOL and NSInteger, and simple structures like CGPoint, CGSize and CGRect.

Swift has a focus on values. While it is still possible to define classes, the basic types of Swift are structures or enumerations that are passed around by value. The compiler may use references for performance, but that’s not our job anymore.

While values are easier to understand and to use than references, coming from Objective-C we are used to reference types. It may be confusing to see the semantics change. To illustrate the problem, let’s have a look at an example where we store a mapping from string to an array of strings. Here is the Objective-C code:

@interface StringLists : NSObject

- (void)addString:(NSString *)string forKey:(NSString *)key;
- (NSArray *)listForKey:(NSString *)key;

@end

@implementation StringLists {
NSMutableDictionary *_stringLists;
}

- (instancetype)init {
self = [super init];
if (self) {
_stringLists = [NSMutableDictionary new];
}
return self;
}

- (void)addString:(NSString *)string forKey:(NSString *)key {
NSMutableArray *list = _stringLists[key];
if (list == nil) {
list = [NSMutableArray new];
_stringLists[key] = list;
}
[list addObject:[string copy]];
}

- (NSArray *)listForKey:(NSString *)key {
return [_stringLists[key] copy];
}

@end

Notice the two copy calls. The first one prevents users of the class to accidently pass us an NSMutableString that they mutate afterwards. The second one is not strictly necessary, but a precaution against someone casting and changing the mutable array afterwards.

If we translate the Objective-C code one-to-one to Swift, a first try may look like this:

class StringLists {
var stringLists: [String: [String]] = [:]

func addString(string: String, forKey key: String) {
var list = stringLists[key]
if !list {
list = []
stringLists[key] = list
}
list!.append(string) // COMPILER ERROR
}

func listForKey(key: String) -> [String]? {
return stringLists[key]
}
}

In Swift, arrays, dictionaries and strings are structures with value-semantics. That means we don’t need to copy anymore. But that also means that coming from Objective-C, things may not behave as we would expect. The first gotcha is the compiler error. That’s because the forced value expression list! returns a value that we cannot mutate, comparable to C and Objective-C where we can’t mutate a value returned from a function directly. Here is some Objective-C code we would not expect to work:

[array count]++; // COMPILER ERROR
[view frame].size = CGSizeZero; // COMPILER ERROR

While the Swift compiler may change in the future to allow mutation of list!, for now we have to store it in a variable first:

func addString(string: String, forKey key: String) {
var list = stringLists[key]
if !list {
list = []
stringLists[key] = list
}
var unwrappedList = list!
unwrappedList.append(string)
}

But that would not work as well. A comparable error in Objective-C would be to try to set the size of a view’s frame to CGSizeZero like this:

CGRect frame = [view frame];
frame.size = CGSizeZero;

What’s of course missing is that we need to write the changed value back:

CGRect frame = [view frame];
frame.size = CGSizeZero;
[view setFrame:frame];

As arrays in Swift have value semantics, we need to write the changed value back as well:

func addString(string: String, forKey key: String) {
var list = stringLists[key]
if !list {
list = []
stringLists[key] = list
}
var unwrappedList = list!
unwrappedList.append(string)
stringLists[key] = unwrappedList
}

Now the code works, but it looks a bit messy. That’s because we translated from Objective-C one-to-one. Let’s clean it up:

func addString(string: String, forKey key: String) {
if var list = stringLists[key] {
list.append(string)
stringLists[key] = list
} else {
stringLists[key] = [string]
}
}

What we should remember: In Swift, arrays, dictionaries, and strings have value semantics.

While I was writing this article (before Xcode 6 beta 3), the array in Swift behaved in a way that most developers found strange. Now have sane value semantics for the array.

Still, I could not resist writing a bit about the old array semantics, and for which scenarios it made sense. That’s not relevant for today’s understanding of Swift though, so feel free to skip the last section below, and continue reading the next article of this series on functions!

Bonus Track: Swift’s Old Array

Before the third beta of Swift, the Array type had a behavior that most developers found weird. If we had a constant array defined with let, we could still change the values in the array, but we could not change its size. So this actually worked before:

let x = [2006, 90, 74, 54]
x.sort(<)
x[3] = 2014
println(x) // Printed [54, 74, 90, 2014]

What we could not do is change the size of the “constant” array, that is, we could not append a value, for example.

This behavior could actually be explained quite easily: An Array in Swift was like a struct with a pointer to the internal storage. With that in mind, everything made sense: a “change” would mean a change to the pointer, and that was only needed if the length of the array changed.

So why would anyone embrace such a weird behavior? My guess is not only performance, but that it simplified implementing functions that worked on arrays. If we used a range subscript on an array, we got back a slice that still allowed changing the original array:

let x = [54, 74, 90, 2006]
let last = x[3..4]
last[0] = 2014
println(x) // Printed [54, 74, 90, 2014]

Because of that, we could use slices as views on the original array, which simplified implementing algorithms like quicksort. Normally, recursive implementations start out like this:

// CAREFUL: This was valid Swift before beta 3,
// it does not work anymore

func quicksort(v: Int[]) {
quicksort(v, 0, v.count — 1)
}

func quicksort(v: Int[], left: Int, right: Int) {
if left >= right { return }

let pivotIndex = partition(v, left, right)
quicksort(v, left, pivotIndex - 1)
quicksort(v, pivotIndex + 1, right)
}

Both quicksort and partition have three input parameters. With the old Swift array behavior, we could use slices instead:

// CAREFUL: This was valid Swift before beta 3,
// it does not work anymore

func quicksort(v: Int[]) {
quicksort(v[0..v.count])
}

func quicksort(v: Slice<Int>) {
if v.count <= 1 { return }

let pivotIndex = partition(v)
quicksort(v[0..pivotIndex])
quicksort(v[(pivotIndex + 1)..v.count])
}

Suddenly the code is more readable, because the slice parameter abstracted away the left and right offset: Instead of left, we could use 0, instead of right + 1, we could use the count of the slice. That simplifies writing the partition function, because we don’t have to make a lot of index calculations.

Still, we should be very happy about the change, because now a constant array is immutable, as it should be.

I hope you enjoyed the article, and learned a thing or two you didn’t know. The next article of this series is about functions in Swift. See you there!

--

--