Swift strange EXC_BAD_ACCESS crash with objc_setAssociatedObject and objc_registerClassPair

1.1k Views Asked by At

I face a very strange crash on Swift.

Xcode 11.3.1

Swift 5

Case 1

class TestObject {
    var deinitExecution: (() -> Void)?
    deinit {
        // comment this to avoid crash
        deinitExecution?()
    }
}
private var associatedDynamicTagHandle: UInt8 = 0
class InterestTests: XCTestCase {
    func testExample() {
        guard let dynamicClass = objc_allocateClassPair(TestObject.self, "DynamicClass", 0) else {
            XCTFail()
            return
        }
        objc_registerClassPair(dynamicClass)
        objc_setAssociatedObject(dynamicClass, &associatedDynamicTagHandle, true, .OBJC_ASSOCIATION_ASSIGN)
    }
}

enter image description here

If I removed code deinitExecution?() or objc_setAssociatedObject(dynamicClass, &associatedDynamicTagHandle, true, .OBJC_ASSOCIATION_ASSIGN). It works fine.

Case 2

class TestObject {
}
private var associatedDynamicTagHandle: UInt8 = 0
class InterestTests: XCTestCase {
    func testExample() {
        guard let dynamicClass = objc_allocateClassPair(TestObject.self, "DynamicClass", 0) else {
            XCTFail()
            return
        }
        objc_registerClassPair(dynamicClass)
        objc_setAssociatedObject(dynamicClass, &associatedDynamicTagHandle, true, .OBJC_ASSOCIATION_ASSIGN)
        let method = class_getInstanceMethod(dynamicClass, NSSelectorFromString("aName"))
        print("method: \(String(describing: method))")
    }
}

enter image description here

If I removed code objc_setAssociatedObject(dynamicClass, &associatedDynamicTagHandle, true, .OBJC_ASSOCIATION_ASSIGN). It works fine.

Is this a swift bug?

1

There are 1 best solutions below

3
On

I think you mis-use objc_setAssociatedObject. The documentation says:

Sets an associated value for a given object using a given key and association policy.

The key point here is that you need an object, not a class.

The runtime functions objc_allocateClassPair and objc_registerClassPair just create new runtime classes, not objects. To get an object, you could use NSClassFromString, call init() and then associate the tag with it.

A complete running example could be (just a console program, no unit test):

import Foundation

class TestObject {
    var deinitExecution: (() -> Void)?

    required init() {
        print ("TestObject.init()")
    }
    deinit {
        print ("TestObject.deinit()")
        deinitExecution?()
    }
}
private var associatedDynamicTagHandle: UInt8 = 0
class InterestTests {
    func testExample() {
        guard let dynamicClass = objc_allocateClassPair(TestObject.self, "DynamicClass", 0) else {
            print("oops")
            return
        }
        objc_registerClassPair(dynamicClass)
        let toClass = NSClassFromString("DynamicClass") as! TestObject.Type
        let obj = toClass.init()
        obj.deinitExecution = { print ("deinitExecution") }
        objc_setAssociatedObject(obj, &associatedDynamicTagHandle, true, .OBJC_ASSOCIATION_ASSIGN)
    }
    deinit {
        print ("InterestTests.deinit()")
    }
}

InterestTests().testExample()

with the following output:

TestObject.init()
one
TestObject.deinit()
deinitExecution
InterestTests.deinit()
Program ended with exit code: 0