Reference and Value Types in Swift

References and Values in Theory

In Swift, a class is considered a reference type, plain and simple. This is similar to Objective-C, where everything that inherits from NSObject is a reference type. Being a reference type means that references share one single copy of data in memory, and they all refer to that same memory address. An issue with this involves the mutability of such an object - if somebody using a reference to Object A in a class changes some mutable values around, and I’m using Object A in a different class and I’m expecting a different value, we have a problem. 

Structs, enums and tuples are all value types in Swift (Objective-C also uses value types in number literals or C structs). If we say that:

let entree = Entree ()

let entree.protein = “Tofu”

let meal = entree

meal.protein = “Chicken”

Then entree.protein should ALSO have a value of “Chicken”. Both entree and meal reference the same object in memory, so changing the value of one changes the other. 

If we redefined the above as a struct, then the it would become a value type, and changing one would NOT change the other. Changing the protein value of meal wouldn’t change the protein value of entree.

It’s important to note that “let” as a constant keyword means different things for value and reference types - for reference types, let means that the reference must remain constant, but the properties are mutable. For value types, let means that the instance must remain constant, meaning that properties are immutable. 

Struct foo { var isReferenceType = False }

Class foo2 {var isReferenceType = True }

let exampleOne = foo ()

let exampleTwo = foo2 ()

// the below code WON’T work, since we are attempting to change the reference of a reference type

exampleTwo = foo

//the below code WILL work, since we are attempting to change a property value for a reference type

exampleTwo.isReferenceType = False

//the below code WON’T work, since we are attempting to change a property value for a value type

exampleOne.isReferenceType = True

 

To sum up, we can’t change the reference of a let constant if it’s a class, but we can change property values of that reference. We also can’t change property values of a let constant if it’s a struct. 


It’s important to note that Swift favors values almost exclusively. If you need to compare two struct instances for equality (assume we’re talking about value equality, not memory equality) then we need to conform to the Equatable protocol (this is something often required with value types). 

extension structName: Equatable { 

    func == (firstArgument: structName, secondArgument: structName) {

        return (firstArgument.value1 == secondArgument.value1) && (firstArgument.value2 == secondArgument.value2)

    }

}

To make data thread-safe, we would need to use a reference type (class) and implement locking. If you need to compare memory (physical) equality, then you should use a reference type. 

References and Values in Practice

Swift makes use of an optimization technique known as Copy-on-Write for value types - if you assign Object B = Object A, Swift will let both share a memory address even though they're value types. Object B will simply contain a reference to Object A. When the value of one changes, a copy is made, and the objects no longer share a memory address. In a cool optimization twist, if you later change one other object to match the other AGAIN, they'll once again share a memory address.

With some low-level Swift programming (of which there are a few resources about online, none of them actually any good), you can prove that copy-on-write exists - HOWEVER, it only seems to work for a few built-in types (such as arrays)! That's because if you want all that optimized goodness that comes from copy-on-write, you've got to implement it yourself for your custom value types. 

To learn about implementing it yourself, and how to build a Swift array from scratch, check out this article by Mike Ash. He runs an incredibly deep and advanced blog on Objective-C and Swift programming, and writes articles all the way down to the compiler level. Most of it is beyond me right now, but it's still good to glance at. 

You mix and match reference and value types all the time - remember that literals are value types, so whenever you have a a class variable that’s a string or a custom struct you created in a different file, your class is a reference type containing value types. This is common, and works out nicely. 

Things get tricky when you have a reference type contained within a value type. 

Let’s get something cleared up that’s related to the protocol-oriented nature of swift - value types should be equatable! This the expected behavior of a value type (as mentioned in a WWDC15 talk); if we have two integers (a value type in swift), don’t we expect to be able to compare their VALUES? I know I do. The same goes for strings, which, as a literal, are also equatable value types. 

Value types need to implement the == operator. The three properties of equality that must be addressed are that the comparison must be reflexive, symmetric and transitive

  1. To be reflexive, we must make sure that “x == x” returns true.
  2. To be symmetric, we must make sure that “x==y” and “y==x” return the same value, whether true or false.
  3. To be transitive, our == operator must make sure that is “a==b” is true, and “b==c” is true, then “a==c” must also be true. 

If your struct contains variables that are already literal value types, then you can declare the equatable function as a global function and simply compare the variables using the == operator. If your struct contains a struct, that nested struct should also conform to the equatable protocol in order to work your way up. It’s also convention to declare protocol adherence as an extension of the Type definition. 

Making reference types equatable is slightly different - it’s entirely case-specific. Say you have a “Person” class (a reference type) - it has a name variable. Two people can have the same name, but be different people. So how you implement whether or not they're equatable is up to you. 

Let’s return to a value type containing a reference type. We can have a struct be mutated very easily if it contains a reference type. There are ways around this, some better than others, which we’ll visit in a moment. Look at the screenshot below - we have two struct instances, one equal to the other (remember copy-on-write here). We change the value of a reference type variables of the first - desired behavior would be that since they’re both value types, changing data in one wouldn’t affect the other. Not so.

We've managed to mutate a struct by changing data in a different struct! This showcases their coupling based on the existence of a reference type in the struct definition.

We've managed to mutate a struct by changing data in a different struct! This showcases their coupling based on the existence of a reference type in the struct definition.

We can prevent this by forcing our Restaurant to use an explicit initializer - this actually creates a copy of the Person being passed in, instead of holding a reference to it. If we do this, and we pass in "managerTest" to be the manager, our Restaurant will use a COPY of the "managerTest" object. You can check the memory addresses and see that they're different. This means that mutating values of the "managerTest" object won't mutate the "manager" property of our Restaurant. Without this initializer, the two share a memory address, so mutating one mutates the other. The flaw in our design is when we act directly on "bestResto.manager.name" - there is nothing preventing this kind of mutation. 

We can mutate struct values here by just acting directly on the struct instance instead of the original reference type we used to create the "Manager".

We can mutate struct values here by just acting directly on the struct instance instead of the original reference type we used to create the "Manager".

The concept at hand here, a far cry from where we came, is data encapsulation. We've covered this in a previous post, but not with the complexity of a reference type contained within a value type. Let's start planning: a good solution would be to create setters and getters for the “manager” property of our Restaurant struct - get simply returns the current value, and set would create a copy of the manager and set the “manager” property to be equal to the new value. This way, if the data is changed, a deep copy of the reference type is created (vs a shallow copy, which creates a new object that simply points to the copied object...aka a referencing object). 

See any problems with this? I do. For one, we're creating a copy every single time the data is written to, instead of just mutating the existing copy. How can we say "If this object is currently referencing another object (shared resource) then make a deep copy with the changes. If nothing else is referencing this, just mutate the existing object instead of making a copy"? In comes "isUniquelyReferencedNonObjC", a method that detects the reference count of an object and returns a boolean value. You pass in the address of an object, it find other references to that object, and if there are no other references, it returns "True". Basically, if this returns "True" for our manager object, it has nothing else referencing it, and we can just mutate the existing copy. This raises a few questions that we'll address in a moment. 

Check out the code below - this is in a project. We have out VC code which creates two structs, the second equal to the first. We check the memory address of the MANAGER (which is a reference type), NOT the struct. We only implemented copy-on-write for the manager here - remember that custom structs don't implement copy-on-write automatically, so if we wanted that, we'd have to implement it ourselves. In this case, we just did it for the Person object. 

The managers share the same memory address - this is what we'd expect. After we change the manager object of the second restaurant object, the memory addresses are suddenly different! Our copy-on-write worked! The "withUnsafePointer" code is how we get the memory address of a struct. This is to prove that our struct doesn't implement copy-on-write (they have different addresses in memory from the very beginning, even though they have equivalent values). The other functions used to obtain memory addresses are just C functions that we're using in our Swift code.  

Memory address console prints to show copy-on-write in action

Memory address console prints to show copy-on-write in action

The last thing I'll add is a screenshot of my code for Restaurant - it changed since the last time we wrote it. The "manager" variable is now private, and we can only change it by using explicit APIs - this is to avoid developers accidentally changing the manager of a restaurant. If they want to change it, they call a clearly defined API that tells them exactly what they're doing so that any confusion is avoided. We get the current manager through a computed property "currentManager" and we implement copy-on-write in our "assignNewManager" function. The function is mutating since, if the manager object is uniquely referenced, we want to change it in place. Else, create a copy of the manager being passed in and assign it to our manager property. 

Encapsulated logic for creating a Restaurant struct

Encapsulated logic for creating a Restaurant struct

There's one more thing to talk about! What if we want to have two restaurants that share the same manager? This is a perfect representation of our situation - Restaurants are unique, and so should be value types. Managers can be the same person, and so CAN be references, but managers with the same name can ALSO be unique, and so CAN be value types (read - structs). Solution - it doesn't work here. We can create a new Restaurant object and pass in the Person we created as the manager, but our init method forces a copy to be made. We need to tinker with our init method and perhaps implement copy-on-write somewhere else in our code.

There are other edge cases to consider where our code WILL NOT WORK. I've been racking my brains for a few days here and there, trying to come up with an elegant solution that works. I'll keep working on it and I'll be sure to post here when I find a good solution.

That’s a ton to cover - we’ve reviewed lazy variables and working with constants, lazy sequences, reference and value types, equatable protocol implementation for value types, and discussed scenarios where you may WANT to have a reference type within a value type and how to write solid, defensive code in that situation. This was a long post, but definitely one of value.