This performance optimization WWDC video suggests that strings are reference counted because they are on the heap. This has implications on the performance of structs with Strings and whether something has changed in Swift 4 (now that Strings are collections again - with copy on write). Curious how to prove this out and get an actual count. CFGetRetainCount
- doesn't work on Strings.
See https://developer.apple.com/videos/play/wwdc2016/416/
Using Swift 4.
Strings Are Value TypesSwift's String type is a value type. If you create a new String value, that String value is copied when it's passed to a function or method, or when it's assigned to a constant or variable.
You've seen how you can type values for strings directly into your code, but Swift also has a feature called string interpolation – the ability to place variables inside your strings to make them more useful.
You can always check the reference count of a variable by using the CFGetRetainCount function. Note: Reference counting is done only to manage memory for reference types (e.g., classes and closures).
Swift Strings are value types which doesn't have reference counting. But the characters that string contains are kept in heap inside a reference type container storage, and that has reference count.
This is why Swift Strings has copy on write optimization -like other collections-
Using Strings -and also any other reference type- inside Structs is not a good idea for performance, because, on each assignment of Struct itself, all other reference types and String storage is retained.
When you have a value type that contains N reference type, on each assignment/deinit you need N retain/release. And you will have copy overhead for value types.
But if you define a reference type that contains N reference types, on each assignment/deinit you will have just 1 retain/release operation.
For exp:
struct Label {
var text: String
var font: UIFont
func draw() { }
}
let label1 = Label(text: "Hi", font: font)
let label2 = label1
retain(label2.text._storage)
retain(label2.font)
// finished using label1
release(label1.text._storage)
release(label1.font)
// finished using label2
release(label2.text._storage)
release(label2.font)
If the Label
was implemented as a class it would be
class Label {
var text: String
var font: UIFont
func draw() { }
}
let label1 = Label(text: "Hi", font: font)
let label2 = label1
retain(label2)
// finished using label1
release(label1)
// finished using label2
release(label2)
On the other hand, this approach conflicts with struct's thread-safety offer. The same instances will be shared among all copies.
Since retain/release operations are on the heap and they have to be thread-safe, there is considerable cost for these operations.
So if you really need a great performance, including micro optimizations, and you want to use value types wisely, you should consider this approach.
PS: This approach is not changed by Swift 4, copy on write is another optimization. It creates a copy, only when value types that contains reference types with multiple references, are mutated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With