Home

Awesome

PropertyTracer

Library for tracing access to properties.

<!-- # Badges -->

Github issues Github forks Github stars Github top language

The following information can be obtained:

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.

Stop/ReStart tracing a property

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