alkenso/swiftspellbook
SwiftSpellbook is a collection of small Swift libraries for Apple platform development. It grew out of utilities reused across macOS and iOS projects. Pick the products you need; the package has no external dependencies.
Motivation
While participating in many projects (macOS and iOS) I use the same tools and standard types extensions. Once I've decided stop to copy-paste code from project to project and make single library that covers lots of developer needs in utility code.
Libraries
The package is split into independent products. Add only the ones you need; every product except SpellbookCrash builds on top of SpellbookFoundation.
| Product | Use it for | | --- | --- | | SpellbookFoundation | Everyday extensions and utilities: collections, errors, files, concurrency, observable values, logging, low-level interop | | SpellbookHTTP | Building URLRequests declaratively and sending them with async/await or callbacks | | SpellbookBinaryParsing | Reading and writing binary formats byte by byte | | SpellbookGraphics | Platform-neutral RGB colors and Core Graphics geometry/image helpers | | SpellbookCrash | Adding your own diagnostic messages to Apple crash reports | | SpellbookUI | Small SwiftUI conveniences | | SpellbookTestUtils | XCTest helpers: temporary directories, test bundle access, CI-friendly timeouts |
SpellbookFoundation
The core of the package: extensions to the standard library and Foundation, plus a set of small, focused types that remove boilerplate you would otherwise copy between projects.
Collections, standard types and utilities
- Collections:
subscript(safe:),removingDuplicates(),sorted(by: \.keyPath),min/max(by: \.keyPath),firstMapped/lastMapped,keyedMap,recursiveMap,rotated,appending,Dictionary[key, create:]and more. - Standard types:
Data⇄ hex string and POD conversion,Optional.get()that throws a meaningful error instead of force-unwrapping,Optional[default:],Result.success/.failure,URL(staticString:),Stringpath helpers, key-value string parsing,TimeInterval.minutes(:)/.hours(:),Date.inPast/inFuture,UUID.zero. - Comparable:
clamped(to:), the@Clampedproperty wrapper andRawComparablefor enums with comparable raw values. - Diffing:
CollectionDiffandDictionaryDiffdescribe added, removed and changed elements between two snapshots. - Helpers:
updateValue(_:using:)for inline mutation of value types,throwingCast,WildcardExpressionfor glob-like (*,?) string matching,Benchmark.measurefor quick timing,SBUnitTime/SBUnitInformationStorageunits. - Environment:
BuildEnvironment.isDebug,RunEnvironment.isXCTesting,isRunFromXcode,isXcodePreview,isSimulator. - Protocols:
EmptyInitializable(types with a natural "empty" value) andSelfIdentifiable(Hashabletypes that serve as their ownid).
Codable and dictionary parsing
ObjectEncoder/ObjectDecoder: a uniform way to pass around "how to encode/decode" — JSON, property list,JSONSerializationor a custom format.DictionaryCoderconvertsCodablevalues to and from[String: Any].- Property wrappers
@JSONSerializable,@PropertyListSerializable,@KeyedArchiveSerializableembed non-Codablevalues intoCodabletypes. DictionaryReader/DictionaryWriterread and write deeply nested[String: Any]values (plists, JSON objects) by coding path or dot path — e.g.dict[dotPath: "settings.network.timeout", as: Int.self]— with descriptiveDictionaryCodingErrors.
Errors
CommonError: a ready-to-use error with codes such as.invalidArgument,.notFound,.unexpected, bridging cleanly toNSError.NSErrorhelpers for POSIX,OSStatusand Mach codes, including throwing wrappers around C calls that pick uperrnoor the returned status automatically:try NSError.posix.try(unlink(path) == 0),try NSError.osstatus.try { SecItemDelete(query) }.CustomErrorUpdatingto attach extra context (userInfo, related object, name) to any error, andError.secureCodingCompliant()for sending errors over XPC.catchingAnyturns Objective-CNSExceptions and C++ exceptions into Swift errors.IOKitErrorwrapsIOReturncodes (macOS only).
Filesystem and Bundle
FileManagerextensions:fileExists(at:),directoryExists(at:),removeItemIfExists,createDirectoryTree, recursive attribute setting,stat, extended attributes (xattr,setXattr,listXattr,removeXattr), unique file names.FileEnumerator: a lazySequenceof URLs over multiple locations with type filtering and per-directory skip/descend control.FileStore: read and write values to files through a pluggable store — the standard disk store, an in-memory store for tests,Codablecoding, or a queue-synchronized variant.TemporaryDirectory: create, set up and clean up scratch directories.Bundleextensions:name,shortVersion,version,existingURL(forResource:withExtension:).
Threading and concurrency
- Locks:
UnfairLock(os_unfair_lock) andRWLock(pthread_rwlock), plusNSLockinghelpers. Synchronized<Value>: a value protected by a lock or dispatch queue of your choice, withread/writeclosures and arithmetic shortcuts.Atomic<Value>andAtomicFlagfor simple thread-safe values and one-time flags.AsyncPromise/AsyncFuture: set a value once, await it from many places.AsyncSerialDispatchQueueruns async operations strictly one after another.BlockingQueue: a producer-consumer queue with blockingdequeueand cancellation.ConcurrentBlockOperation: anOperationthat finishes when your async work calls completion.- Bridging sync and async code:
synchronouslyWithTask,synchronouslyWithCallback,DispatchQueue.syncOnMain. DispatchQueuehelpers:debounce,asyncAfter(delay:),asyncPeriodically.Task.sleep(forTimeInterval:),UncheckedSendable,synchronized(::)(Objective-C@synchronizedequivalent).
Value observing
A lightweight, Sendable-first alternative to Combine / KVO for sharing state between components:
ValueStore<Value>: holds a mutable value and notifies observers about every change (ValueChangewithold,newand optionalcontext). Stores can be scoped to a sub-property (scope(\.settings)) so each component only sees what it needs.ValueObservable<Value>: a read-only, observable projection of a store.ValueView<Value>: a read-only accessor without observation.ValueBroadcast<Value>: fire-and-forget event notifications.ValueAsk<Request, Response>: ask all attached responders and collect their answers.ValueObserverconfigures delivery —sync,async, on a specific queue, withmap/filter/compactMap. Any source can also be consumed as anAsyncStream.- Property wrappers
@ValueStored,@ValueObserved,@ValueViewed, andObservableObjectadapters for SwiftUI.
let store = ValueStore(initialValue: Settings())
let subscription = store.observe(.sync { change in
print("Changed from \(change.old) to \(change.new)")
})
store.update(\.isEnabled, true)
for await change in store.scope(\.isEnabled).stream() { ... }Types and property wrappers
Resource<T>: RAII-style ownership — runs a cleanup closure on deinit (e.g. free a pointer, delete a temporary file);DeinitActionfor arbitrary "run on deinit" logic.Box,Weak,Unowned,Indirect: reference, weak and indirect wrappers usable as values and in collections.Closure/ThrowingClosure: store and compose callbacks (one-shot, dispatched on a queue).- Small general types:
Either,Pair,KeyValue,Change,ProgressValue,EmptyCodable.
Logging
SpellbookLoggerwith levels (verbose…fatal), subsystem/category sources and pluggable destinations (print,NSLog, or your own closure). The package logs its own internal issues through it, so you can route them into your logging system.
Combine
Cancellation— aSendablecancellable that is cancelled on deinit — and helpers to store it.ProxyPublisher,ProxySubscriber,ProxySubscriptionfor building custom publishers;Publisher.mapToChange,Cancellable.capturing(_:).
Low level and system
unsafepointer helpers and theCPointerprotocol for C interop;SafePOD/UnsafePODaddCodable/Hashableto C structs (stat,timespec,audit_token_t, …).BridgedCEnumfor Swift-friendly wrappers around C enums.- Mach time conversions:
TimeInterval(machTime:),Date(machTime:). audit_token_taccessors (pid, euid, …) andNSXPCConnection.auditToken(macOS only).DeviceInfo: hardware UUID and serial number on macOS, model name on iOS``.
SpellbookHTTP
A thin, testable layer over URLSession.
HTTPRequestdescribes a request declaratively: URL,HTTPMethod, query items, headers, and a body built fromEncodablevalues, JSON/plist objects or raw data. It converts toURLRequestwhen sent.HTTPParameters,HTTPHeader,HTTPQueryItem,HTTPAuthorizationType(.basic,.bearer) are typed building blocks for headers and query strings.HTTPClientsends requests with async/await or completion handlers and returns rawDataor decoded objects viaObjectDecoder. It can carry default headers for every request.HTTPClientProtocolmakes it easy to mock in tests.HTTPResult<T>keeps the decoded value together with itsHTTPURLResponse, so status codes and headers stay available.
var request = HTTPRequest(urlString: "https://api.example.com/items", method: .get)
request.query.set("10", forKey: "limit")
request.headers.setAuthorization(.bearer, token)
let result = try await HTTPClient().object(for: request, decoder: .json([Item].self))
print(result.response.statusCode, result.value)SpellbookBinaryParsing
Read and write binary file formats and protocols (headers, Mach-O, custom wire formats) without manual pointer arithmetic.
BinaryReaderreads integers of any width, fixed-size (POD) values and raw byte ranges — either sequentially withread/skip/seek, or at explicit offsets withpeekwithout moving the cursor. It reads fromDataor any customBinaryReaderInput(e.g. a file handle).BinaryWriterwrites values sequentially or at offsets into anyBinaryWriterOutput;DataBinaryWriterOutputcollects the output intoData.BinaryParsingErrorreports out-of-bounds reads and other malformed input instead of crashing.
SpellbookGraphics
Color and Core Graphics helpers that are independent of any UI framework.
RGBColor: aCodable,HashableRGBA color. Create it from hex strings ("#FF8800"),CGColor,NSColororUIColor, and convert back to any of them or to a SwiftUIColor. Includesrandom(),gray(_:)and opacity emulation.- Geometry: arithmetic on
CGPointandCGSize, scaling,area,aspectRatio,CGRect.center, centering one rect within another, vertical flipping between coordinate systems. CGImage: encode to and decode from PNG/JPEG/otherUTTypeformats, read from and write to files.
SpellbookCrash
Make crash reports explain themselves. Messages you attach appear in the Application Specific Signature or Application Specific Backtrace block of the system crash report, so you can see what the app was doing when it crashed.
CrashReportAugmentation.addMessage/removeMessageattach and detach messages, targeting the report's signature or backtrace block.CrashReportAugmentation.withMessagescopes a message to a synchronous or async operation — it is present only while the work is running.CrashInfoexposes the underlying__crash_infosection for advanced use.- Has no dependencies, not even
SpellbookFoundation, so it can be linked anywhere.
try CrashReportAugmentation.withMessage("Migrating database v3 → v4") {
try migrateDatabase()
}SpellbookUI
SwiftUI conveniences.
View.modify { view in ... }applies a transform conditionally (e.g. an OS-version-specific modifier) without breaking the modifier chain. Returningnilfrom the closure keeps the original view.
SpellbookTestUtils
Helpers for XCTest suites.
testBundleandtestTemporaryDirectoryonXCTestCasegive access to test resources and an isolated scratch directory.waitForExpectations(timeout:)returns the wait error (if any) and uses a short, configurable default timeout (XCTestCase.waitTimeout).XCTestCase.waitRateandTimeInterval.testSeconds(_:)scale all timeouts at once — set theSPELLBOOK_TEST_SLEEP_RATE_x5(x2, x10, …) compilation flag on slow CI machines instead of editing tests.TestDataprovides sample URLs and file paths;TestErroris a simple error to throw from mocks.
Package Metadata
Repository: alkenso/swiftspellbook
Default branch: main
README: README.md