Getting Rid of kCGImageSourceTypeIdentifierHint Error when Loading NSImage from a File

This post is mostly for people googling for a similar error message as I did, being stuck clueless.

Here it is, a message in the Xcode log that you can get when you load an NSImage:

extractOptions:147: *** unknown hint identifier ‘kCGImageSourceTypeIdentifierHint:dyn.age80q2pssvu1g8pftb4gn5xxrf106’ – ignoring…

The source of the problem, for me, was loading an image via NSImage.init(contentsOf:) where the URL had a custom file extension. Say you store a PNG image with the .fantasyextension path extension, the message above is what you may get. The dictionary value is prefixed with dyn.age80, followed by a hash of some sort. The hash changes depending on the file extension. For .attachment, I got "dyn.age80c7dyqfv0u5pfr34a", for example.

It’s okay to load a PNG file from a wrong extension, e.g. .jpeg; you won’t get the error there.

My best guess is that path extensions that no app has registered will produce this message. I wasn’t better off without a file extension, either, though. For example "dyn.age8u" is the value I get when the file name is the number 2428272662287976958 (which is the hashValue of the file name I used before to try what happens then).

Apparently, kCGImageSourceTypeIdentifierHint is a CoreGraphics image-loading hint that is automatically provided based on the path extension when you try to load an NSImage from a URL where the file extension is not something image-y. I don’t know if there’s a way to get rid of this hint when loading, and I don’t want to create a CoreGraphics image source myself. I bypass the convenience hint provision completely, instead.

Here’s a quick fix for you:

func image(fromURL url: URL) -> NSImage? {
    guard let data = try? Data(contentsOf: url) else { return nil }
    guard let image = NSImage(data: data) else { return nil }
    return image
}

The path extension-based “type identifier” hint is a useful feature. But if you know that you expect a properly encoded image, reading in a hint-less piece of Data works just as well.

So don’t worry if you see this error. The file may be totally fine. It’s just that AppKit cannot use the misleading file extension information for anything.

Now Hiring: Freelance Mac Dev

I’m looking to hire on help for my Mac app projects. You can apply now and I’ll be looking forward to pick someone in March.

The Available Topics

Ordered by importance:

  1. Wiring NSTextView and friends and use the Cocoa Text System to write a better, no, the best Markdown syntax highlighter. I wrote a ton about the topic myself, and I have a couple of interesting approaches prepared, but my time is too limited to make the ideas reality as quickly as I would like, so help (aka paid labor) is appreciated! This includes variants based on C libraries, server/client like service architecture, and pure Swift. We can also talk about Open Sourcing the result if that is a thing you burn for.
  2. Writing a data model and UI for better file content tracking (got the file/directory tracking part already) to integrate project progress better into the WordCounter.
  3. Designing a UI to manage user-created scripts in an app and execute these manually or on a regular basis. A poor man’s cron, so to speak, but more accessible to non-programmers. Scripts will usually work with plain text content and produce more plain text stuff.
  4. Writing a replacement for, or customizing the vanilla NSTableView of TableFlip to make multi-line cell contents a thing while keeping the UI responsive. A replacement component would be more greenfield-ish than improving the existing UI code, of course.
  5. Actually not for Mac: Adding iCloud sync to a Core Data-backed iOS app. It’s a simple latest-change-wins merge, with a data model with only 1 Entity/table that predates easy iCloud syncing, though. It’s part Objective-C, and I’m fine adding on more Objective-C.

Have another look at my apps to see what kind of stuff I work on.

The Setting

This list of stuff is mostly about self-contained modules. You can think of these to be greenfield projects, so you don’t need to deal with existing code most of the time. We will design components from UI to API together and you’ll get to be as creative as you want and the project allows.

Depending on what you eventually want to do with the rest of your life and how well we resonate with each other, consider the topics above to be entryways into a long-term cooperation.

Timing is flexible. Part-time is totally fine. For longer-term nuts to crack, it even makes sense to spread out the workload a bit, especially in the beginning. After all, you’ll need to get accustomed to everything, and I believe it’s nicer for both of us if you have more headspace. The pressure of trying to churn out 40 hours right from the get go might actually get in the way. The opposite sounds a lot more sensible, so if you only have 2 days per week to spend, no problemo.

Please note there will be no SwiftUI or Combine for the foreseeable future, but I’m game for RxSwift.

The Requirements

I’m looking for experienced developers with a knack for the problem of their choosing, because I’m happy if you’re happy at work, and we’re both more happy if you pick a topic that motivates you to excel.

If you really, absolutely want to tackle more boring and menial tasks, I can see what I can do after we finished some of the interesting stuff.

Get in touch if

  • you are interested in a topic mentioned above,
  • you have experience with the problem domain and macOS app development,
  • you are available “soon”,
  • you can work as a freelancer/contractor.

Don’t know if “experienced” applies to you? Introduce yourself and we’ll find out!

Auto-Growing NSTextField

I ran into a situation with a window that is movable by its background and a text field inside it. The text field draws neither border nor background, so to the user, it looks like an input field directly on the window’s background. Much like the Spotlight search box.

You can hover with the mouse over the empty macOS Spotlight widget and drag the window around anywhere

Even when a window’s isMovableByWindowBackground is enabled, a NSTextField captures click and drag events, and it changes the pointer to a text insertion variant (NSCursor.iBeam). All that is weird when you don’t know there’s a text field at that point. And you cannot know how large the text field is if nothing is drawn there. When the background is gone, this feels pretty weird.

One could subclass NSTextView and change mouseDownCanMoveWindow to return true. That would let some drag operations through, but even for controls that are supposed to be clickable but not draggable, like table view lists, this produced a lot of micro movements during my tests. That’s partly because I use an external mouse: the touch bar on MacBooks usually doesn’t register move/drag commands that quickly. I discarded this cheap way out for that reason, though.

My solution was to shrink the text field to fit the content.

  1. Give it a >= trailing Auto Layout constraint so it can get smaller than the window,
  2. Set the window’s contentView‘s width to a constant value,
  3. Update NSTextField.intrinsicContentSize while the user is typing to fit the typed text.

Spotlight, by the way, doesn’t use NSTextField.placeholderString. I think Apple conditionally show/hide a label behind the text field, with isVisible wired to !String.isEmpty. For a regular placeholder to show, the text field has to be wide enough. But when the text field is as wide as the placeholder, the I-beam cursor should show when you hover over the placeholder text. With the Spotlight search box, it doesn’t.

Here’s my code for both variants: one using a placeholder, which makes the size caching a bit more complicated, and one that ignores the placeholder completely, so you have to wire a label like Spotlight does it.

Drop-in NSTextField subclass that uses the placeholder

  • It respects the placeholderString, either changed during runtime or set in Interface Builder, to affect the minimum width.
  • It adapts the intrinsicContentSize to match the visible contents of the field editor (or the placeholder, if the field editor is empty).
  • It reacts to programmatic changes of the stringValue property.
class AutoGrowingTextField: NSTextField {
    private var placeholderWidth: CGFloat? = 0

    /// Field editor inset; experimental value
    private let rightMargin: CGFloat = 5

    private var lastSize: NSSize?
    private var isEditing = false

    override func awakeFromNib() {
        super.awakeFromNib()

        if let placeholderString = self.placeholderString {
            self.placeholderWidth = sizeForProgrammaticText(placeholderString).width
        }
    }

    override var placeholderString: String? {
        didSet {
            guard let placeholderString = self.placeholderString else { return }
            self.placeholderWidth = sizeForProgrammaticText(placeholderString).width
        }
    }

    override var stringValue: String {
        didSet {
            guard !isEditing else { return }
            self.lastSize = sizeForProgrammaticText(stringValue)
        }
    }

    private func sizeForProgrammaticText(_ string: String) -> NSSize {
        let font = self.font ?? NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .regular)
        let stringWidth = NSAttributedString(
            string: string,
            attributes: [ .font : font ])
            .size().width

        // Don't use `self` to avoid cycles
        var size = super.intrinsicContentSize
        size.width = stringWidth
        return size
    }

    override func textDidBeginEditing(_ notification: Notification) {
        super.textDidBeginEditing(notification)
        isEditing = true
    }

    override func textDidEndEditing(_ notification: Notification) {
        super.textDidEndEditing(notification)
        isEditing = false
    }

    override func textDidChange(_ notification: Notification) {
        super.textDidChange(notification)
        self.invalidateIntrinsicContentSize()
    }

    override var intrinsicContentSize: NSSize {
        var minSize: NSSize {
            var size = super.intrinsicContentSize
            size.width = self.placeholderWidth ?? 0
            return size
        }

        // Use cached value when not editing
        guard isEditing,
            let fieldEditor = self.window?.fieldEditor(false, for: self) as? NSTextView
            else { return self.lastSize ?? minSize }

        // Make room for the placeholder when the text field is empty
        guard !fieldEditor.string.isEmpty else {
            self.lastSize = minSize
            return minSize
        }

        // Use the field editor's computed width when possible
        guard let container = fieldEditor.textContainer,
            let newWidth = container.layoutManager?.usedRect(for: container).width
            else { return self.lastSize ?? minSize }

        var newSize = super.intrinsicContentSize
        newSize.width = newWidth + rightMargin

        self.lastSize = newSize

        return newSize
    }
}

Variant that ignores the placeholder (so you have to add a label yourself)

  • It adapts the intrinsicContentSize to match the visible contents of the field editor.
  • It reacts to programmatic changes of the stringValue property.

You have to create a label below this text field with the placeholder value and then set up view controller wiring to conditionally hide and show the placeholder. Here’s some boilerplate to get you started, not taking into account programmatic changes to NSTextField.stringValue:

class MyViewController: NSViewController, NSTextFieldDelegate {
    @IBOutlet weak var placeholderLabel: NSTextField!
    @IBOutlet weak var textField: NSTextField!

    func controlTextDidChange(_ obj: Notification) {
        placeholderLabel.isHidden = !textField.stringValue.isEmpty
    }
}

And here’s the way simpler text field subclass:

class AutoGrowingTextField: NSTextField {
    /// Field editor inset; experimental value
    private let rightMargin: CGFloat = 5

    private var lastSize: NSSize?
    private var isEditing = false

    override var stringValue: String {
        didSet {
            guard !isEditing else { return }
            self.lastSize = sizeForProgrammaticText(stringValue)
        }
    }

    private func sizeForProgrammaticText(_ string: String) -> NSSize {
        let font = self.font ?? NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .regular)
        let stringWidth = NSAttributedString(
            string: string,
            attributes: [ .font : font ])
            .size().width

        // Don't use `self` to avoid cycles
        var size = super.intrinsicContentSize
        size.width = stringWidth
        return size
    }

    override func textDidBeginEditing(_ notification: Notification) {
        super.textDidBeginEditing(notification)
        isEditing = true
    }

    override func textDidEndEditing(_ notification: Notification) {
        super.textDidEndEditing(notification)
        isEditing = false
    }

    override func textDidChange(_ notification: Notification) {
        super.textDidChange(notification)
        self.invalidateIntrinsicContentSize()
    }

    override var intrinsicContentSize: NSSize {
        var minSize: NSSize {
            var size = super.intrinsicContentSize
            size.width = 0
            return size
        }

        guard isEditing,
            let fieldEditor = self.window?.fieldEditor(false, for: self) as? NSTextView,
            let container = fieldEditor.textContainer,
            let newWidth = container.layoutManager?.usedRect(for: container).width
            else { return self.lastSize ?? minSize }

        var newSize = super.intrinsicContentSize
        newSize.width = newWidth + rightMargin

        self.lastSize = newSize

        return newSize
    }
}

Boilerplate to Add AppleScript to Your macOS App in 2020

There’s not a lot to do, but the documentation is (1) old, (2) not maintained anymore, (3) very wordy. The steps involved are actually very simple. I’ll go through them and provide detail and links for further reading.

Open the Script Editor app. You’ll want to use that for test-driving your app.

Enable AppleScript in Your App

The steps from the now archived docs are:

  1. You need an .sdef file with the script command definitions,
  2. reference this file in your Info.plist, and
  3. enable AppleScript in your Info.plist as well.

First, create a script definition file ending in .sdef. It usually has the same name as your app, but you can pick whatever you want. For TableFlip, this file would be TableFlip.sdef, for The Archive I picked TheArchive.sdef.

Xcode won’t help you with a template here, so you might as well use touch from the Terminal or your favorie text editor and drag the file into your project later.

This is the bare minimum container boilerplate:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">
<dictionary xmlns:xi="http://www.w3.org/2003/XInclude" title="YOUR APP NAME HERE Terminonoly">
</dictionary>

Replace the title with the name of your app plus the word “Terminology”. That appears to be a convention.

Add this file to your Xcode project and make sure it’s part of the app target. You may want to double-check that the target’s Build Phases > Copy Bundle Resources now contains your .sdef file.

Second, open your app target’s Info.plist in Xcode and add two new rows:

  1. In the first row, enable AppleScript support. Set the key to NSAppleScriptEnabled (will become “Scriptable”) and the value to YES (it’s a boolean Xcode will recognize).
  2. In the next row, define which .sdef file exposes available commands. Set the key to OSAScriptingDefinition (will become “Scripting definition file name”) and the value to the file name of your .sdef, e.g. “TableFlip.sdef”. No need to specify a path.

Now build your project in Xcode.

Reveal the build product in Finder and drag the app bundle onto the Script Editor. When you drag app bundles onto the Script Editor, it’ll show a browsable scripting definition, aka the terminology your app exposes.

Since the boilerplate is empty, this should also be empty. For now.

Add default Cocoa commands to e.g. quit your app

How do you get started with defining script commands? – Thanksfully, there’s a default set of commands that is provided as a quick start.

You can open it in Xcode straight from the Terminal:

open /System/Library/ScriptingDefinitions/CocoaStandard.sdef

It looks like this at the moment of writing on my machine:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">

<dictionary title="Standard Terminology">

    <suite name="Standard Suite" code="????" description="Common classes and commands for all applications.">

        <command name="open" code="aevtodoc" description="Open a document.">
            <direct-parameter description="The file(s) to be opened.">
                <type type="file"/>
                <type type="file" list="yes"/>
            </direct-parameter>
                <result description="The opened document(s).">
                <type type="document"/>
                <type type="document" list="yes"/>
            </result>
        </command>

    ... lines 19--263 follow ...

    </suite>

</dictionary>

That’s the same structure as the boilerplate from above. You see that inside of a terminology <dictionary> element, there is a <suite> element that contains many <command> elements.

Your .sdef can only have 1 root level <dictionary> element, only one!, but that can contain multiple suited to group different purposes.

If you want, copy the whole code from the opening <suite> until the closing </suite> into your .sdef file. Note that you will want to have a look at each command and change it so it fits your app. Not everything from the CocoaStandard.sdef makes sense.

Let’s look at an example to see how you can customize this.

Customize the standard Quit command

If you look at the definition of the “quit” command in the CocoaStandard.sdef file, you’ll see this (broken onto multiple lines to preserve space):

<command name="quit" code="aevtquit" description="Quit the application.">
    <cocoa class="NSQuitCommand"/>
    <parameter name="saving" code="savo" 
            type="save options" optional="yes" 
            description="Should changes be saved before quitting?">
        <cocoa key="SaveOptions"/>
    </parameter>
</command>

In my note-taking app The Archive, there’s no notion of manual saving. Files are saved as you type. Yet the command definition above offers an optional parameter to the “quit” command. I don’t need that, so it has to go. I delete the whole <parameter>...</parameter> node.

My .sdef looks like this afterwards:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">
<dictionary xmlns:xi="http://www.w3.org/2003/XInclude" title="YOUR APP NAME HERE Terminonoly">
    <suite name="Standard Suite" code="????" description="Common classes and commands for all applications.">
        <command name="quit" code="aevtquit" description="Quit the application.">
            <cocoa class="NSQuitCommand"/>
        </command>
    </suite>
</dictionary>

You can paste this into your .sdef, too. Save the file, build the project, then drag the app bundle onto Script Editor again. It does not auto-update just because the underlying bundle did change during compilation.

Lo and behold – your first command appears!

The quit command is now available!

Test your commands

With Script Editor still open, create a new document. That’ll be the scratchpad for AppleScript interaction.

Use the power of the new “quit” command to see how things work.

tell application "THE APP NAME" to quit

You have to replace THE APP NAME with your app’s name. It appears to work well with the product name. In my case, that’ll be The Archive with a space, or TableFlip without one.

First, run the app from Xcode so it is open.

Then execute the script.

Did your app close? If so, congratulations!

If not, it could be because you typed

# Warning: will not work!
tell "THE APP NAME" to quit

That’ll send the “quit” message to the string. You want to send it to the application with that name.

I cannot show what an app looks like when it is quit. But I can show you the result of the command in Script Editor.

Where to go from here

I think it’s a good first step to redact the CocoaStandard.sdef to fit your app.

Heads up: Removing commands like the “quit” command from your app’s .sdef will actually not disable them. The app still responds to the standard messages; apparently, these are inherited and the suite with the same name and identifier can be overridden. So the commands just don’t show up in the Script Editor terminology view.

When you have cleaned up the “Standard Suite”, you can start adding your own.

To prototype your app’s scriptability, the archived docs recommend you start with the .sdef file first and don’t actually implement any functionality. Instead, you write test scripts in Script Editor and hit the “Compile” button. That’ll verify the syntax and show errors if you mistyped the invokation in the script, or botched the definition in you .sdef file.

I really like the idea of outside-in development of the syntax to see what kind of interactions you will want, and then you figure out how to create the appropriate command implementations.

You will be putting your commands into your own <suite></suite> node, with a unique name and a unique 8-character identifier. Whatever that is for, and why-ever that is limited to 8 characters. Legacy, I guess. We’ll have a look at custom command creation in another post.

Please note that you need to run your app from Xcode before you execute a Script Editor script. If you don’t, macOS’s launch services may figure that the best fit for an application of that name is in you /Application folder. Run your app from Xcode to make this debug version occupy the name and receive the AppleScript events.

Further reading:

NSProgress with KVO-based Finish Callbacks

Here’s NSProgress subclass that allows block-based callbacks for finishing the progress in a new callback called finishHandler that should work the same as the existing handlers.

By default NSProgress (or with Swift just Progress) comes with handlers for resumeing, pausing, and cancellation events, but not for finishing. I don’t know why.

This subclass uses Key–Value-Observations on the isFinished property (that’s actually "finished" by its Objective-C property name).

Note that you need to add a convenience initializer init(totalUnitCount:) yourself in both subclass variants because the Progress.init(totalUnitCount:) is not inheritable. Sad.

RxSwift Variant

This one is pretty short because you can model the KVO and its “dispatch only once please” condition very succinct:

class FinishObservableProgress: Progress {
    private var disposeBag = DisposeBag()

    var finishHandler: (() -> Void)? {
        didSet {
            disposeBag = DisposeBag()

            guard let finishHandler = finishHandler else { return }

            self.rx.observeWeakly(Bool.self, "finished", options: [.new])
                .filter { $0 == true }
                .distinctUntilChanged()
                .map { _ in () }
                .subscribe(onNext: finishHandler)
                .disposed(by: disposeBag)
        }
    }

    convenience init(totalUnitCount: Int64) {
        self.init(parent: nil, userInfo: nil)
        self.totalUnitCount = totalUnitCount
    }
}

Regular KVO Variant

If you don’t have RxSwift handy, regular KVO will do the trick just as well. I’ll introduce a private helper class to actually handle the observation so the progress doesn’t have to self-observe, which I always find a bit odd.

class FinishObservableProgress: Progress {
    private class Observer: NSObject {
        let callback: (() -> Void)

        init(callback: @escaping (() -> Void)) {
            self.callback = callback
        }

        /// Prevents multiple completions.
        private var didFinish = false

        override func observeValue(
                forKeyPath keyPath: String?,
                of object: Any?,
                change: [NSKeyValueChangeKey : Any]?,
                context: UnsafeMutableRawPointer?) {
            guard didFinish == false else { return }
            guard keyPath == "finished",
                let value = change?[.newKey] as? Bool,
                value == true
                else { return }
            didFinish = true
            callback()
        }
    }

    private var observer: Observer?

    deinit {
        if let observer = observer {
            self.removeObserver(observer, forKeyPath: "isFinished")
        }
    }

    var finishHandler: (() -> Void)? {
        get {
            return observer?.callback
        }
        set {
            self.observer = {
                guard let newValue = newValue else { return nil }

                let newObserver = Observer(callback: newValue)
                self.addObserver(newObserver,
                                 forKeyPath: "finished",
                                 options: [.new],
                                 context: nil)
                return newObserver
            }()
        }
    }

    convenience init(totalUnitCount: Int64) {
        self.init(parent: nil, userInfo: nil)
        self.totalUnitCount = totalUnitCount
    }
}

WhatsNewKit v1.2.0 Supports Multiple Update Notices at Once

I was getting back to fix a couple of small issues in The Archive and release an update when I noticed that one new feature I added, a statistics status bar, made most sense when hidden by default to avoid UI clutter. But how do people notice the new feature, then?

Well, that’s what the “What’s New” window is for. To make it support a history of amazing changes, I adapted my very own WhatsNewKit to support this. And there you go, WhatsNewKit version 1.2.0 is out now and supports multiple update notices!

Update notice switching is now baked right in

You pass it an Array<Update> with each Update.version specifying the app version a feature was introduced. By default, WhatsNewKit then determines the subset of all Updates since the last known update that was displayed. It then displays all that remain. That means you will get 1 update notice for the upcoming update of The Archive if you have last launched it yesterday. If you haven’t opened the app for more than half a year, you will get 2 update notices.

You can also forcibly show all known update notices, e.g. from The Archive‘s main menu when you pick Help > Show What’s New…

Get WhatsNewKit on GitHub

Thread Safe Property and Resource Access with the Transaction Wrapper

Here is a transaction type to copy & paste into projects to encapsulates thread-safe read/write access:

struct Transaction {
    let queue: DispatchQueue

    func read<T>(_ value: @autoclosure () -> T) -> T {
        return read(block: value)
    }

    func read<T>(block: () -> T) -> T {
        return queue.sync {
            block()
        }
    }

    func write(block: @escaping () -> Void) {
        queue.async(flags: .barrier) {
            block()
        }
    }
}

Just make sure to not use the main queue, because .sync call from main to main will deadlock your app!

It ensures you read values synchronously, which isn’t dangerous, and enqueue and execute write operations in order. This is useful if you need to access any resource from multiple threads and want to avoid the overhead of mutex locks.

I called it Transaction because the result is access to resources in a way similar to ACID transactions: complex operations are executed in a batch (Atomicity), write operations don’t get in each other’s way (Consistency & Isolation); the Durability part is debatable, though.

Usage example

Here’s how you’d wrap read and write operations for a totally contrived example:

class Counter {
    private static let queue = DispatchQueue(label: "counter", qos: .background)
    private var count = 0

    var tx: Transaction {
        return Transaction(queue: Counter.queue)
    }

    func increment() {
        tx.write {
            self.count += 1
        }
    }

    var currentValue: Int {
        return tx.read(count)
    }
}

Checking if this really does help

Here’s an example program to demonstrate the effects:

let counter = Counter()
for _ in (0 ..< 20) {
    DispatchQueue.global(qos: .background).async {
        // This is actually bad sample code because it's two calls where time passes in between
        counter.increment()
        print(counter.currentValue, terminator: ", ")
    }
}

It’s output:

10, 10, 10, 10, 10, 10, 10, 10, 10, 17, 10, 18, 18, 18, 19, 20, 20, 20, 20, 20

You see there are 20 print statements, but they don’t happen right after the increment. They are still in order, but apparently some “read” calls have been intercepted by other “write” operations and had to wait, e.g. the last 5.

Here’s one without the Transaction‘s queue being used, demonstating how random the values can appear if you read and write willy-nilly:

14, 14, 15, 14, 16, 15, 15, 19, 20, 20, 15, 20, 15, 20, 20, 20, 20, 20, 20, 20
            ^^      ^^  ^^              ^^      ^^

It’s even more apparent with 100+ iterations, but I won’t print these here.

Property Wrapper variant

It cannot be a struct because async mutations need to capture a mutable reference to self in the async setter.

Also keep in mind that one-line increments like += 1 actually equal one get plus one set call. That will produce results you do not anticipate. The counter example above with a naive += 1 ends at the value 5 after 100 iterations on my machine, for example, because a ton of reading happens pretty quickly and asynchronously before a write operation sets up the barrier.

Do access the backing property directly with the underscore prefix and call e.g. _counter.mutate { $0 += 1 } to do it all at once.

That is, in effect, similar to tx.write { self.count += 1 }.

@propertyWrapper
final class Transaction<Value> {
    private let queue: DispatchQueue
    private var value: Value

    init(wrappedValue: Value, queue: DispatchQueue) {
        self.queue = queue
        self.value = wrappedValue
    }

    var wrappedValue: Value {
        get { queue.sync { value } }
        set { queue.async(flags: .barrier) { self.value = newValue } }
    }

    func mutate(_ mutation: (inout Value) -> Void) {
        return queue.sync {
            mutation(&value)
        }
    }
}

And then use it like so:

class Counter {
    private static let queue = DispatchQueue(label: "counter", qos: .background)

    @TransactionWrapper(wrappedValue: 0, queue: Counter.queue)
    private(set) var count

    func increment() {
        _count.mutate { $0 += 1 }
    }
}

Conclusion and usage

I use the Transaction type for access to a cache and for object repositories. Works like a charm and the call site doesn’t get too distracting. A very useful tool.

Heads up: There’s no need to go all-in with this. The overhead of async calls with the .barrier flag is not to be underestimated. Simply setting the value is many magnitudes faste. Only protect resources that you absolutely need to, and only introduce concurrency when you cannot get by without. Network API calls should not write to resources via transaction; they should finish in the background and then produce the result to your app on the main queue.

I didn’t invent any of this. There’s plenty of discussion on the web. For some more recent iterations on the topic, see also:

Swift API Docs for String.index(_:offsetBy:limitedBy:) Is Still Misleading

When you look at the docs for String.index(_:offsetBy:limitedBy:), you get this description:

Returns an index that is the specified distance from the given index, unless that distance is beyond a given limiting index.

The “unless that distance is beyond a given limiting index” part got my attention. I remembered it to be a pain to use in practice. The overall API docs page looks way too innocent for that.

So what’s the example code?

let s = "Swift"
if let i = s.index(s.startIndex, offsetBy: 4, limitedBy: s.endIndex) {
    print(s[i])
}
// Prints "t"

Paste it in a Playground. You’ll see that, yes, this prints t.

But what if I change the offset to 5 to actually see how the limit at the limit works?

Here’s the changed code for you to copy and paste, with the 5 instead of 4:

//  /!\  Warning! Do not use in production!   /!\
let s = "Swift"
if let i = s.index(s.startIndex, offsetBy: 5, limitedBy: s.endIndex) {
    print(s[i])
}

What does it print?

Nothing, you hope?

Because the String is too short?

(cue sad trombone)

Surprise! It’s a runtime error!

“Ok, Christian, the API sample was just for demoing a valid use. You inferred something wrong from what you saw”, you might say.

Did I?

The String.index(_:offsetBy:limitedBy:) docs go on like this:

The next example attempts to retrieve an index six positions from s.startIndex but fails, because that distance is beyond the index passed as limit.

let j = s.index(s.startIndex, offsetBy: 6, limitedBy: s.endIndex)
print(j)
// Prints "nil"

… which is true, this code returns nil and does not crash. Oh, so it’s safe to try to get to indices beyond the bounds, nice, right?

Up next is this hand-wavy explanation:

The value passed as n must not offset i beyond the bounds of the collection, unless the index passed as limit prevents offsetting beyond those bounds.

Remember: This is an API documentation, not a marketing text. So is it safe to try to access an index beyond the bounds? It’s hard to say. All of this can be utterly misleading.

What does “prevent offsetting beyong those bounds” mean?

Is it allowing indexes up to or including the limit?

The docs don’t tell. You have to force yourself to consider this to be a vague statement and test its implications. Experiences programmers will undoubtedly do that thanks to years and years of trial by fire – but that doesn’t make the docs any better. If you start developing in Swift, you might not notice the vague statement here, and the omission of a specification of what happens when index + offset == endIndex.

If you copy the sample codes from the docs to play around, and only test the resulting algorithms in your program with an offset somewhere inside the bounds of the string and and somewhere way past the end of the string, you might be tricked into believing your algorithm is working as expected. – It doesn’t, though, because these tests made you overlook what happens when the result is equal to endIndex. That’s false confidence. The code does work for the cases you tested, yes, but it utterly crashes for one case.

So the crux is this: the offset may not be set so that the resulting index is at endIndex. Because that’s not a valid limit.1

From my understanding, the API docs is conveying the wrong information through these examples. (So my initial doubt in how simple it all looked was warranted, and now I’m becoming even more paranoid, great!) The function works real nice when you know what to do, but the String.endIndex doesn’t mean what you might take away from the sample.

The docs on endIndex says:

A string’s “past the end” position—that is, the position one greater than the last valid subscript argument.

You want the limitedBy: parameter to not be past the end, you want it be at at the end. Why is the sample code using an obviously incorrect approach to show how the function works?

That means you need to figure out the index before endIndex, provided the string is long enough to actually cover that. (An empty string has its startRange equal endRange.)

The following is an actually useful, safe way to limit String index access to the String’s real length:

let s = "Swift"

if let indexAtEnd = s.index(s.endIndex, offsetBy: -1, limitedBy: s.startIndex),
    let i = s.index(s.startIndex, offsetBy: 5, limitedBy: indexAtEnd) {
    print(s[i])
}
// nothing happens, as expected!

It’s also ugly as heck.

Remember: that’s why you need to be extra cautious and thorough in your unit tests, kids.

Always be testing

  • right before,
  • at, and
  • right after the limits you want to verify.

Don’t just test somewhere before and somewhere after the limit. Imagine your tests are clamps, and your problem is being clamped so tight it cannot escape. No wiggle room, no margin for error.

—Now guess who was bitten by not testing that way in a particular place.

In other words, an update to The Archive is coming out soon.

I also filed a Feedback issue for this, finally.


  1. I know, you probably understood this point about 5 paragraphs earlier already; but no amount of editing this post seems to make the undertone of disappointment go away. I just can’t get rid of it. It … Is. Too. Strooooong. 

MultiMarkdown Filter for nanoc

I recently dropped blog posts rendered via MultiMarkdown. I used MMD to support citations, but this is not a book, this is a website!

So I retired my MultiMarkdown processor for nanoc, the static site generator that I use.

If you need something like it for your project, here it is:

require "systemu"

class MultiMarkdownFilter < Nanoc::Filter
  identifier :mmd
  type :text

  def run(content, args = {})
    output = ''
    stderr = ''
    status = systemu(
      [ 'multimarkdown' ],
      'stdin'  => content,
      'stdout' => output,
      'stderr' => stderr)

    unless status.success?
      $stderr.puts stderr
      raise RuntimeError, "MultiMarkdown filter failed with status #{status}"
    end

    output.force_encoding('UTF-8')
  end
end

It requires a local installation of the MultiMarkdown binaries. This is not part of any Ruby gem that I’m aware of, so you need to install it separately, e.g. via brew install multimarkdown.

Use it in your Rules file:

compile '/posts/**/*' do
  filter :erb
  filter :mmd  # <----
  layout '/default*'
end

→ Blog Archive