Reference and Value Types in Swift

In this post we are going to examine the differences between reference and value types. We’ll introduce both concepts, take a look at their strengths and weaknesses and examine how we can take advantage of them in Swift.

Reference Types

Reference type: a type that once initialized, when assigned to a variable or constant, or when passed to a function, returns a reference to the same existing instance.

A typical example of a reference type is an object. Once instantiated, when we either assign it or pass it as a value, we are actually assigning or passing around the reference to the original instance (i.e. its location in memory). Reference types assignment is said to have shallow copy semantics.

In Swift, objects are created using the class keyword.

Value Types

Value type: a type that creates a new instance (copy) when assigned to a variable or constant, or when passed to a function.

A typical example of a value type is a primitive type. Once instantiated, when we either assign it or pass it as a value, we are actually getting a copy of the original instance. Value types assignment is said to have deep copy semantics.

In Swift, value types can be defined using the struct keyword. Also, enums and tuples can be value types.

The issue with Reference Type instances: implicit data sharing

In order to show a typical issue with reference types, let’s define a class to represent a point in a 2D space.

class PointClass {
var x: Int = 0
var y: Int = 0
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}

Now, what happens if we instantiate a PointClass object and assign it to another one?

var pointA = PointClass(x: 1, y: 3)
var pointB = pointA

Because PointClass is a reference type, the last statement is actually assigning the reference to pointA to pointB. We can graphically represent the above scenario as follows:

Reference type instances

In this situation, pointB and pointA share the same instance. Hence, any change to pointA will reflect on pointB and vice versa. This may be fine in many circumstances, but is also a very common source of subtle bugs.

One way to avoid this issue is to explicitly create a copy of the instance. Instead of just assigning pointA, we can manually create a copy and assign it:

var pointB = pointA.copy()

Now, pointB has its own separate reference and there will be no more shared data between pointA and pointB. This technique works fine but has a couple of disadvantages:

  • explicitly calling copy() for each assignment introduces some overhead
  • it is easy to forget to call copy() for each assignment

Value Type instances: no implicit sharing

When assigning value types, the compiler will automatically create (and return) a copy of the instance. Let’s see what happens if, instead of defining our 2D point as a class (reference type), we make it a struct (value type).

struct PointStruct {
var x: Int = 0
var y: Int = 0
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}

Now, we can create an instance of PointStruct and assign it to another one.

var pointA = PointStruct(x: 1, y: 3)
var pointB = pointA

Because PointStruct is a value type, the last statement is creating a copy of pointA to be assigned to pointB. This makes the assignment safe as the two instances are distinct. We can graphically represent this situation as follows:

Values type instances

We can see that pointB has its own separate reference and there will be no shared data between pointA and pointB. This shows that by using value types we can easily make sure that all our instances are distinct and don’t share any data.

From a performance standpoint, using value types doesn’t add a significant overhead:

Copies Are Cheap
• Copying a primitive type (Int, Double, …) takes constant time
• Copying a struct, enum or tuple of value types takes constant time
Extensible data structures use copy-on-write
• Copying involves a fixed number of reference-counting operations
• This technique is used by many standard library types: String, Array, Set, Dictionary, …

In addition to the above, another performance benefit of value types is that they are stack allocated, which is more efficient than heap allocation (used for reference types). This makes access faster but comes with the drawback of having to drop support for inheritance.

It is important to point out that structs, enums and tuples are true value types only if all their properties are value types. If any of their properties is a reference type, we still could run into the implicit data sharing issues illustrated in the previous paragraph.

Reference Types, Value Types and Immutability

Immutability: the property of an instance whose state cannot be modified after it is created.

Immutability is a very important property and it is strictly related to the functional programming paradigm. We saw that by using value types we are able to create instances that preserve their state indefinitely, which are immutable by definition. Immutable objects have some interesting pros and cons.

Pros:

  • immutable objects share no data and, therefore, have no shared state among instances. This avoid the issue of unexpected changes caused by side effects
  • a direct consequence of the previous point is that immutable objects are inherently thread-safe. This means we don’t need to worry about race conditions and thread synchronization
  • because immutable objects preserve their state, it is easier to reason about your code

Cons:

  • immutability doesn’t always map efficiently to machine model. A typical example of this are algorithms that perform in-place modifications (like the Sieve of Eratosthenes). Those are not easy to implement using value types while, at the same time, maintaining the original performances

In Swift, we can define variables using two different keyword:

  • var: defines mutable instances
  • let: defines immutable instances

The above keywords have different behaviors, depending on whether they are used for reference or value types.

Mutable Instances: var

Reference types

The reference can be changed (mutable): you can mutate the instance itself and also change the instance reference.

Value types

The instance can be changed (mutable): you can change the properties of the instance.

Immutable Instances: let

Reference types

The reference remains constant (immutable): you can’t change the instance reference, but you can mutate the instance itself.

Value types

The instance remains constant (immutable): you can’t change the properties of the instance, regardless whether a property is declared with let or var.

Which type should you choose

A very common question is: “How can I decide when to use reference types and when to use value types?”. You can find a lot of discussions about that over the Internet.

As a basic rule, we are forced to create reference types every time we are subclassing from NSObject. This is a common scenario when interacting with the Cocoa SDK. There are some common rules, provided by Apple, for when using reference types versus value types which are summarized below.

Reference Type

  • Subclasses of NSObject must be class types
  • Comparing instance identity with === makes sense
  • You want to create shared, mutable state

Value Type

  • Comparing instance data with == makes sense (Equatable protocol)
  • You want copies to have independent state
  • The data will be used in code across multiple threads (avoid explicit synchronization)

Interestingly enough, the Swift standard library favors value types:

  • Primitive types (Int, Double, String, …) are value types
  • Standard collections (Array, Dictionary, Set, …) are value types

Aside from what illustrated above, the choice really depends on what we are trying to implement. As a rule of thumb, if there is no specific constraint that forces to opt for a reference type, or you are not sure which option is best for your specific use case, you could start by implementing your data structure using a value type. If needed, you should be able to convert it to a reference type later with relatively little effort.

Conclusion

You can download a playground with the code from this article here.

In this article we examined the differences between reference and value types. After looking into a common issue with implicit data sharing, we saw how it is possible to avoid that by using value types instead of reference types.

We also introduced the concept of immutability and saw how it applies to reference and value types in Swift. Lastly, we reviewed some use cases for which the choice between reference and value types is quite straightforward. For all other cases, experimentation is the best way to find out which could be the best option.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.