Search code examples
iosobjective-cswiftiphone-privateapi

Unrecognised selector invoking "_setApplicationIsOpaque:"


I am trying to access the iOS private function _setApplicationIsOpaque: (just for personal use and test).

I have implemented this code:

@_silgen_name("_setApplicationIsOpaque:") func _setApplicationIsOpaque(_ arg1: Bool)

let invokeSetApplicationIsOpaque: (Bool) -> Void = {
    // The Objective-C selector for the method.
    let selector: Selector = Selector(("_setApplicationIsOpaque:"))
    guard case let method = class_getInstanceMethod(UIApplication.self, selector), method != nil else {
        fatalError("Failed to look up \(selector)")
    }

    // Recreation of the method's implementation function.
    typealias Prototype = @convention(c) (AnyClass, Selector, Bool) -> Void
    let opaqueIMP = method_getImplementation(method)
    let function = unsafeBitCast(opaqueIMP, to: Prototype.self)

    // Capture the implemenation data in a closure that can be invoked at any time.
    return{ arg1 in function(UIApplication.self, selector, arg1)}
}()

extension UIApplication {
    func setApplicationIsOpaque(_ isOpaque: Bool) {
        return invokeSetApplicationIsOpaque(isOpaque)
    }
}

I found this way to access private iOS API in the following StackOverflow question Access Private UIKit Function Without Using Bridging Header and in this file on GitHub.

The problem is that running the app I get the error

[UIApplication _setBackgroundStyle:]: unrecognized selector sent to class 0x10437f348

I have found the header of iOS private APIs of UIApplication in this GitHub repository.


Solution

  • - (void)_setApplicationIsOpaque:(BOOL)arg1;
    

    is an instance method (of UIApplication), as indicated by the initial hyphen -. Instance methods are sent to an instance of the class, not to the class itself.

    Objective-C methods are C functions with two hidden arguments, compare Messaging in the "Objective-C Runtime Programming Guide":

    It also passes the procedure two hidden arguments:

    • The receiving object
    • The selector for the method

    For instance methods, the "receiving object" is the instance to which the message is sent, in your case the UIApplication instance. (For class methods it would be the class object).

    Therefore the Swift extension method setApplicationIsOpaque must pass self to the closure, and that must be passed as the first argument to the implementation method:

    let invokeSetApplicationIsOpaque: (UIApplication, Bool) -> Void = {
        // The Objective-C selector for the method.
        let selector = Selector(("_setApplicationIsOpaque:"))
        guard case let method = class_getInstanceMethod(UIApplication.self, selector), method != nil else {
            fatalError("Failed to look up \(selector)")
        }
        
        // Recreation of the method's implementation function.
        typealias Prototype = @convention(c) (UIApplication, Selector, Bool) -> Void
        let opaqueIMP = method_getImplementation(method)
        let function = unsafeBitCast(opaqueIMP, to: Prototype.self)
        
        // Capture the implemenation data in a closure that can be invoked at any time.
        return { (appl, arg1) in function(appl, selector, arg1)}
    }()
    
    extension UIApplication {
        func setApplicationIsOpaque(_ isOpaque: Bool) {
            return invokeSetApplicationIsOpaque(self, isOpaque)
        }
    }
    

    Note that the _silgen_name declaration is not needed here.

    @_silgen_name("_setApplicationIsOpaque:") func _setApplicationIsOpaque(_ arg1: Bool)
    

    binds the Swift function to a global symbol "_setApplicationIsOpaque", which does not exist. If you would add a call to that function

    _setApplicationIsOpaque(true)
    

    then building the app would fail with a linker error:

    Undefined symbols for architecture x86_64:  "__setApplicationIsOpaque:"