2016-07-01 13 views
0

Ich versuche, alle verfügbaren Airplay-Geräte von der privaten API MPAVRoutingController zu holen. Ich benutze eine Selektorbibliothek von Drittanbietern für swift namens performSelector-Swift. Die Methode, die ich aufrufen möchte, ist fetchAvailableRoutesWithCompletionHandler. Dies erfordert einen Parameter, einen Ziel-C-Block. - (void)fetchAvailableRoutesWithCompletionHandler:(id /* block */)arg1; Wenn ich versuche, eine Schließung zu übergeben bekomme ich einen Kompilierfehler und wenn ich nichts übergebe, stürzt meine App ab. Ich werde diese App nicht veröffentlichen und deshalb verwende ich die priv API.Übergeben von Sperren an private APIs

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

Kennen Sie die Signatur der geforderten Schließung, d. H. Ist "Objekt" definitiv richtig? Weil die private Funktion sicher die Schließung für etwas verwendet und wenn es versucht, verschiedene Parameter zu übergeben, haben Sie einen Fehler. Akzeptiert Swift_performSelector sogar eine Schließung für den Objektparameter? Was sagt das Dokument dieser dritten Partei darüber? – Gero

+0

Entschuldigung, ich dachte, ich habe die Methodensignatur eingefügt, aber ich habe etwas anderes eingefügt. Die Signatur ist '- (void) fetchAvailableRoutesWithCompletionHandler: (id/* block * /) arg1;' entsprechend der umgekehrten ingenieurmäßigen Headerdatei (https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks /MediaPlayer.framework/MPAVRoutingController.h). Was die Bibliothek angeht, kann ich sie einfach verwerfen und diesen Teil der App in Obj-C schreiben und ihn später überbrücken –

+0

Dang, das klärt es nicht, denn der wichtige Teil ist auskommentiert.Unter der Annahme, dass sie den '(^) (parameterTypes) '-Teil wirklich weggelassen haben, sieht es so aus, als ob der Block mindestens' id 'zurückgeben sollte (d. H.' AnyObject' in swift). Ich würde diesen Header so verstehen: "Übergeben Sie einen Block, der' id' zurückgibt. Also versuchen Sie vielleicht '/ * ... */withObject: {() -> AnyObject in/* definieren Sie ein Dummy-Objekt, geben Sie es zurück/*})'. Gewöhnlich ruft man Reverse Engineering-Methoden auf, ohne genau zu wissen, welche Parameter zu setzen sind, zumindest aus meiner Erfahrung. – Gero

Antwort

2

Erste .. Wie ich den richtigen Abschluss Blocksignatur gefunden: http://i.imgur.com/UGVayPE.png

Das zeigt, dass es eine NSMutableArray als Parameter zur Fertigstellung Block zuweist, wenn er sie aufruft. Das ist der einzige Parameter. Sie müssen das nicht tun (zerlegen Sie es). Wenn eine Ausnahme ausgelöst wird, können Sie die Signatur drucken. Manchmal wird es Ihnen auch sagen, welche Art von Block erwartet wird.


Als nächstes meine Meinung über Selektoren dynamisch aufrufen ..

Ihre beste Möglichkeit ist, um nicht Selektoren durchführen .. Es ist ein besonders Schmerz, wenn der Anruf mehrere Parameter enthält ..

Was können Sie do ist Aufruf durch Interface/Erweiterung Zeiger .. Ich mache dies in C++ (Idee von der Pimpl idiom .. COMM-Schnittstellen tun dies auch) die ganze Zeit und es funktioniert mit Swift, Objective-C, Java .. etc ..

Erstellen Sie ein Protokoll mit der gleichen Schnittstelle e als das Objekt. Erstellen Sie eine Erweiterung, die dieses Protokoll erbt. Übergeben Sie dann die Objektinstanz an diese Erweiterung/Schnittstelle/Protokoll.

Rufen Sie die gewünschte Funktion über die Schnittstelle/Erweiterung/Protokollzeiger auf.

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. 
} 

Verbrauch:

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

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

Wenn Sie es mit einem Brückenkopf statt Erstellen der Erweiterung + Protokoll zu tun, würde man nur eine einzige Objective-C Kategorie tun:

#import <Foundation/Foundation.h> 

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

@implementation NSObject (MPAVRoutingControllerProtocol) 

@end 
Dann

:

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

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

Schließlich, wenn Sie Protokoll-Injektion verwenden können, können Sie dies viel einfacher tun:

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

Ja dachte gerade das. Vielen Dank! –