Call Once
30 Jan 2026In this post I explore Swift’s @propertyWrappers and parameter packs to implement a common behaviour.
I ran into some code that captured work in a closure and ensured it was only invoked once. The pattern for this one-shot closure looked like this:
class Example {
private var delayedWork: (() -> Void)?
func functionThatQueuesWork() {
delayedWork = { print("some work") }
}
func functionThatInvokesTheWork() {
if let delayedWork {
self.delayedWork = nil
delayedWork()
}
}
}
Upon seeing this I wondered a) if we had a helper for this pattern b) if not what could the API look like. Basically I was nerd sniped into exploring what a reusable API could look like.
A Basic Property Wrapper
As always I find it best to start with concrete types then try and make things generic later on. The closure above is pretty simple - it takes no arguments and returns nothing so it’s a good place to start.
A rough scaffold to get some types lined up would look like this:
@propertyWrapper class CallOnce {
var wrappedValue: () -> Void
init(wrappedValue: @escaping () -> Void) {
self.wrappedValue = wrappedValue
}
}
This compiles but doesn’t really do anything useful for us as it’s literally just wrapping a closure with no additional behaviour.
We need to mirror the previously mentioned logic, which was to store the closure then ensure it’s only called once.
We currently have storage with var wrappedValue but we need to arrange for this to be some kind of no-op after it’s been invoked.
The simplest thing I can think of is to override the setter for wrappedValue and wrap the incoming closure with the additional logic.
In order to do this we’ll need to define some backing storage.
Let’s start with storing the closure by adding some storage and just delegating to that
@propertyWrapper class CallOnce {
private var function: () -> Void = {}
var wrappedValue: () -> Void {
get { function }
set { function = newValue }
}
init(wrappedValue: @escaping () -> Void) {
self.wrappedValue = wrappedValue
}
}
Here I’ve opted to keep the wrappedValue as non optional and instead I’m just going to use a no-op instead of assigning function = nil.
To handle the functionality of changing the passed in closure to a no-op I’ll add a helper method that both init and the setter can use.
@propertyWrapper class CallOnce {
private var function: () -> Void = {}
var wrappedValue: () -> Void {
get { function }
set { decorate(newValue) }
}
init(wrappedValue: @escaping () -> Void) {
decorate(wrappedValue)
}
private func decorate(_ function: @escaping () -> Void) {
self.function = { [weak self] in
defer { self?.function = {} }
function()
}
}
}
I capture self weakly so the wrapper doesn’t participate in a retain cycle with the stored closure if it’s already gone, the no-op behavior is fine.
You’ll probably have noticed by now that this is making no attempt to be thread safe and accessing this property wrapper from the correct isolation context is left to the caller.
This all works great now but only for simple closure types.
Making it slightly more generic
To make this slightly more reusable we could open it up to allow any return type.
This is fairly simple to do as we just need to introduce one new generic and slot it in to any place where we currently have a return type of Void.
- @propertyWrapper class CallOnce {
- private var function: () -> Void = { }
+ @propertyWrapper class CallOnce<Output> {
+ private var function: () -> Output? = { nil }
- var wrappedValue: () -> Void {
+ var wrappedValue: () -> Output? {
get { function }
set { decorate(newValue) }
}
- init(wrappedValue: @escaping () -> Void) {
+ init(wrappedValue: @escaping () -> Output?) {
decorate(wrappedValue)
}
- private func decorate(_ function: @escaping () -> Void) {
+ private func decorate(_ function: @escaping () -> Output?) {
self.function = { [weak self] in
- defer { self?.function = {} }
+ defer { self?.function = { nil } }
return function()
}
}
}
Again this is working but wouldn’t it be nice if we could support any shape of function?
Parameter packs to the rescue
The way this is going to work is that we are going to add a parameter pack in a similar way to how we added the Output generic.
Swift’s parameter packs let us abstract over an arbitrary number of parameters in a function signature.
When we introduce the generic we use the each syntax then anytime after that when we want to use them again we use repeat each.
- @propertyWrapper class CallOnce<Output> {
- private var function: () -> Output? = { nil }
+ @propertyWrapper class CallOnce<each Argument, Output> {
+ private var function: (repeat each Argument) -> Output? = { (_: repeat each Argument) in nil }
- var wrappedValue: () -> Output? {
+ var wrappedValue: (repeat each Argument) -> Output? {
get { function }
set { decorate(newValue) }
}
- init(wrappedValue: @escaping () -> Output?) {
+ init(wrappedValue: @escaping (repeat each Argument) -> Output?) {
decorate(wrappedValue)
}
- private func decorate(_ function: @escaping () -> Output?) {
- self.function = { [weak self] in
- defer { self?.function = { nil } }
- return function()
- }
+ private func decorate(_ function: @escaping (repeat each Argument) -> Output?) {
+ self.function = { [weak self] (argument: repeat each Argument) in
+ defer { self?.function = { (_: repeat each Argument) in nil } }
+ return function(repeat each argument)
+ }
}
}
Without the diff markup that looks like this
@propertyWrapper class CallOnce<each Argument, Output> {
private var function: (repeat each Argument) -> Output? = { (_: repeat each Argument) in nil }
var wrappedValue: (repeat each Argument) -> Output? {
get { function }
set { decorate(newValue) }
}
init(wrappedValue: @escaping (repeat each Argument) -> Output?) {
decorate(wrappedValue)
}
private func decorate(_ function: @escaping (repeat each Argument) -> Output?) {
self.function = { [weak self] (argument: repeat each Argument) in
defer { self?.function = { (_: repeat each Argument) in nil } }
return function(repeat each argument)
}
}
}
This is pretty handy and can handle any shape of function now. If we applied this to the original code we’d have this
class Example {
@CallOnce private var delayedWork: () -> Void? = { nil }
func functionThatQueuesWork() {
delayedWork = { print("some work") }
}
func functionThatInvokesTheWork() {
delayedWork()
}
}
Now if we instantiate this type, enqueue some work and repeatedly invoke it we’d only see one log
The initializer syntax is a little clunky because the wrapper expects an initial closure, even if it’s just a no-op.
let example = Example()
example.functionThatQueuesWork()
example.functionThatInvokesTheWork() //=> "some work"
example.functionThatInvokesTheWork() //=> nil
example.functionThatInvokesTheWork() //=> nil
Conclusion
I’m not sure this is actually useful but it’s always a fun exercise to try and see how you can utilise different features to craft the functionality you need. I find knowing the available primitives and having the hands on experience of piecing them together is really powerful for helping me solve problems and just generally not be phased when a challenge comes in that I’m unfamiliar with. Even if this exact wrapper never ships, the exercise paid off by forcing me to combine property wrappers and parameter packs into something concrete.