2015-09-17 23 views
18

Quando si utilizza Swift, i framework Cocoa vengono dichiarati per restituire i tipi nativi di Swift, anche se i framework restituiscono effettivamente oggetti Objective-C. Allo stesso modo, i metodi usano i tipi Swift come parametri, dove ciò ha senso.C'è un modo per lavorare con gli oggetti Foundation (NSString, NSArray, NSDictionary) in Swift senza eseguire il bridging?

Supponiamo di voler chiamare un metodo Cocoa che (in Objective-C) mi restituisce un NSArray e poi lo passa a un metodo Cocoa che prende uno NSArray. Con il codice come questo:

let a: [AnyObject] = [] // Imagine calling a method that returns a huge NSArray. 
let mutable = NSMutableArray() 
mutable.addObjectsFromArray(a) 

Sembra l'enorme NSArray sta per arrivare a ponte a un array di Swift quando viene assegnato a a e poi colmato di nuovo ad un NSArray quando passato come parametro. Almeno è così che sembra dal profiling e guardando allo smontaggio.

C'è un modo per evitare queste conversioni potenzialmente lente quando non ho bisogno di lavorare effettivamente con la matrice in Swift? Quando lo ricevo da Cocoa e poi lo rimando a Cocoa?

In un primo momento, ho pensato che sarebbe utile per aggiungere informazioni sul tipo per a:

let a: NSArray = [] // Imagine calling a method that returns a huge NSArray. 
let mutable = NSMutableArray() 
mutable.addObjectsFromArray(a as [AnyObject]) 

Ma poi devo convertire il parametro a un array Swift tardive e il compilatore si lamenta.

Inoltre, lo smontaggio per il codice del tipo:

let c: NSArray = mutable.subarrayWithRange(NSMakeRange(0, 50)) 

mostra le chiamate verso __TF10Foundation22_convertNSArrayToArrayurFGSqCSo7NSArray_GSaq__ e __TFer10FoundationSa19_bridgeToObjectiveCurfGSaq__FT_CSo7NSArray, apparentemente la conversione del valore di ritorno a Swift e poi di nuovo a Objective-C. (Questo accade anche con le versioni di Release.) Speravo che digitando c come NSArray non ci fosse il necessario ponte.

Sono preoccupato che ciò possa portare a inefficiencies con strutture di dati molto grandi, con molte conversioni disparate di quelle regolari e con raccolte pigre/proxy perché non sono necessariamente grandi ma possono essere costose da calcolare. Sarebbe bello poter usare receive tale array dal codice Objective-C e passarlo indietro senza dover realizzare tutti gli elementi dell'array se non sono mai accessibili da Swift.

Questo è un modello molto differentperformance rispetto a Core Foundation/Foundation in cui il bridging era gratuito. Ci sono così tanti casi in cui il codice passa gli oggetti avanti e indietro supponendo che sarà O (1), e se questi sono modificati in modo invisibile in O (n) gli algoritmi esterni potrebbero diventare quadratici o peggiori. Non mi è chiaro cosa si dovrebbe fare in questo caso. Se non c'è modo di disattivare il bridging, sembra che tutto ciò che tocca quegli oggetti debba essere riscritto in Objective-C.

Ecco alcuni codice di temporizzazione campione in base all'esempio precedente:

NSArray *getArray() { 
    static NSMutableArray *result; 
    if (!result) { 
     NSMutableArray *array = [NSMutableArray array]; 
     for (NSUInteger i = 0; i < 1000000; i++) { 
      [array addObjectsFromArray:@[@1, @2, @3, @"foo", @"bar", @"baz"]]; 
     } 
     result = array; 
    } 
    return result; 
} 

@interface ObjCTests : XCTestCase 
@end 
@implementation ObjCTests 
- (void)testObjC { // 0.27 seconds 
    [self measureBlock:^{ 
     NSArray *a = getArray(); 
     NSMutableArray *m = [NSMutableArray array]; 
     [m addObjectsFromArray:a]; 
    }]; 
} 
@end 

class SwiftTests: XCTestCase { 
    func testSwift() { // 0.33 seconds 
     self.measureBlock() { 
      let a: NSArray = getArray() as NSArray 
      let m = NSMutableArray() 
      m.addObjectsFromArray(a as [AnyObject]) 
     } 
    } 

    func testSwiftPure() { // 0.83 seconds 
     self.measureBlock() { 
      let a = getArray() 
      var m = [AnyObject]() 
      m.appendContentsOf(a) 
     } 
    } 
} 

In questo esempio, testSwift() è circa il 22% più lento rispetto testObjC(). Solo per divertimento, ho provato a fare l'array append con l'array nativo di Swift, e questo è stato molto più lento.

Un problema correlato è che quando il codice Objective-C passa codice Swift un NSMutableString, la Swift String finisce con un copia della stringa mutevole.Questo è buono nel senso che non sarà inaspettatamente mutato dietro la schiena di Swift. Ma se tutto quello che devi fare è passare una stringa a Swift e guardarla brevemente, questa copia potrebbe aggiungere un sovraccarico inaspettato.

+1

Avete fatto i test di sincronizzazione? Se no, stai solo indovinando. Non indovinare, misura. Anche perché stai creando un 'NSMutableArray' e assegnandolo a una variabile' let' che è immutabile "' let mutable = NSMutableArray() '. – zaph

+1

@zaph Sì, in un test di temporizzazione che ho fatto, il 25% del tempo sembrava essere spesi avanti e indietro in questo caso significa che non assegnerò un altro oggetto a quella variabile.L'oggetto stesso può ancora essere mutevole, proprio come se fosse, per esempio, un 'NSMutableURLRequest' che –

+0

Puoi aggiungere del codice per mostrare la conversione avanti e indietro? FYI ... puoi inizializzare un 'NSMutableArray' da un' NSArray': 'let mutable = NSMutableArray (array: a)' –

risposta

1

hai provato a fare un'estensione?

extension NSMutableArray 
{ 
    func addObjectsFromNSArray(array:NSArray) 
    { 
     for item in array 
     { 
       self.addObject(item); 
     } 
    } 
} 

Ora che ho avuto il tempo di giocare in realtà con questo invece di parlare in teoria, ho intenzione di rivedere la mia risposta

creare un'estensione, ma, invece, lo fanno in un file di Objective C

@interface NSMutableArray(Extension) 
    - (void)addObjectsFromNSArray:(NSObject*) array; 
@end 

@implementation NSMutableArray(Extension) 
    - (void)addObjectsFromNSArray:(NSObject*) array 
{ 
    [self addObjectsFromArray:(NSArray*)array]; 
} 
@end 

Ho trovato che il codice funzionava molto più velocemente in questo modo. (Quasi 2x dai miei test)

testSwift 4,06 secondi

testSwiftPure 7,97 secondi

testSwiftExtension 2,30 secondi

+0

Sì, penso che l'approccio possa funzionare, ma perde il controllo dei tipi, potrebbe potenzialmente richiedere il wrapping di molte API e potrebbe richiedere un sacco di ristrutturazione del codice per evita di chiamare direttamente nel tuo codice Swift. –

+0

sì, sfortunatamente non ho visto nessun altro modo per disabilitare l'autobridging, quindi probabilmente userei questo modo se avessi bisogno di un aumento di velocità in una certa circostanza. Dovrò controllare se ho ancora il parco giochi a casa, aggiungerò un controllo di tipo di NSArray e vedrò quanto più lento è. – Knight0fDragon

Problemi correlati