Skip to content

Commit ed23973

Browse files
authored
[Observation] Add generation for _modify to fields such that we avoid extra CoW (#71122)
1 parent 991a6de commit ed23973

File tree

3 files changed

+42
-2
lines changed

3 files changed

+42
-2
lines changed

lib/Macros/Sources/ObservationMacros/ObservableMacro.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,18 @@ public struct ObservationTrackedMacro: AccessorMacro {
326326
}
327327
}
328328
"""
329+
330+
let modifyAccessor: AccessorDeclSyntax =
331+
"""
332+
_modify {
333+
access(keyPath: \\.\(identifier))
334+
\(raw: ObservableMacro.registrarVariableName).willSet(self, keyPath: \\.\(identifier))
335+
defer { \(raw: ObservableMacro.registrarVariableName).didSet(self, keyPath: \\.\(identifier)) }
336+
yield &_\(identifier)
337+
}
338+
"""
329339

330-
return [initAccessor, getAccessor, setAccessor]
340+
return [initAccessor, getAccessor, setAccessor, modifyAccessor]
331341
}
332342
}
333343

stdlib/public/Observation/Sources/Observation/Observable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public macro Observable() =
5050
/// The ``Observation`` module uses this macro. Its use outside of the
5151
/// framework isn't necessary.
5252
@available(SwiftStdlib 5.9, *)
53-
@attached(accessor, names: named(init), named(get), named(set))
53+
@attached(accessor, names: named(init), named(get), named(set), named(_modify))
5454
@attached(peer, names: prefixed(_))
5555
public macro ObservationTracked() =
5656
#externalMacro(module: "ObservationMacros", type: "ObservationTrackedMacro")

test/stdlib/Observation/Observable.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,28 @@ class GuardedAvailability {
225225
}()
226226
}
227227

228+
struct CowContainer {
229+
final class Contents { }
230+
231+
var contents = Contents()
232+
233+
mutating func mutate() {
234+
if !isKnownUniquelyReferenced(&contents) {
235+
contents = Contents()
236+
}
237+
}
238+
239+
var id: ObjectIdentifier {
240+
ObjectIdentifier(contents)
241+
}
242+
}
243+
244+
245+
@Observable
246+
final class CowTest {
247+
var container = CowContainer()
248+
}
249+
228250
@main
229251
struct Validator {
230252
@MainActor
@@ -440,6 +462,14 @@ struct Validator {
440462
expectEqual(obj.innerEventCount, 2)
441463
expectEqual(obj.outerEventCount, 2)
442464
}
465+
466+
suite.test("validate copy on write semantics") {
467+
let subject = CowTest()
468+
let startId = subject.container.id
469+
expectEqual(subject.container.id, startId)
470+
subject.container.mutate()
471+
expectEqual(subject.container.id, startId)
472+
}
443473

444474
runAllTests()
445475
}

0 commit comments

Comments
 (0)