Owning AnyCancellable with Cancellor
Swift 5.4 officially introduces @resultBuilder
, which is a way to create custom DSLs and generally create more semantic APIs (think of the way you write SwiftUI today).
I wanted to play with it and create something useful for Combine developers so I thought “Would it not be handy to batch-add publisher subscriptions to an owned list of cancellables?”.
I remembered back in the day I used an extension on NSObject
that adds automatically a dispose bag to all objects in case you need one created by Ash Furrow and I thought I’d put together something in the same fashion.
Here’s the completed code source, in case you’d like to skip the implementation details below: https://github.com/icanzilb/Cancellor.
An owned list of subscriptions
First I added a lock and a raw key value to NSObject
- you need the latter to “assign” a dynamic property to an Objective-C object, and the former to avoid data races:
|
|
Note: Arguably, using a single lock is not very efficient if you create large amounts of subscriptions all the time. But it should suffice for most cases.
Then I added a method that takes a list of AnyCancellable
and adds it to a list of cancellables assigned to the current object. (And create an empty list if there is none found assigned already.)
|
|
Now for the fun part - adding the @resultBuilder
. I played with few scenarios but in the end it turned I only ever needed the @resultBuilder
to collect the cancellables and pipe them through to addCancellables()
which is already fleshed out.
|
|
As evident by buildBlock
s signature, the builder gets a list of AnyCancellable
values and just returns them.
ownedCancellables
Next, CancellablesBuilder
can be used back in NSObject
to provide a DSL-like API for managing subscriptions:
|
|
Sweet! With ownedCancellables
in place you can easily batch-create all your Combine subscriptions, let’s say in one of your view controllers, like so:
|
|
This code will collect all the cancellables for your subscriptions and store them in a dynamically created cancellables list assigned to your view controller.
If you don’t cancel your subscriptions manually, at the time the view controller is popped out or otherwise dismissed your subscriptions will all be cancelled and released as well.
owned(by:)
For kicks we can also add an extension on AnyCancellable
to provide more-reactive (tm) way to manage a subscription. In effect we’d like to “tie” the lifetime of the subscription to the lifetime of a random object.
With minimal effort the AnyCancellable
extension looks like this:
|
|
Now, if you have a view model like so:
|
|
And you need for a given subscription to last as long as the view model is around you can do this:
|
|
When vm
’s value is released from memory via setting it to nil
or is otherwise released from memory, your subscription will automatically be cancelled as well.
Where to go from here?
I’ve published the code under https://github.com/icanzilb/Cancellor.
I think with some more effort this could become a handy tool. Some questions I’ve been considering are:
- how to manage the list so that cancelled subscriptions are removed? E.g. if you have a very long-lived view controller that always creates new subscriptions how to manage the size of the list?
- what more could a tool that manages subscriptions do?
- what if there is an
ownedCancellables
parameter that automatically logs the subscriptions in Timelane?
Hit me up with your ideas on Twitter at https://twitter.com/icanzilb.