Swift version: 5.10
Working with value types like structs and enums makes your code easier to write, easier to test, and easier to reason about. However, they aren’t always possible: classes and closures are both reference types, and are used extensively in Swift for the handful of times when sharing data is important, or perhaps because you’re using them through someone else’s Swift code.
This can cause a variety of side effects with your code: if you use reference types inside value types, they still behave like reference types. The solution to this is to wrap those reference types in value semantics, meaning that they behave more like value types.
To demonstrate the problem, consider this ChatHistory
class:
class ChatHistory: NSCopying {
var messages: String = "Anonymous"
func copy(with zone: NSZone? = nil) -> Any {
let copy = ChatHistory()
copy.messages = messages
return copy
}
}
It doesn’t hold much data, but it’s enough for us to work with.
If you wanted to use that inside a struct your first attempt might look like this:
struct User {
var chats = ChatHistory()
}
let twostraws = User()
twostraws.chats.messages = "Hello!"
Although that’s a value type – meaning that it may have only one owner at a time – it doesn’t really behave like one. You can see we’re modifying the struct directly, even though it’s marked as a constant. We could also create a different struct and make it re-use the same chats
property:
var taylor = User()
taylor.chats = twostraws.chats
Because we made taylor.chats
point to the same object as twostraws.chats
– a reference type – then changing one will also change the other. So, this will print “Goodbye!” twice:
twostraws.chats.messages = "Goodbye!"
print(twostraws.chats.messages)
print(taylor.chats.messages)
To fix this we’re going to change the implementation of User
so that messages
becomes a private property called _messages
, and expose a custom getter/setter called messages
that will act in its place. The setter part will just change _messages
directly, but the getter will take a copy of _messages
before returning it so that our properties are always unique.
To do this we’re going to use Swift’s mutating getters, which are enabled using the mutating get
keyword. This allows us to change the struct inside the getter, and Swift will back this up by refusing to allow it when used with constants.
Here’s how that looks:
struct User {
var _chats = ChatHistory()
var chats: ChatHistory {
mutating get {
_chats = _chats.copy() as! ChatHistory
return _chats
}
set {
_chats = newValue
}
}
}
This is an improvement, because now Swift will force us to make twostraws
a variable, like this:
var twostraws = User()
This makes sense, because we are changing it. However, it introduces a new problem: all that copying is quite wasteful if you do repeated work when the messages object was unique all along.
To fix this new problem you need the isKnownUniquelyReferenced()
function, which returns true when called on an instance of a Swift class that has only one owner.isKnownUniquelyReferenced()
. This means we can return _chats
if it is uniquely referenced (no one else has a reference to it), otherwise we’ll take a copy. This reduces copying down to the bare minimum, which is much more efficient.
struct User {
private var _chats = ChatHistory()
var chats: ChatHistory {
mutating get {
if !isKnownUniquelyReferenced(&_chats) {
_chats = _chats.copy() as! ChatHistory
}
return _chats
}
set {
_chats = newValue
}
}
}
If you run the program again, you’ll see that the two users now have their own chat logs – using twostraws.chats.messages = "Goodbye!"
inherently means reading chats
, which will notice the object isn’t uniquely referenced and take a copy before continuing.
You could still abuse that if you really wanted to, but it’s getting increasingly hard – Swift is protecting us from a wider variety of mistakes.
Ideally your goal is to copy as infrequently as possible, so it’s common to make copies only when something is definitely changing rather than simply being read. In practice, this would mean marking methods in your struct as mutating
, then funneling access through there so you copy only as needed. At WWDC 2015, [Apple gave a demonstration of using two different properties (one reading, one writing) to accomplish this – see https://developer.apple.com/videos/play/wwdc2015/414 for more information.
Warning: Don’t try using isKnownUniquelyReferenced()
on Objective-C objects, because it will not behave as you expect – it’s designed only for Swift classes.
SPONSORED Alex is the iOS & Mac developer’s ultimate AI assistant. It integrates with Xcode, offering a best-in-class Swift coding agent. Generate modern SwiftUI from images. Fast-apply suggestions from Claude 3.5 Sonnet, o3-mini, and DeepSeek R1. Autofix Swift 6 errors and warnings. And so much more. Start your 7-day free trial today!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Available from iOS 8.0
This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.
Link copied to your pasteboard.