Mark Bourke Mark Bourke - 5 months ago 46
iOS Question

Passing closures to Private API's

I'm trying to fetch all the available airplay devices from the private API MPAVRoutingController. I'm using a third party perform selector library for swift called performSelector-Swift. The method I am trying to call is fetchAvailableRoutesWithCompletionHandler. This takes one parameter, an objective-c block.

- (void)fetchAvailableRoutesWithCompletionHandler:(id /* block */)arg1;
When I try and pass in a closure I get a compile error and if I don't pass anything in my app crashes. I'm not releasing this app and thats why I'm using the 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)
}

Answer

Your best option is to just not perform selectors.. It's a pain especially when the call contains MULTIPLE parameters..

What you can do is invocation through interface pointers.. I do this in C++ (Idea from the Pimpl idiom) all the time and it works with Swift.

Create a protocol that has the same interface as the object. Create an extension that inherits that protocol. Then cast the object instance to that extension/interface/protocol.

Call whatever function you want via the interface/extension/protocol pointer.

import UIKit
import MediaPlayer

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

extension NSObject : MPAProtocol {

}

Usage:

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

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

If you were to do it with a Bridging header, you'd just do:

#import <Foundation/Foundation.h>

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

@implementation NSObject (MPAVRoutingControllerProtocol)

@end

Then:

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

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