Передача замыканий в частные API

Я пытаюсь получить все доступные устройства трансляции из частного API MPAVRoutingController. Я использую стороннюю библиотеку для выбора исполнителей для swift, которая называется executeSelector-Swift. Метод, который я пытаюсь вызвать, это fetchAvailableRoutesWithCompletionHandler. Это принимает один параметр, блок target-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)
    }

1 ответ

Решение

Сначала.. Как я нашел правильную подпись блока завершения: http://i.imgur.com/UGVayPE.png

Это показывает, что он выделяет NSMutableArray в качестве параметра для блока завершения, когда он вызывает его. Это единственный параметр. Вам не нужно этого делать (разбирать). После исключения вы можете распечатать подпись. Иногда он также скажет вам, какой тип блока ожидается.


Далее мое мнение о динамическом вызове селекторов.

Ваш лучший вариант - не выполнять селекторы. Это боль, особенно когда вызов содержит несколько параметров.

Что вы можете сделать, так это вызывать через указатели интерфейса / расширения. Я делаю это в C++ (идея из идиомы Pimpl. Команды COMM делают это тоже) все время, и это работает с Swift, Objective-C, Java... и т. Д.,

Создайте протокол, который имеет тот же интерфейс, что и объект. Создайте расширение, которое наследует этот протокол. Затем приведите экземпляр объекта к этому расширению / интерфейсу / протоколу.

Вызывайте любую функцию через указатель интерфейса / расширения / протокола.

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);
}

Если бы вы делали это с заголовком Bridging вместо создания протокола extension +, вы бы просто сделали одну категорию 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);
}
Другие вопросы по тегам