Swift weak reference assertion

If you find these questions interesting, let me demonstrate how you can write a concise and self-explanatory Swift test case that verifies correct reference type.

Weak verificationWhether your code keeps a strong or weak reference to some other object is crucial for non-garbage-collected languages like Swift.

Even a tiny mistake may lead to a leak, invalid behavior or even crash.

Although I don’s see many developers writing them, I believe that verification on a unit test level for misaligned reference type should a part of the implementation cycle— no matter if you practice TDD approach or not.

Maybe our developer community categorizes them as second-class citizens because we are not sure how to write them and their readability is often far from perfect? Let’s consider the simplest possible test that checks if an instance of a listener added to MasterClass’s is kept weakly:We can identify several code-smells there, including:unnecessary weakListener — duplication of listener just to inspect if a weak reference becomes nilOptional type of a listener (with force unwrap )— to let it be nilledmanual assignment (weakListener=listener) of a strong reference to a weak oneEven if you fix the second issue by introducing a scope that automatically releases listener at the end of its lifetime, this could end up with even more esoteric code:Tip: Swift’s do block we often identify with do-catch structure that catches Errors, while standalone do keyword is still a valid scope generator.

As you may expect, within a body of suchdo block you cannot call try since potential thrown Error wouldn’t be handled.

Alternatively, you may find XCTestCase’s addTeardownBlock function useful here.

Similarly to defer keyword it defers the execution a block until all other commands in a test case finish — practically just before tearDown function.

Nifty approach yet with a bit limited flexibility due to potential execution reorder.

Weakly-scoped — `with` inspirationMany modern languages (like Kotlin) provide setup functions intended to build up an instance in a self-contained scope (often named as with,apply), like:In a discussion started by Erica Sadun, the Swift community considered adding it to a language.

Ignoring the self-rebinding syntax, the implementation would be really straight-forward — take a starting instance T, mutate it in a block that takes that reference’s copy as an inout parameter and return mutated value:Maybe you already noticed that our initial problem resembles the solution that discussed with tries to solve.

To verify weak reference we had to:take initial/starting value of some generic T reference typepass it to the block body that presumably leverages it (e.

g.

adds a listener, send as a function argument etc.

)observe our initial instance weak reference if it is nilCombining the approaches in the first paragraph and with implementation, one may try to write it as follows:At glance, it looks OK but the above snippet doesn’t work as one might expect.

Even the dummies weaklyScopedNaive(MasterListener(), action: {_ in }) always returns non-nil value because first(v) argument keeps a strong reference until the end of weaklyScopedNaive function, making in practice weakValue a strong reference.

Weakly-scoped solutionTo overcome that problem we should transfer instance generation to the scope controlled by us and observe a weak reference after the scope destroy.

No matter how complex that sounds, Swift provides such feature of the box — @autoclosure.

Originally, it was designed to instantiate heavy instances on demand, to not compromise performance but it perfectly suits our need.

Let’s introduce small changes to our function’s definition (add @autoclosure and rethrows):autoreleasepool (don’t mix up with autoclosure) could be useful if you play with Objective-C objects that might receive autorelease message (instead of release one that always happens for Swift class), that deffers deallocation until end of autoreleasepool.

If you don’t expect Objective-C instancess, do scope is enough.

Let’s come back to our initial test case and try to rewrite it using weaklyScoped helper:It presents the essence of our test case scenario with a clear separation into Arrange, Act, Assert sections and elimination of noisy assignments.

All the commands perform in a synchronous manner so complex scenarios with several steps also can apply this technique.

SummaryTest cases that verify reference types (strong vs weak) can eliminate plenty of invisible, nasty bugs affecting memory footprint or invalid behavior.

If you ever found them clumsy and counter-intuitive to read, with the presented approach we can eliminate most of their drawbacks.

 Although the weaklyScoped implementation uses some advanced Swift features (autoclosure, rethrow ) the final test case is simple and self-explanatory, even for beginners.

Add-on:The usability of weaklyScoped function exceeds that trivial scenario with a listener or delegate.

You can leverage it for leak detection or even completion-cancellation.

For the inspiration on how to track them, see snippet below:.

. More details

Leave a Reply