(I apologize that this post is long. The original version was short and linked to a longer post on my blog, but that version was stuck in moderation for days because of the external link. This time I've reproduced the entire post here.)
I’m continuing to listen to the WWDC videos and think about how they apply to my own code, and recently I’ve been thinking about the intersection of three Swift topics:
- Building Better Apps with Value Types in Swift, where they promote the advantages of value types over reference types for certain uses.
- Optimizing Swift Performance, where they describe possible performance issues with value types and how to work around them.
- What’s New in Swift, where they describe the (currently unimplemented) indirect keyword for use in recursive enums.
In the optimization video, Michael Gottesman points out that Swift’s copy-on-write semantics for structs can cause performance problems if the struct contains many reference-counted properties. (14:12) The first time a struct is modified it must be copied, which means that the reference counts of all reference properties must be incremented. His solution is to group the reference properties into a wrapper class and store an instance of the wrapper class in the struct. That way only one reference count needs to be incremented when the struct is copied. In the value types talk, Bill Dudney shows how to implement this workaround efficiently for Swift types using the isUniquelyReferencedNonObjC function. (32:25)
This solves the performance problem, but at a cost in legibility and maintenance. For example, take this simple struct with three reference properties:
struct Customer {
var firstName: String
var middleName: String
var lastName: String
}
If you apply the wrapper class optimization to this struct, you get something like this:
struct Customer {
private class Wrapper {
var firstName = ""
var middleName = ""
var lastName = ""
func copy() -> Wrapper {
let clone = Wrapper()
clone.firstName = firstName
clone.middleName = middleName
clone.lastName = lastName
return clone
}
}
private var _values = Wrapper()
private var pathForReading: Wrapper {
get {
return _values
}
}
private var pathForWriting: Wrapper {
mutating get {
if !isUniquelyReferencedNonObjC(&_values) {
_values = _values.copy()
}
return _values
}
}
var firstName: String {
get {
return pathForReading.firstName
}
set {
pathForWriting.firstName = newValue
}
}
var middleName: String {
get {
return pathForReading.middleName
}
set {
pathForWriting.middleName = newValue
}
}
var lastName: String {
get {
return pathForReading.lastName
}
set {
pathForWriting.lastName = newValue
}
}
}
This is a lot of code to write and maintain for a simple (and presumably common) optimization. And although Swift can catch some common errors in this code, there are many that it can’t catch. For instance, while the compiler will complain if you use pathForWriting in a get method, it won’t say a word if you use pathForReading in a set method, which is arguably a much bigger problem. And of course it will have no idea if you make a copy/paste error and return pathForReading.firstName from the accessor for middleName.
A Proposed Improvement
If these wrapper classes are going to be common in production Swift code, then perhaps it would be better for Swift to provide built-in support for them. I was trying to figure out what keyword might be used to request this optimization when I remembered Chris Lattner’s talk where he reveals the (currently unimplemented) indirect keyword for creating recursive cases in enums.
To me, this seems like the right keyword for requesting indirect properties in structs as well.
From a certain point of view, enums and structs in Swift aren’t that different from one another. They are both value types that can contain sub-values. (The sub-values are known as associated values for enums, and properties for structs.) The main difference is that while an enum contains one of the sub-values, a struct contains all of the sub-values.
The indirect keyword being added to enums in Swift 2.0 solves a practical, rather than a conceptual, problem. Conceptually, there is no issue with an enum containing itself as an associated value, since a program can only create a finite number of enums and eventually the recursion must end. But in practice, associated values are stored directly in the enum and so indirection is needed to avoid allocating an infinitely large object.
The use of the indirect keyword that I’m proposing for structs is also to solve a practical problem — in this case, a performance problem. But the effect is essentially the same as with enums: the sub-values are moved out to a separate object and referenced indirectly.
If the indirect keyword were allowed in structs as well as enums, then the above code could simply become:
struct Customer {
indirect var firstName = ""
indirect var middleName = ""
indirect var lastName = ""
}
The Swift compiler could gather all of the indirect properties into a wrapper class and take care of creating the hidden wrapper class, the hidden value property, and rewriting the getters and setters to indirect through the hidden property.
I’d love to hear what others think of this proposal.