When to Use Value Types and Reference Types in Swift

Value-Types-and-Reference-Types-in-Swift

Let’s talk about Value Types and Reference Types in Swift. Since Apple released Swift, there has been a lot of attention paid to value types. Especially coming from Objective-C where almost everything is a class (reference type), the fact that Swift’s standard library is mostly made up of value types is a big deal. At every conference I’ve been to recently, it seems that there has been at least one talk on value types and there’s always a couple at WWDC each year. While trying to dig into the differences between the two for an upcoming talk that I am giving on memory management in Swift (an iOS programming language) at ConnectTech, I found that even the details on how value types work seem to be all over the place. Even various WWDC talks on the same topic seemed to bring up contradictory points. I took a deep dive to understand the difference between value and reference type semantics, to be able to make better-informed decisions about which to use and when.

So what are reference and value types exactly?

The Basics

In Swift, structs, enums and tuples are all value types, while classes and closures are reference types.

In a nutshell, a value type contains data and a reference type contains the location in memory where data lives.

In practice, this means that when a value type is assigned to a variable or passed as a parameter to a function, it is copied, creating a new and unique instance of its data. So, say we make a Point struct, and then assign it to a new point.

struct Point {
    var x, y: Int
}

let aPoint = Point(x: 0, y: 0)
var anotherPoint = aPoint

When assigning aPoint to anotherPoint, the struct is copied so there are now two distinct points (0,0) in memory. So if we change one Point, the other is not affected.

anotherPoint.x = 5
print(aPoint.x) // prints 0

 

When a reference type is assigned to a variable or passed to a function, instead of creating a new copy of the object, it just creates another reference to that object which is shared among all the different references to it.

For example, let’s say we have a Person class and an instance of that class with the name “Bob” at the age of 30.

class Person { 
    let name: String
    var age: Int
    ...
}

let bob = Person(name: “Bob”, age: 30)

In this case, bob is just a reference to the Person instance that we created.

When assigning a new variable equal to bob, we are not creating a new Person, but adding another reference to that same Person in memory.

let myNeighbor = bob
myNeighbor.age = 50
print(bob.age) // prints 50

The same goes for passing a reference type into a function;

func increaseAge(person: Person) {
    person.age += 1
}

So if we pass bob into the increaseAge function, we now have three references to the same object: person, myNeighbor and bob.

increaseAge(person: bob)
print(myNeighbor.age) // prints 51

When person in the function has its age increased by 1 to 51, bob and myNeighbor also now have an age of 51, because they are all pointing to the same Person.

Because reference types are managing shared memory, there is some work that needs to be done to allocate memory in the heap for the object and also to clear this memory when it is no longer needed. This is done by keeping track of the number of references to each reference type and clearing that memory when its reference count reaches 0.

This WWDC talk is great for better understanding memory and performance of value and reference types.

Mixing Reference and Value Types in Swift

While the basic differences between the two are fairly straightforward, how they interact when mixed together is important.

Value types with more than one reference type property will have even more memory overhead than having a reference type. Any time the value type is copied, a new instance is created and there is reference counting overhead for each of its reference type properties. Also, having reference types on a value type negate several of the benefits of having value types that we will cover below.

Including value types as properties on a reference type can actually improve performance because new instances are not created for reference types when they are copied. Therefore, passing this reference type into a function, or assigning it to another variable will not cause its value type properties to be copied either.

Ok, so far so good, right? If you’re mixing types, just be sure to use reference types as the top level object and everything should be ok. Or will it? This is where some of the confusion starts…

Value Types Backed By Reference Types

Swift value types that are collections: Array, Dictionary, Set and String, which is a collection of characters, are all backed by reference types.

Backing these collections with reference types allows them to copy only when they’re changed. Meaning this:

let array1 = [1, 2, 3 … 100]
let array2 = array1

doSomethingWithArray(array2)

will not cause any copies of the initial array. This can be extremely helpful for performance when passing around large collections as the content of the collections are not copied until they are modified. However, when you’re including these collections as properties on other value types, you will run into the same reference counting overhead as including reference types as properties on a value type.

The difference between these collections and other value types seems to be the most misunderstood or misrepresented in different talks and blog posts I have seen on this topic. However, understanding this point seems to clear up a lot of the confusion and seemingly contradictory points.

When to Use Which

 

belief.jpgPhoto credit: Disney

So when should you use value types? Some blogs or talks will tell you that value types should only be used for small, basic model objects. Others will tell you that you should be using only value types unless classes are absolutely necessary. One agreed upon answer to this question that I encountered fairly often, was to start with a value type until a reference type is needed. The ‘give up’ point is what seems to differ from person to person, and as a programmer coming from Objective-C, I’ve found that I have a tendency to give up too quickly and just move to a class as soon as it seems convenient.

I especially don’t like the idea of making, what I believe to be unnecessary, copies of model objects constantly. However, is this truly a good reason to move away from value types? As Apple points out in the WWDC video mentioned above, copies are cheap in Swift. Sure, value types with reference types in them and value type collections carry some reference counting overhead, but does this really matter compared to the benefits of using value types? In some cases, yes, this absolutely can matter, but it’s worth exploring the benefits of value types and reference types so that you can make informed decisions on which to use.

Classes Add Power, At a Cost

The extra memory and performance overhead of reference types provides more power, particularly inheritance and other options inheritance provides, like dynamically dispatched methods. They also, by definition, have the ability to have multiple owners, which can be useful for receiving actions, events and messages from other sources.

If you need inheritance or multiple owners for your object, classes are definitely the way to go; if you don’t need these things, there’s a good chance you’d be better served with a value type. Maybe you don’t actually need inheritance, but you do need dynamically dispatched methods. This could also be done using value types and protocols.

Identity vs Equality

Because reference types may refer to the same instance, identity (===) makes sense. So in our initial Person example, if we want to know if my neighbor is Bob, we want to know if it is the exact same person, not just any person with the same name and age.

For value types, since we are creating new instances whenever they are copied, we need to be able to compare them not by their identity, but by their value. So in this case, it generally makes sense to compare value types using equality (==) instead.

If I have a 20 dollar bill and someone takes it and replaces it with another 20 dollar bill, I might not even notice. The identity of the bill isn’t important, the value is.

When thinking about your model and trying to figure out which type to use, see if it makes sense to compare the object with identity or equality. If it is clearly one or the other, it can make the decision between reference and value types easy.

This is another argument against including reference types as properties on a value type, as this makes it hard to compare the value types with equality. If you are unable to compare its properties using equality, it makes it hard to make the object itself Equatable.

Sharing Isn’t Always Caring

While there can be advantages to allowing an object to have multiple owners, if this is not necessary it will just cause problems. When your objects have multiple owners, they are implicitly dependent upon the other owners which could change the object out from underneath you at any time. This creates bugs that are harder to track down and to remove. It also makes your code harder to comprehend when you are working with an object; you don’t know where its other owners may be and when it might change. When working with value types, you will always know that you are the only owner of it and that makes reasoning about the code around it much easier. Similarly, value types with only single owners makes threading much easier, as you don’t need locking to prevent other threads from modifying your value type while you’re working with it. These advantages exist even for value types that are backed by reference types and can heavily outweigh the reference counting overhead that occurs from including these in other value types.

Mutability

Value types give you better control over mutability. When declaring a reference type with a let you are only ensuring that the reference to the object doesn’t change. However, a value type declared with a let cannot be modified at all.

class SomeClass {
    var value: Int
    ...
}

let aClass = SomeClass(value: 0)
aClass.value = 1 // This is ok

struct SomeStruct {
    var value: Int
}

let aStruct = SomeStruct(value: 0)
aStruct.value = 1 // The compiler won't let you do this

While you can create classes that have all of their properties declared with lets to make the class immutable (which also helps with the threading and multiple owner issues), you lose the control that you have with value types to make them mutable or immutable as needed. This immutability is also harder to enforce, as other programmers (or future you) can add mutable properties to the class or make a subclass with mutable properties. Methods on value types that modify its value must be explicitly marked as mutating, making it clearer what the function does and making it easier to control the object’s mutability.

Performance

Reference types incur more memory overhead, from reference counting and also for storing its data on the heap. It’s worth knowing that copying value types is relatively cheap in Swift, but it’s important to keep in mind that if your value types become too large, the performance cost of copying can become greater than the cost of using reference types.

One trick that Apple brings up in its Swift Github repo and a few of the WWDC talks is wrapping a struct inside a reference type and creating your own copy-on-write behavior like the collection value types. This prevents the struct from being copied on assignment or when passing it as an argument to a function. This is easy to do using the isKnownUniquelyReferenced function and an internal reference type:

final class Ref<T> {
  var val : T
  init(_ v : T) {val = v}
}

struct Box<T> {
    var ref : Ref<T>
    init(_ x : T) { ref = Ref(x) }

    var value: T {
        get { return ref.val }
        set {
          if (!isKnownUniquelyReferenced(&ref)) {
            ref = Ref(newValue)
            return
          }
          ref.val = newValue
        }
    }
}

The above code is from the Optimization Tips document linked above, but updated for Swift 3. This document is another great place to go for a deeper dive in understanding Swift performance.

Conclusion

There is no easy quick litmus test for knowing when to use a reference type instead of a value type, but if you understand the tradeoffs between the two and remember to default to value types, you should be well equipped to make the right decisions when writing your code.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *