Blog

bevkjbvkbdkdxoeoziejdoiehz fiugebfuyegwik

Xcode 6.3 beta 2 sadness

So we are all excited about all the new features those Swift betas bring us, but we also lose some things. From the Swift 1.2 release notes:

• Swift now detects discrepancies between overloading and overriding in the Swift type system and the effective behavior seen via the Objective-C runtime. (18391046, 18383574)

What this also takes away is overriding Objective-C methods in extensions, a technique that I use in my CLI test runner xctester:

extension XCTestCase {
    [...]

    func recordFailureWithDescription(description: String!, inFile filePath: String!, atLine lineNumber: UInt, expected: Bool) {
        [...]
    }
}

The -recordFailureWithDescription:inFile:lineNumber:expected: selector is already declared in XCTestCase, but up until now, Swift was kind enough to let us do this anyway.

With 1.2, this gets significantly more verbose. I am utilizing an old ObjC runtime friend imp_implementationWithBlock() to do this, but where to we put that code and how do we create a compatible block?

Global variables are lazy in Swift, so they're not an option, neither is +load in a dummy class. I opted for the unelegant solution of using +initialize and instantiating a dummy object like this:

class LolSwift: NSObject {
    override class func initialize() {
        let recordFailure_IMP = imp_implementationWithBlock(unsafeBitCast(recordFailure_block, AnyObject.self))
        let recordFailure_method = class_getInstanceMethod(XCTestCase.self, "recordFailureWithDescription:inFile:atLine:expected:")
        let recordFailure_old_IMP = method_setImplementation(recordFailure_method, recordFailure_IMP)
    }
}

and then later

let l = LolSwift()

This works, but how do we actually form that recordFailure_block? A simple Swift closure converted will crash at runtime 😭. What we need is an @objc_block, defined like this:

let recordFailure_block : @objc_block (sself: XCTestCase, description: String!, filePath: String!, lineNumber: UInt, expected: Bool) -> Void = { (sself, description, filePath, lineNumber, expected) -> (Void) in
    [...]
}

Let there be much rejoicing, the tests are running again \o/.