将闭包传递给 Private API's

Passing closures to Private API's

我正在尝试从私有 API MPAVRoutingController 获取所有可用的 airplay 设备。我正在为 swift 使用名为 performSelector-Swift 的第三方执行选择器库。我尝试调用的方法是 fetchAvailableRoutesWithCompletionHandler。这需要一个参数,一个 objective-c 块。 - (void)fetchAvailableRoutesWithCompletionHandler:(id /* block */)arg1; 当我尝试传入一个闭包时,出现编译错误,如果我没有在我的应用程序中传递任何内容,我的应用程序就会崩溃。我不会发布此应用程序,这就是我使用 priv API 的原因。

let MPAVRoutingController = NSClassFromString("MPAVRoutingController")! as! NSObject.Type
let routingController = MPAVRoutingController.init()
if let availableRoutes = routingController.swift_performSelector("fetchAvailableRoutesWithCompletionHandler:", withObject: {
        object in
    }) {
        print(availableRoutes)
    }

首先..我是如何找到正确的完成块签名的:http://i.imgur.com/UGVayPE.png

这表明它在调用它时分配了一个NSMutableArray作为参数给完成块。那是唯一的参数。您不必执行此操作(将其拆解)。抛出异常时,您可以打印签名。有时它还会告诉你需要哪种块。


接下来,我对动态调用选择器的看法..

你最好的选择是不执行选择器..这很痛苦,尤其是当调用包含多个参数时..

你可以做的是通过 interface/extension 指针调用。我一直在 C++ 中这样做(来自 Pimpl 习语的想法。COMM 接口也这样做)并且它与 Swift, Objective-C, Java..等..

创建一个与object 具有相同接口的协议。创建一个继承该协议的扩展。然后将 object 实例转换为 extension/interface/protocol.

通过interface/extension/协议指针调用你想要的任何函数。

import UIKit
import MediaPlayer

@objc
protocol MPAProtocol { //Functions must be optional. That way you don't implement their body when you create the extension.
    optional func availableRoutes() -> NSArray
    optional func discoveryMode() -> Int
    optional func fetchAvailableRoutesWithCompletionHandler(completion: (routes: NSArray) -> Void)
    optional func name() -> NSString
}

extension NSObject : MPAProtocol { //Needed otherwise casting will fail!

     //Do NOT implement the body of the functions from the protocol.
}

用法:

let MPAVRoutingControllerClass: NSObject.Type = NSClassFromString("MPAVRoutingController") as! NSObject.Type
let MPAVRoutingController: MPAProtocol = MPAVRoutingControllerClass.init() as MPAProtocol

MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
    print(routes);
}

如果您要使用桥接 header 而不是创建扩展 + 协议,您只需执行一个 Objective-C 类别:

#import <Foundation/Foundation.h>

@interface NSObject (MPAVRoutingControllerProtocol)
- (void)fetchAvailableRoutesWithCompletionHandler:(void(^)(NSArray *routes))completion;
@end

@implementation NSObject (MPAVRoutingControllerProtocol)

@end

然后:

let MPAVRoutingControllerClass: NSObject.Type = NSClassFromString("MPAVRoutingController") as! NSObject.Type
let MPAVRoutingController = MPAVRoutingControllerClass.init()

MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
    print(routes);
}

最后,如果你可以使用协议注入,你可以更容易地做到这一点:

func classFromString(cls: String, interface: Protocol?) -> NSObject.Type? {
    guard let interface = interface else {
        return NSClassFromString(cls) as? NSObject.Type
    }

    if let cls = NSClassFromString(cls) {
        if class_conformsToProtocol(cls, interface) {
            return cls as? NSObject.Type
        }

        if class_addProtocol(cls, interface) {
            return cls as? NSObject.Type
        }
    }
    return nil
}

func instanceFromString<T>(cls: String, interface: Protocol?) -> T? {
    return classFromString(cls, interface: interface)?.init() as? T
}



@objc
protocol MPAProtocol {
    optional func availableRoutes() -> NSArray
    optional func discoveryMode() -> Int
    optional func fetchAvailableRoutesWithCompletionHandler(completion: (routes: NSArray) -> Void)
    optional func name() -> NSString
}


let MPAVRoutingController: MPAProtocol = instanceFromString("MPAVRoutingController", interface: MPAProtocol.self)!

MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
    print(routes);
}