Skip to content

Inversion of Execution in Swift

From the archive. This article describes the tools and APIs available when it was written.

Originally published as a GitHub gist on March 6, 2015. This is a historical Swift 1.2 example; the original APIs and syntax are preserved.

Demonstration of an Inversion of Execution (IoE) technique for how the same function can be executed either synchronously with a return value or asynchronously with a callback block depending on an optional trailing iex (inverted executor) closure.

The work being performed doesn’t need to be aware of the execution context (modulo synchronization concerns).

The executor closure

func sum<X>(
    numbers: [Int],
    iex: ((() -> Int) -> X)! = { f in f() as! X }
) -> X {
    // Note: compiler crashes if iex is not marked as an optional or implicitly unwrapped optional

    return iex {
        // The hard stuff: actually perform the calculation
        return reduce(numbers, 0, +)
    }
}

An async-capable reduce

func reductor<S: SequenceType, U, X>(
    sequence: S,
    initial: U,
    combine: (U, S.Generator.Element) -> U,
    iex: ((() -> U) -> X)! = { f in f() as! X }
) -> X {
    return iex { reduce(sequence, initial, combine) }
}

A recursive example

func naïvefib<X>(n: Int, iex: ((() -> Int) -> X)! = { $0() as! X }) -> X {
    return iex {
        // here we take advantage of the trailing closure having a default synchronous executor:
        // we recursively execute ourselves synchronously by omitting the parameter
        return n < 2 ? n : (naïvefib(n - 1) + naïvefib(n - 2))
    }
}

Demonstrating the behavior

import XCTest

class InversionOfExecutionDemo: XCTestCase {

    func testExample() {

        // Running with direct callback provides the work result as a return value
        let result1 = sum([5, 1], iex: { $0() })
        XCTAssertEqual(6, result1)

        // You can also exclude the final parameter and it will automatically execute as a return value
        // But note that the type cannot be inferred, so it needs to be declared with the property
        let result2: Int = sum([5, 9])
        XCTAssertEqual(14, result2)

        // Running with an immediate callback provides the work result synchronously as the callback argument
        let noreturn: Void = sum([3, 6]) { f in
            let result3 = f()
            println("immediate: \(result3)")
            XCTAssertEqual(9, result3)
        }

        // Running with a execution callback will enqueue the work and asynchronously provide it to the callback
        weak var xp = expectationWithDescription("eventual callback")

        sum(Array(1...1000)) { f in
            dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
                let result4 = f()
                println("eventual: \(result4)")
                XCTAssertEqual(500_500, result4)
                xp?.fulfill()
            }
        }

        // Await the the eventual callback result
        waitForExpectationsWithTimeout(1) { err in XCTAssertNil(err) }

        let concat: String = reductor(["a", "b", "c"], "", +)
        XCTAssertEqual("abc", concat)

        // A small fib calculation should be executed synchronously
        let fib5: Int = naïvefib(5)

        weak var xp2 = expectationWithDescription("eventual callback")

        // a large fib calculation should be run in the background
        naïvefib(30) { f in
            dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
                let fib30 = f()
                println("fib30: \(fib30)")
                XCTAssertEqual(832_040, fib30)
                xp2?.fulfill()
            }
        }

        waitForExpectationsWithTimeout(10) { err in XCTAssertNil(err) }
    }
}


Previous Post
Apple DMA Compliance Workshop
Next Post
Debugging slow Swift compile times