Library for tracing access to properties.
The following information can be obtained:
- Accessor Type
- get: current value
- set: current & new value
- Call stack info
- caller symbol name
- caller symbol address
- return address
- Parent object
- KeyPath
- ...
Warning It does not work correctly depending on the setting of swift's optimization flag. With the default settings, only Debug builds work.
For example, when we define:
struct Item {
@Traced(trace(_:_:))
var title = "hello"
}
func trace<P, V>(_ access: PropertyAccess<P, V>, _ tracedKeyPath: KeyPath<P, Traced<P, V>>?) {
print("\n[Access]------------------")
print("\(access.accessor.description)")
print("called from: \(access.callStackInfo.demangledSymbolName ?? "unknown")")
if let parent = access.parent {
print("parent: \(parent)")
}
if let keyPath = access.keyPath {
print("keyPath: \(keyPath)")
}
if let tracedKeyPath {
print("tracedKeyPath: \(tracedKeyPath)")
}
print("----------------------------")
}
Suppose the following operation is performed:
let item = Item()
print(item.title)
item.title = "new value"
item.printTitle()
At this time, the specified trace
function is called and the output is as follows:
[Access]------------------
getter(String): initial value
called from: PropertyTracerTests.PropertyTracerTests.test() -> ()
----------------------------
initial value
[Access]------------------
setter(String): initial value => new value
called from: PropertyTracerTests.PropertyTracerTests.test() -> ()
----------------------------
[Access]------------------
getter(String): new value
called from: PropertyTracerTests.PropertyTracerTests.Item.printTitle() -> ()
----------------------------
new value
It is also possible to set up additional information to be received in the callback as follows:
struct Item {
// specify parent and variable type
@Traced<Item, String>(trace(_:_:))
var title = "initial value"
init(title: String = "initial value") {
self.title = title
// parent object
_title.parent.value = copiedOwn
// keyPath
_title.keyPath.value = \Self.title
// traced keyPath
_title.tracedKeyPath.value = \Self._title
}
func printTitle() {
print(title)
}
func copiedOwn() -> Self {
let copied = self
return self
}
}
For example, what would happen if you accessed the parent property directly in the trace
function described above?
Accessing properties within the trace
function will result in further calls to the trace
function, leading to an infinite loop.
Therefore, there are methods that allow manipulation of values without tracing.
-
get value without tracing
let title = item._title.untracedGet()
-
set value without tracing
let newTitle = "new" item._title.untracedSet(newTitle)
-
stop tracing
item._title.untraced()
-
restart taracing
item._title.traced()
The following definition will cause all properties of type Item to be traced.
It is defined by a macro, and the parent
and keyPath
are set automatically.
Properties to which the @NoTraced
attribute is attached are excluded from trace.
@PropertyTraced(trace(_:_:))
class ClassType1 {
static let staticVar = ""
var value1: String = "ใใใซใกใฏ"
var value2: Int = 12
@NoTraced
var value3: Double = 1.0
func modify() {
value1 = "hello"
value2 *= 2
value3 = 14
}
}
func trace(_ access: AnyPropertyAccess, _ tracedKeyPath: AnyKeyPath?) {
print("\n[Access]------------------")
print("\(access.accessor.description)")
print("called from: \(access.callStackInfo.demangledSymbolName ?? "unknown")")
if let parent = access.parent {
print("parent: \(parent)")
}
if let keyPath = access.keyPath {
print("keyPath: \(keyPath)")
}
if let tracedKeyPath {
print("tracedKeyPath: \(tracedKeyPath)")
}
print("----------------------------")
}
PropertyTracer is released under the MIT License. See LICENSE