How to Fix fileReferenceURL() to Work with NSURL in Swift 3 and Swift 4

I am upgrading the code base of the Word Counter to Swift 3. Yeah, you read that right. I didn't touch the Swift code base for almost 2 years now. Horrible, I know – and I'm punished for deferring this so long in every module I try to convert and build. One very interesting problem was runtime crashes in a submodule I build where URLs were nil all of a sudden. This code from 2015 (!!) used to work:

Continue reading …

Remove Trailing Whitespace in TextMate 2 Code Files

I still use TextMate for some things: editing documents quickly, scripting in Ruby, navigating project folders of foreign code bases (especially when they're not using my main language so I could use Xcode, e.g. Java projects), and finding and replacing text. But it always bugged me that when I move around code and indent and outdent and whatnot, that sometime lines with nothing but whitespaces would be saved. Or I'd combine stuff and have 10 trailing spaced all of a sudden. I do show invisible characters, but I don't want to pay attention to that kind of stuff when I'm coding.

Continue reading …

ReSwift Custom Diffs and Enqueued State Updates

Vinh Nguyen found that his ReSwift status updates became slow.

  1. There were too many subscribers.
  2. Objects would react to state updates by dispatching a new action immediately. (ReSwift action dispatching happens synchronously.)

His app state ends up containing a lot of objects in a 3-level hierarchy that mimicks the hierarchy of view components on screen. In a drawing or otherwise canvas based graphics app, it seems. It doesn't make sense to have each objects on the canvas responds to state updates when one other object updates on screen. Instead, you'll want to at least minimize the amount of updates that get passed through.

Vinh implemented a custom diff or "delta update" for the 2nd level in his 3-level hierarchy of objects because they were few enough to be performant during state updates, and could easily manage their child objects.

Read about his discovery of state update bottlenecks on his blog.

He solved the second problem, newState callbacks triggering the dispatch of another action, by enqueuing the dispatch in an asynchronous block on the main queue, which is the queue ReSwift uses:

class ObjectView {
    func newState(state: ObjectState) {
        // ...
        if conditionThatTriggersAnAction == true {
            DispatchQueue.main.async {
                store.dispatch(Action())
            }
        }
    }
}

Sure, this enqueues the action dispatch until the current execution is finished. But you have to take care about other actions being dispatched in between now, and if that is a problem. (E.g. another subscriber type reacting to the same state update with another action.)

I had prefered another solution initially: subscribe to updates in the top level Canvas object, then delegate down the view hierarchy as needed. Every sub-component that wants to fire an action tell the Canvas about this, which enqueues the actions, and then processes the queue after all sub-component updates are finished. A bit like in game development where the game loop ensures there is just 1 point of action handling per run. But then again, Vinh's approach does exactly that: it enqueues action dispatching until later, ensuring the current run loop run isn't interrupted. Also, my approach to delegation would make everything just so much more complicated in the app code.

I wonder is it'd be beneficial if the ReSwift store operated on a high priority queue that is not the main queue all the time. Then you can dispatch actions synchronously from view components on the main queue, waiting for the result, or asynchronously.

I will have to think more about the consequences of an approach like this before I suggest anything to anybody, though. I don't do a lot of concurrent programming in my apps, and when I do, I contain it very strictly; on the downside, I don't have developed any instinct regarding implications of using multiple queues.

Synchronize Scrolling of Two (or More) NSScrollViews

You can make two NSScrollViews scroll in concert quite easily because every scrolled pixel is broadcasted to interested parties. In TableFlip, the main table is a NSTableView contained in a NSScrollView. You can view and hide row numbers in TableFlip; but I didn't want to reload the whole table and mess with the table model to insert and remove the first column. Instead, I use a second table view with a single column. The upside of this approach: I can animate hiding the whole scroll view with the row numbers inside easily without affecting the main table.

Continue reading …

NSTextField usesSingleLineMode Stops Working When You Implement NSTextViewDelegate Methods

Today I learned why my NSTextField permits pasting of newline characters even though I set usesSingleLineMode properly. It's because I made it conform to NSTextViewDelegate to cache changes. When you edit text inside of an NSTextField, you actually type inside a field editor of the window. That's a shared NSTextView instance. Most of the hard work of an NSTextField is done by its cell, which is an NSTextCell. NSTextCells implement at least the delegate method NSTextViewDelegate.textView(_:shouldChangeTextIn:replacementText:) – and when you set usesSingleLineMode, this is actually set for the cell, not the view itself. You can use textView(_:shouldChangeTextIn:replacementText:) to sanitize input text, and I suspect that's where the usesSingleLineMode implementation happens. If your NSTextField subclass implements this method, the NSTextCell implementation isn't called. And since that one isn't public (it was called "implicit protocol conformance" back in the day), you cannot delegate up in Swift because the compiler knows it isn't there.

Continue reading …

Format Strings with Templates in Swift

Gordon Fontenot published his StringTemplate library the other day. You can use it as a much better number formatter, for example:

  • "5125550001" is your user input
  • "(XXX) XXX-XXXX" is your template
  • "(512) 555-0001" is the result of applying the template

There are options to ignore overflow or crop the remainder when only inserting part of the required digits. I think that's pretty slick!