The first libraries I am sharing are primarily my CoreExtensions
library… this is a dumping ground for any utilities or additions to the standard library or libraries that “ship” with Swift (by that I mean in Xcode on Apple platforms, as I mentioned in the superthread I may eventually start adding other platforms and factor Apple-only stuff out)… and since that library has a few dependencies on other small libraries of mine, I have to include those in the inaugural showcase as well.
The small libraries CoreExtensions
depend on are:
Assertions - Test assertions that throw
Stubbing - A library for generating random data in tests
Synchronization - Some synchronization primitives for thread safety (the blocking, not async/await
type)
These libraries are not very “interesting”… they don’t really showcase any advanced Swift design. They’re just low level utilities, a big chest of tools I end up reinventing every time I start a new Swift development engagement. They form the underlying support for the other libraries that do get into the advanced design space.
However there are a few interesting tidbits I can discuss about them. Also, while tossing all these utilities together into packages wasn’t a huge effort, testing all of them was. I’ve been slugging through getting to 100% test coverage on CoreExtensions
for months. Testing the async utilities in particular was very challenging. Correspondingly the tests are pretty intriguing, anyone who is mystified by how to test not just code that is concurrent but testing concurrency itself should take a look at them.
The Assertions
library is to replace XCTAssert…
functions with functions that throw on assertion failures. Theres’s a setting that is supposed to make it work this way, but (at least 1.5 years ago or so when I last checked) it doesn’t work with async
tests (I filed a radar for this). So I just wrote my own. I also have trouble with XCTAssert…
functions all taking autoclosures. For example if you try XCTAssertEqual(await getFirst(), await getSecond())
, it thinks you’re passing async
closures to XCTAssertEqual
, which of course aren’t supported. That’s why I split them into ones that take values and ones that take regular (not auto) closures.
Stubbing
contains the first macro I ever wrote for generating random data in tests. The next logical step is to add support for all enums, including with associated values, which I’ll try to add soon.
Some interesting points about the Synchronization
library:
- First, it is not designed for optimal performance. I’m not doing any of the
Builtin
trickery to get stable struct addresses, so most of these types arefinal class
es, which leads to very suboptimal memory layout (cache invalidation from jumping between a value and its associated lock). It’s just made to be logically correct. The Standard Library now hasMutex
and I only haven’t switched to that because it’s not a readers-write lock (which is frankly almost certainly an irrelevant micro-optimization to begin with, and because of the cache thrashing probably performs much worse). Eventually I may try to mimic the Standard Library’s approach and turn these all into non-copyable structs. Synchronized.wrappedValue
originally usedget
andset
. But this is not just unperformant, it’s incorrect. If you do something likevalue.wrappedValue += 1
because the in-out behavior splits it into two atomic operations. I wasn't suredefer
works the way I wanted it to inside a_modify
but testing confirmed it does.- The
Event
implementation was more complicated than I originally thought, because it’s incorrect to wait on a condition variable without a predicate (due to spurious wakeups). - It took a while to convince myself the implementation of
AnyConditionVariable
is correct… specifically where on thenotify
calls it just locks and immediately unlocks the private mutex, instead of locking around the calls to the “real”notify
functions. I saw from this that’s how libc++ implements it, and had to think carefully about it to accept that really is good enough.