book covers

Indie macOS Dev Bundle, Swift 3 Ed..

Design solid applications and own your distribution process. Learn software architecture principles to make your Swift apps easier to maintain and extend. Set up your app for sale, with a trial mode and license generation all set. Including fully functional sample projects and code templates for your app.

Handle Pending File Changes with ReSwift as Your Backbone

Automatic saving of changes in some user interface component to a file should be handled differently when you employ ReSwift.

sketch of the information flow
Your UI tells the app it finished editing. Then a reducer enqueues a pending file change.

In short, you have to extract the state information from the “save file” action and store it in the overall app’s state somehow. I append these to a collection of PendingFileChanges, a ReSwift.StateType that is part of my overall app state.

(This is a follow-up to my previous musings on the topic.)

“Save file” has the state info of “which file” and “what contents”. In order to enqueue multiple changes to the same file, add a UUID to each file change to identify the event:

struct FileContentChange {
    let url: URL
    let content: String
    let uuid: UUID
}

Instances of this type are enqueued with the PendingFileChanges substate. (I used real queues first and relied on the order, but switched to simple sets so I can find and remove elements by UUID easily. You need to make it Hashable to work with sets, though.)

struct AppState: StateType {
    var pendingFileChanges: PendingFileChangesState = .empty
}

struct PendingFileChangesState: StateType {

    static var empty: PendingFileChangesState {
        return PendingFileChangesState(fileContentChanges: [])
    }
    
    public fileprivate(set) var fileContentChanges: Set<FileContentChange>

    public var nextFileContentChange: FileContentChange? {
        return fileContentChanges.first
    }

    public mutating func insert(fileContentChange: FileContentChange) {
        fileContentChanges.insert(fileContentChange)
    }

    public mutating func remove(fileContentChange: FileContentChange) {
        fileContentChanges.remove(fileContentChange)
    }
}

Some long-running service object will listen to changes to this substate and perform the necessary writes periodically.

To simplify the setup, make the resulting ChangeFileContents service object a ReSwift.StoreSubscriber; now it’ll receive updates to the app state from your store. Cache the last received (and thus pending) change in the service so you can make the operation asynchronous and allow more incoming state updates without issuing to overwrite the file time and again with the same stuff.

When it finishes a write operation, it in turn dispatches an action so the fitting item is removed from the queue.

Here’s a sample service wireframe:

typealias DefaultStore = Store<AppState>

class WriteFileChanges: StoreSubscriber {
    let store: DefaultStore

    public init(store: DefaultStore) {
        self.store = store
    }

    fileprivate(set) var lastChange: FileContentChange?

    public func newState(state: PendingFileChangesState) {
    
        // If the state update shows the queue is empty, 
        // reset the cache.
        guard let change = state.nextFileContentChange else {
            lastChange = nil
            return
        }
        
        // Skip identical, unprocessed updates.
        guard change.uuid != lastChange.uuid else { return }
        
        // Set the cached value to prevent duplicate changes.
        lastChange = change

        delegatePerformingTheFileWriteOperation() { success in
            if !success { 
                // TODO: handle error :)
            }
            
            store.dispatch(CompletingFileContentChange(change))
        }
    }
    
    fileprivate func delegatePerformingTheFileWriteOperation(
        completion: (Bool) -> Void) {
        
        // Use FileManager or String.write(toURL:) etc.
        
        completion(true)
    }
}

So you end up with WriteFileChanges as a service that processes a part of the PendingFileChangesState queue. To let the app know when it has finished, i.e. let a reducer remove the entry from the pending changes queue, it dispatches a CompletingFileContentChange action. I leave this simple wrapper as an exercise to the reader.

Recording of Wednesday’s Webinar

Sadly, the recording doesn’t contain the webcam stream. I find videos like this pretty hard to follow. Especially if the speaker isn’t native English, then it can be quite a pain sometimes. I hope it’s not too bad in this case!

I promised not to distribute any follow-up material until next week so that webinar attendees have early access.

But you can get a 20% discount on “Make Money Outside the Mac App Store” by using the coupon code MORECONTROL, good until March 31st!

Make Money Outside the Mac App Store – Swift 3 Update

book cover

Ladies and gentlemen, I have finished updating my e-book about ditching the app store and selling your apps with the help of FastSpring for Swift 3!

I also got annoyed by “$x.99” prices and lowered the price from $24.99 to $22. The Indie Mac Developer Book Bundle is now just $27, too.

The code was updated a while ago already. I just never finished editing the manuscript because I wanted to add so many things. Now I’ve only done minor edits, but that’s better than having an outdated book, right?

(The print version is still being proofed and will take another day or two until it’s ready.)

If you bought the book from my store already, you can download an updated version using the same download link. Lost the link? E-mail me and I’ll re-send you the details.

Register for My Webinar About Ditching the Mac App Store

I will be hosting a webinar about distributing your app outside the Mac App Store next week. It’s free, and you’re very welcome to attend!

Wednesday, February 15th, 2017 @ 10:00 AM PST (will run 1 hour)

→ Register Now

Topics include:

  • Ditching the Mac App Store – Why, and what it means.
  • Is There Life After the Mac App Store? – Choosing an e-commerce provider.
  • Getting Back to Development – How you change your code to work outside the Mac App Store.

Plus you’ll see me live. That alone should be motivation for you to visit, no matter the topic :)

Disclaimer, aka Code of Honesty and Transparency

Maybe you just arrived on my blog and don’t know how I do things around here, yet, and how much I value morality over profit anytime. So let me erase your doubts about this webinar cooperation.

I use FastSpring to sell my stuff. I really like their service, so I wrote a guide in 2015. FastSpring in turn was impressed by my initiative and helped me spread the word a bit and provide background info whenever I needed anything. I am not getting paid by FastSpring for the book. It’s my own creative work. I maintain it because I think it helps you, fellow developer, to set up your own indie business.

Then late 2016, FastSpring approached me as their go-to expert for Mac app development. They plan to show how easy it is to use FastSpring to distribute Mac apps. In other words, they want to own part of the good news for obvious marketing reasons.

With the recent Out-of-App-Store Success Stories by Rogue Amoeba and Kapeli, it may even be a growing market.

I was skeptical at first. I will not violate my strong ethics; teaching people the One True Way™ is more important than easy money. But I came to find FastSpring values delivering useful content over running a 60-minute ad show. From the get go, FastSpring wanted me to create the content. Not even once did they suggest I add something to my slides. They totally risk I go live on Wednesday and tell people to use a competing service. But I won’t, because I know no better service provider. I liked the concept, so I agreed. I am getting paid by FastSpring for this webinar gig. But it’s 100% my webinar.

To stay true to myself, I will give genuinely helpful advice to empower the attendees to become independent. Of course I’ll show FastSpring’s features, just like the screenshots I put in my book. All because I believe in their service, not because they bought my loyalty with the speaker fee.

In short, this is not an advertisement for FastSpring. I haven’t sold my soul. It’s a cooperation out of mutual respect.

Hope to be seeing you around on Wednesday!

Express Either–Or-Relationships as Enums

If you want to encapsulate the notion of “either A or B” (also called “lifting” in functional parlance), an enum type in Swift is the best fit:

enum EitherOr {
    case A, B
}

You can use associated values to wrap types with enums, too:

enum Fruit {
    case banana(Banana)
    case apple(Apple)
}

These things seem to be expressible through a common ancestor type or protocol. But bananas and apples can be modeled in totally different manners (apart from sharing nutritional value, for example).

A real-life example is the result of a network request. Instead of a classic Cocoa callback, express the strict either-or through a type:

/// Only one is ever supposed to be present, but you never
/// know at the call site.
typealias ResultCallback<T> = (result: T?, error: Error?) -> Void

/// Only one can ever be used, never both.
enum Result<T> {
    case success(T)
    case error(Error)
}
typealias ResultCallback2<T> = (Result<T>) -> Void

How to Unit Test Dispatching ReSwift Actions from RxSwift Observables

Say you are like me and work with ReSwift for unidirectional data flow goodness and employ RxSwift for your reactive cravings. You want to dispatch a ReSwift.Action when an RxSwift.Observable signal produces a new value. How do you write tests for that wiring?

In code, it could look like this:

someObservable
    .map { SomeAction($0) }
    .subscribe(onNext: store.dispatch($0))
    .addDisposableTo(disposeBag)

Testing that code requires a bit more understanding of both libraries than their respective documentation reveals.

Here’s an example:

import ReSwift

struct AppState {
    var count = 0
}

typealias DefaultStore = Store<AppState>

struct ChangingCount: ReSwift.Action {
    let value: Int
    init(value: Int) {
        self.value = value
    }
}

Imagine a reducer that takes incoming ChangingCount.value and replaces AppState.value with that. Simple.

Now you want to dispatch a ChangingCount action when some signal produces a new value. You need to create an observer for the signal and store it somewhere. So a friendly helper class comes to mind:

import RxSwift

class Dispatcher {
    let store: DefaultStore
    let disposeBag = DisposeBag()
    
    init(store: DefaultStore) {
        self.store = store
    }
    
    func wireSignalToAction(signal: Observable<Int>) {
        signal.map(ChangingCount.init(value:))
            .subscribe(onNext: { [weak self] in
                self?.store.dispatch($0) })
            .addDisposableTo(disposeBag)
    }
}

And that’s it. Now how can you make sure it does what it should do? What do unit tests look like?

Taking a look at RxSwift’s TestObserver and TestScheduler, you can record incoming events yourself and perform checks. To record events, you hijack a ReSwift.Store’s dispatchFunction with a closure that captures the incoming action. Using a TestScheduler, you can time production of events in the signal and keep track of the time the action comes in:

import RxSwift
import RxTest

class DispatchingTests: XCTestCase {
    /// Replacement for your real reducer because you won't need it.
    struct NullReducer: ReSwift.Reducer {
        func handleAction(action: Action, state: AppState?) -> AppState {
            return AppState(value: -1)
        }
    }
    
    func testDispatchesValueChanges() {
        
        // Set up a signal. Subscription per default starts at time=200
        let scheduler = TestScheduler(initialClock: 0)
        let signal = scheduler.createHotObservable([
            next(300, 98),
            next(600, 12)
            ])
        
        // Set up the store and record matching actions.
        let storeDouble = Store(reducer: NullReducer(), state: nil)
        var actions = [Recorded<Event<ChangingCount>>]()
        storeDouble.dispatchFunction = {
            guard let action = $0 as? ChangingCount else { return () }
            // `return f()` where `f() -> Void` is just like `return ()`.
            // We need to return something to satisy the `-> Any`.
            return actions.append(Recorded(time: scheduler.clock, value: .next(action)))
        }

        // Configure the object under test and start emitting
        // values in virtual time.
        let dispatcher = Dispatcher(store: storeDouble)
        dispatcher.wireSignalToAction(signal)
        scheduler.start()

        XCTAssertEqual(actions, [
            next(300, ChangingCount(value: 98)),
            next(600, ChangingCount(value: 12))
            ])
    }
}

Core Data is Invasive. You Can Hide It, Or You Can Embrace It

Found this nice post about using Swift protocols to expose read-only properties of Core Data managed objects so you don’t couple your whole app to Core Data.

Using Swift protocols in Core Data NSManagedObjects is a great way to limit the visibility of properties and methods. In my 1st book on Mac app development I talked about this, too, and this is a lot easier to handle than a custom layer of structs that you have to map to NSManagedObject and back again. Core Data is designed to be invasive and convenient. It’s not designed to be used as a simple object-relational mapper.

If you have more than just data, like a fairly complex Domain Model with rich behavior and nested sub-components, then using protocols to expose read-only data won’t cut it, though. This approach is super useful to expose properties for reading when their types are built-in or part of Foundation – in other words, when Core Data knows what to do with them. But if you create your own Tree type with a [Banana] property, this again won’t help.

When you plan your app, think hard about the real use of your database/persistence layer. Core Data can do a lot of the tedious work and you can easily sync stuff over iCloud. But if you really need a custom database, don’t try to force Core Data into the equation.

The age-old “pro tip” of deferring decisions about persistence frameworks is sound, but you may have a harder time adding Core Data late in the project than adding it early. As I said, Core Data is invasive, but you can use this to your advantage if you know that the amount of coupling to Core Data is just what you need.

If all you can think about is clean code and awesome software architecture, this will feel like betraying your own principles. It’s an utterly pragmatic choice, based on the principle of “does this help us ship?”

“Bugs are the edges of what works”

There’s a lot to learn in terms of company culture from Basecamp (former 37signals). Shawn Blanc has written about a workshop by Basecame. One thing I just had to share immediately is this: “Bugs are the edges of what works”:

Software simply has bugs. And so if it’s not a critical bug — (such as: if it’s not losing customer data) — then they probably won’t ever fix it. Because to fix an edge bug and chase them all down means they’re not making new things or improving features that really matter.

That’s no excuse for a crappy user experience. But not every edge case has to be solved.

Better Swift FileManager File Existence Checks

I found the Foundation way to check for file existence very roundabout. It returns 1 boolean to indicate existence and you can have another boolean indicate if the item is a directory – passed by reference, like you used to in Objective-C land.

That’s a silent cry for an enum. So I created a simple wrapper that tells me what to expect at a given URL:

public enum FileExistence: Equatable {
    case none
    case file
    case directory
}

public func ==(lhs: FileExistence, rhs: FileExistence) -> Bool {

    switch (lhs, rhs) {
    case (.none, .none),
         (.file, .file),
         (.directory, .directory):
        return true

    default: return false
    }
}

extension FileManager {
    public func existence(atUrl url: URL) -> FileExistence {

        var isDirectory: ObjCBool = false
        let exists = self.fileExists(atPath: url.path, isDirectory: &isDirectory)

        switch (exists, isDirectory.boolValue) {
        case (false, _): return .none
        case (true, false): return .file
        case (true, true): return .directory
        }
    }
}

And since you’re probably interested in well-tested components, here’re the unit tests:

class FileManager_ExistenceTests: XCTestCase {

    func testExistence_NonExistingFile() {

        let url = generatedTempFileURL()
        XCTAssertEqual(FileManager.default.existence(atUrl: url), FileExistence.none)
    }

    func testExistence_ExistingFile() {

        let url = generatedTempFileURL()

        guard let _ = try? "some content".write(to: url, atomically: false, encoding: .utf8)
            else { XCTFail("writing failed"); return }
        XCTAssertEqual(FileManager.default.existence(atUrl: url), FileExistence.file)
    }

    func testExistence_ExistingDirectory() {

        let url = createTempDirectory()
        XCTAssertEqual(FileManager.default.existence(atUrl: url), FileExistence.directory)
    }
}

… and here are the helper functions to create temporary files:

func createTempDirectory() -> URL {

    let fileName = "filemanagertest-temp-dir.\(NSUUID().uuidString)"
    let fileUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)

    try! FileManager.default.createDirectory(at: fileUrl, withIntermediateDirectories: false, attributes: nil)

    return fileUrl
}

func generatedTempFileURL() -> URL {

    let fileName = "filemanagertest-temp.\(NSUUID().uuidString)"
    let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)

    return fileURL
}

If you happen to have a better name than existence(atUrl:) come to mind, I’d be happy to change it!


→ Blog Archive