2

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?

Yanni
  • 580
  • 2
  • 6
  • 21

1 Answers1

1

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

Andreas Oetjen
  • 9,889
  • 1
  • 24
  • 34
  • Yep. I think you are right. `objc_setAssociatedObject ` only support object. Not pure class (objective-c class actually is an object but pure swift class not). I am curious why `objc_setAssociatedObject` 's first parameter is `Any` not `AnyObject`? – Yanni May 27 '20 at 03:19
  • They changed the mapping of `id` from `AnyObject` to `Any` during the migration from Swift 2 to Swift 3. I think the main reason is that _typical_ objects like `NSArray` are not objects in Swift but structs, so auto-bridging those types requires the use of `Any` in all those cases. – Andreas Oetjen May 27 '20 at 04:55
  • *Anything* can be converted to an object since Swift 3 (compare e.g. https://stackoverflow.com/a/39546887/1187415). In the case of a Swift `Bool` that is a `NSNumber`. So this alone does not explain the crash. – Martin R May 27 '20 at 05:00