Recently I have stumbled across a Reddit thread in r/swift, where a user is confused about why the output of his code does not match his expectation :
class Member {
var memberID: Int? = nil
}
// Array of "Member" class
var members = [Member]()
// temporary variable for member
var memberHolder = Member()
for i in 1...5 {
memberHolder.memberID = i
members.append(memberHolder)
}
for m in members {
print("member ID is \(m.memberID)")
}
He was expecting the output to be like this :
member ID is Optional(1)
member ID is Optional(2)
member ID is Optional(3)
member ID is Optional(4)
member ID is Optional(5)
But the actual output from Xcode is that all member has the same memberID! đ±đ€
member ID is Optional(5)
member ID is Optional(5)
member ID is Optional(5)
member ID is Optional(5)
member ID is Optional(5)
If you also got surprised by the actual output, read on! The explanation for this code behaviour is at the bottom of this article.
Table of contents:
Memory Address
Before going into explaining the difference between reference and value type, It is crucial to know the concept of Memory Address.
Your computer and iOS devices has a component named RAM (Random Access Memory), which is used to store working data when you are using the device. For example, when you are playing a game, there will be a loading screen to load the graphic assets and audio needed in the level into the memory (RAM), then when you are playing that level, the game will access the graphic / audio / character information from the RAM.
For your Mac, the RAM refer to the 'Memory' section.
For most modern computers, RAM / Memory segments its space (eg: 8GB space) into discrete chunks (multiple 1-byte space), and each chunk has an unique memory address. Below is a diagram showing different chunk (with different memory address) holding different text data :
Memory address usually is a hexadecimal string (eg: 0x12345) in most computer system, when the CPU need to get a data from a certain chunk of memory, it will reference the memory address. This concept is also known as Pointer in Computer Science.
When we declare variables in Swift / Objective-C , the operating system will allocate a (few) chunk of memory (with memory address) to store them.
Value types
Some examples of value type include primitive types such as Int, Double, String ( in Swift) , tuples, enum and struct. Value type instances (ie. var myMoney: Int = 500
) create a new copy of the value assigned instead of storing a reference to the value.
var yourMoney : Int = 500
var myMoney : Int = yourMoney
// you lost 50 dollar
yourMoney = yourMoney - 50
print("yourMoney is \(yourMoney)") // 450
print("myMoney is \(myMoney)") // 500
Despite the myMoney value is assigned from yourMoney value, myMoney 's value remain unchanged even though yourMoney has decreased. This is because during the assignment myMoney = yourMoney
, the value '500' is copied over.
Reference types
Just a note from Swift official documentation :
If you have experience with C, C++, or Objective-C, you may know that these languages use pointers to refer to addresses in memory. A Swift constant or variable that refers to an instance of some reference type is similar to a pointer in C, but isnât a direct pointer to an address in memory, and doesnât require you to write an asterisk (*) to indicate that you are creating a reference. Instead, these references are defined like any other constant or variable in Swift.
As using / modifying a pointer directly can be dangerous (C/C++/ObjC has this ability), Swift has isolated direct memory access from us and thus the variable (of reference type) might not directly store the memory address, but the concept of memory address is very much similar. The explanation below assume the variable stores the memory address directly for simplification.
Class, Closure and Functions are reference type. When a reference type variable is declared (eg: var dog : Dog = Dog()
), the system will allocate a chunk of memory space in RAM, and store the memory address of the Dog object to the variable. The variable is actually storing the memory address of the Dog data, not the Dog data itself. When another class object is assigned to an existing class object, the memory address is copied over, not the data.
class Dog {
var name : String
init(name: String) {
self.name = name
}
}
var labrador = Dog(name: "labrador")
print("dog name is \(labrador.name)") // "dog name is labrador"
var schnauzer = labrador
print("schnauzer name is \(schnauzer.name)") // "schnauzer name is labrador"
schnauzer.name = "schnauzer"
print("dog name is \(labrador.name)") // dog name is schnauzer
The diagram above shows how reference types work, as both "labrador" and "schnauzer" variables point to the same memory address, modifying the .name on either one of them will affect the same name data stored on the memory address.
In Swift, we can print out the memory address of reference-type variables using Unmanaged.passUnretained(variable).toOpaque() like this :
print("memory address of labrador is \(Unmanaged.passUnretained(labrador).toOpaque())")
print("memory address of schnauzer is \(Unmanaged.passUnretained(schnauzer).toOpaque())")
In Objective-C, we can use NSLog(@"%p", variable) to print out the memory address, the "%p" is a pointer formatter in C.
NSLog(@"memory address of labrador is %p", labrador);
NSLog(@"memory address of schnauzer is %p", schnauzer);
Here's the output if we execute the code above in Playground :
Notice that the memory address of labrador and schnauzer is exactly same! (0x00007fa409f3cb30)
Next time if you find yourself confused about how reference type work or why a class object variable output value isn't as you expected, just remember that reference type copies memory address and modify the data in that address, hence other variable that share the same memory address will be affected as well.
I searched for articles about Swift value / reference type online and none of them explained about the memory address (pointer) aspect of a reference type. I think it would be easier to grasp the concept of reference type with understanding of memory address.
Explanation for the code issue
Now that we understand how reference type works, let's take a look at the code at top again :
class Member {
var memberID: Int? = nil
}
// Array of "Member" class
var members = [Member]()
// temporary variable for member
var memberHolder = Member()
for i in 1...5 {
memberHolder.memberID = i
members.append(memberHolder)
}
for m in members {
print("member ID is \(m.memberID)")
}
And here's how the code works :
As the memberHolder points to the same memory address and the members array append the memberHolder 5 times, members array now hold 5 memberHolder objects that point to the same memory address, which the memberID is 5 after the latest iteration (when i=5).
So when all member in members array are printed, they all print the same memberID (5) !