Skip to content

Creating Custom Signals

Pavel edited this page Oct 20, 2022 · 3 revisions

There are a few types of Signals defined by the framework, each capturing some specific type of value and possibly some context around it. Most types of values will be logged using a WrapperSignal, unless a more appropriate type exists. Defining a custom signal requires subclassing Signal. However, in many cases, it may be less work to just subclass WrappedSignal instead.

As an example, let's say we're creating a custom signal for this simple struct:

struct Duck {
    name: String
}

We'll call this new signal - DuckSignal.

Subclassing Signal

Our subclass just needs to capture the value we want to log:

class DuckSignal: Signal {
    private(set) var duck: Duck
    
    init(_ aDuck: Duck, userInfo: [AnyHashable : Any]? = nil) {
        duck = aDuck
        super.init()
    }
}

WrapperSignal already captures the value it wraps, so it's often easier to subclass it rather than the base Signal class.

class DuckSignal: WrapperSignal {}

You would typically want to override a few properties to distinguish this new signal from others:

class DuckSignal: WrapperSignal {
    var duck: Duck { value as! Duck }
    override var signalName: String { "🦆" }
    override var valueDescription: String? { "A Duck named: \(duck.name)" }
}

Another benefit of subclassing from WrapperSignal is portability - any subclass automatically uses the same portableClassName as opposed to introducing new types that may not have been defined elsewhere. See Portability for more information.

Using the Signaling protocol

Lastly, we need to associate the value type with the new signal type so that calls to emit() can infer this information. This is done via the Signaling protocol:

extension Duck: Signaling {
    public var beaconSignal: DuckSignal { .init(self) }
}

All of this is captured in this snippet, which you can easily add to XCode.

Clone this wiki locally