Consider the following class definition
class Class1 {
var property: String {
get {
return ""
}
set {
print("set called")
}
}
}
If you add breakpoint inside the get block and read property
, the execution is paused and you observe that the topmost method in the call stack is Class1.property.getter
Similarly, if you add breakpoint inside the set block and set property
, the execution is paused and you observe that the topmost method in the call stack is Class1.property.setter
While debugging a crash, I observed that the topmost method in the call stack was ClassName.computedPropertyName.modify
where ClassName
and computedPropertyName
are placeholders.
Can anyone point out what the modify
method does and when it is called?
Like get
and set
, modify
is an accessor. It's a part of the move towards generalised accessors, and is used to obtain a mutable reference to an underlying value using a yield-once coroutine.
You can actually write modify
accessors in today's Swift using the _modify
keyword. Though note however that it's not yet an official feature, so any code that explicitly depends on _modify
and yield
is subject to breaking without notice.
class C {
var _property: String = ""
var property: String {
get {
return _property
}
_modify {
yield &_property
}
}
}
let c = C()
c.property += "hello"
print(c.property) // hello
Upon mutating c.property
, the _modify
accessor is called to obtain a mutable reference to some underlying storage. The yield
keyword is used in order to transfer control back to the caller with a reference to _property
's storage. At this point, the caller can apply arbitrary mutations to the storage, in this case calling +=
. Once the mutation has finished, control is transferred back to _modify
, at which point it returns.
modify
accessor useful?Simply put, it avoids the copying of values, which can trigger expensive copying operations for copy-on-write types such as String
, Array
, and Dictionary
(I talk about this in more detail here). Mutating c.property
through a modify
accessor allows the string to be mutated in-place, rather than mutating a temporary copy which is then written back.
modify
use coroutines?The use of a coroutine allows a mutable reference to be temporarily handed back to the caller, after which the accessor can then perform additional logic.
For example:
class C {
var _property: String = ""
var property: String {
get {
return _property
}
_modify {
yield &_property
_property += " world!"
}
}
}
let c = C()
c.property += "hello"
print(c.property) // hello world!
which first lets the caller perform its mutations, and then appends " world!"
to the end of the string.
modify
accessor showing up in your code?The Swift compiler can implicitly synthesise a modify
accessor for mutable properties. For a computed property with a getter and setter, the implementation looks like this:
class Class1 {
var property: String {
get {
return ""
}
set {
print("set called")
}
// What the compiler synthesises:
_modify {
var tmp = property.get() // Made up syntax.
yield &tmp
property.set(tmp)
}
}
}
The getter is first called in order to get a mutable copy of the value, a reference to this mutable copy is then passed back to the caller, and then the setter is called with the new value.
The modify
accessor is primarily used in this case in order to enable efficient mutation of a property through dynamic dispatch. Consider the following example:
class C {
var property = "hello" {
// What the compiler synthesises:
_modify {
yield &property
}
}
}
class D : C {
override var property: String {
get { return "goodbye" }
set { print(newValue) }
// What the compiler synthesises:
_modify {
var tmp = property.get()
yield &tmp
property.set(tmp)
}
}
}
func mutateProperty(_ c: C) {
c.property += "foo"
}
On mutating c.property
, the modify
accessor is dynamically dispatched to. If this is an instance of C
, this allows a reference to the storage of property
to be directly returned to the caller, allowing for an efficient in-place mutation. If this is an instance of D
, then calling the modify
just has the same effect as calling the getter followed by the setter.
modify
showing up as the topmost call in the stack trace of your crash?I would assume this is because the compiler has inlined the implementation of your getter and setter into the modify
accessor, therefore meaning that the crash has likely been caused by the implementation of either the getter or setter of your property.