PropertyTracer

0.2.0

๐Ÿ“ Library for tracing access to properties.
p-x9/swift-property-tracer

What's New

0.2.0

2024-06-01T10:38:29Z

What's Changed

  • Setup workflow by @p-x9 in #1
  • Support swift-syntax 510.0.0 by @p-x9 in #2

New Contributors

  • @p-x9 made their first contribution in #1

Full Changelog: 0.1.0...0.2.0

PropertyTracer

Library for tracing access to properties.

Github issues Github forks Github stars Github top language

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.

Table of Contents

Usage

Single Property

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

Additional Info

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
    }
}

Get/Set values without tracing

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/ReStart tracing a property

  • stop tracing

    item._title.untraced()
  • restart taracing

    item._title.traced()

Trace all member properties of a certain type

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("----------------------------")
}

License

PropertyTracer is released under the MIT License. See LICENSE

Description

  • Swift Tools 5.9.0
View More Packages from this Author

Dependencies

Last updated: Mon Nov 25 2024 13:22:04 GMT-1000 (Hawaii-Aleutian Standard Time)