1

I am attempting to use the Objective-C runtime in Swift to dynamically call Objective-C methods using method_getImplementation. My approach works well for methods that either return void or that return an object, but it breaks when attempting to return a value type such as an NSUInteger.

My implementation will not crash for that value type, instead it will return an UnsafePointer where the memory address is actually the value (e.g. instead of an UnsafePointer with the pointee value of 2, the memory address is 0x0000000000000002).

How do I get the method to return the correct value instead of an invalid memory type?


My test code

// Initialize a NSMutable Array
var nsMutableArrayClass: AnyObject? = objc_lookUpClass("NSMutableArray")
nsMutableArrayClass = call(selector: "alloc", on: nsMutableArrayClass)
nsMutableArrayClass = call(selector: "init", on: nsMutableArrayClass)

// Append 0 through 7 to the array
call(selector: "addObject:", on: nsMutableArrayClass, with: [0])
call(selector: "addObject:", on: nsMutableArrayClass, with: [1])
call(selector: "addObject:", on: nsMutableArrayClass, with: [2])
call(selector: "addObject:", on: nsMutableArrayClass, with: [3])
call(selector: "addObject:", on: nsMutableArrayClass, with: [4])
call(selector: "addObject:", on: nsMutableArrayClass, with: [5])
call(selector: "addObject:", on: nsMutableArrayClass, with: [6])
call(selector: "addObject:", on: nsMutableArrayClass, with: [7])

// the value now of nsMutableArrayClass is an NSMutableArray containing all 8 elements

// FIXME: Breaks on return of NSUInteger

let indexOfTwo = call(selector: "indexOfObject:", on: nsMutableArrayClass, with: [2]) // returns Unsafe Pointer with memory address of 0x0000000000000002

let indexOfFive = call(selector: "indexOfObject:", on: nsMutableArrayClass, with: [5]) // returns Unsafe Pointer with memory address of 0x0000000000000005 

My implementation

typealias ObjcMethodWithoutArguements = @convention(c) (AnyObject?, Selector) -> UnsafeRawPointer?
typealias ObjcMethod1Arg = @convention(c) (AnyObject?, Selector, AnyObject?) -> UnsafeRawPointer?

// Use this to dynamically call a selector exposed to the Objective-C
@discardableResult func call(selector selectorString: String, on target: AnyObject?, with arguments: [Any] = []) -> AnyObject? {
    let selector = sel_getUid(selectorString)
    
    guard let isa = object_getClass(target),
          let i = class_getInstanceMethod(isa, selector) else {
        return nil
    }
    
    let m = method_getImplementation(i)
    var buf = [ Int8 ](repeating: 0, count: 46)
    method_getReturnType(i, &buf, buf.count)
    let returnType = String(cString: &buf)
    
    let result : UnsafeRawPointer?
    switch arguments.count {
        case 0:
            let typedMethod = unsafeBitCast(m, to: ObjcMethodWithoutArguements.self)
            result = typedMethod(target, selector)
        case 1:
            let typedMethod =  unsafeBitCast(m, to: ObjcMethod1Arg.self)
            result = typedMethod(target, selector, arguments[0] as AnyObject)
        default:
            fatalError("Not yet implemented")
    }

    // Handle the return type of objects
    if returnType == "@" {
        guard let result = result else {
            return nil
        }
        
        let p = Unmanaged<AnyObject>.fromOpaque(result)
        return shouldReleaseResult(of: selectorString)
                 ? p.takeRetainedValue()
                 : p.takeUnretainedValue()
    } else if returnType == "Q" {
        // Handle the return type of a value
        return result as AnyObject
    }
    
    return target
}

func shouldReleaseResult(of selector: String) -> Bool {
    return selector.starts(with: "alloc")
        || selector.starts(with: "init")
        || selector.starts(with: "new")
        || selector.starts(with: "copy")
}
S.Moore
  • 1,277
  • 17
  • 17
  • NSUInteger is not a pointer to an object. Consider using `NSNumber` instead otherwise an `NSUInteger == 0` leads to undefined behaviour. – Ol Sen Jun 29 '20 at 16:04
  • If you're `convention(c)` calling an objective-c method with return type `UnsafeRawPointer` what do you expect? You need a proper `-> NSUInteger` return in the `convention(c)` signature (assuming I got right your obj-c method, you haven't mentioned it). See also my answer here for more examples https://stackoverflow.com/a/43714950/5329717 – Kamil.S Jun 29 '20 at 16:31

0 Answers0