2015-08-31 29 views
5

Sto trovando Swift e NSData come un immondo matrimony di frustrazione. Trovo che mi sembra che tutte le presunte nuove scoperte della sicurezza di Swift escano dalla finestra ogni volta che mi occupo di questa cosa. La quantità di arresti anomali (con tracce inutili) non aiuta.Estrarre i dati da NSData con Swift

Così, ho imparato che posso evitare l'UnsafeMutablePointer roba spaventosa facendo le cose come la seguente:

var bytes = [UInt8](count: 15, repeatedValue: 0) 
anNSData.getBytes(&bytes, length=15) 

Ho anche scoperto che posso estrarre direttamente in valori singolari:

var u32:UInt32 = 0 
anNSData.getBytes(&u32, length=4) 

Questo porta a due domande intermedie:

1) c'è qualcosa che posso usare che è più affidabile di costanti hardcoded lì. Se questo fosse C, userei semplicemente sizeof. Ma penso di aver letto che forse dovrei usare strideof invece sizeof? E non funzionerebbe su [UInt8], vero?

2) I documenti (per Swift) indicano che questo parametro deve essere _ buffer: UnsafeMutablePointer<Void>. Quindi come funziona? Sto solo diventando fortunato? Perché dovrei farlo invece del più nativo/gestito [Uint8] costrutto ?? Mi chiedevo se lo UnsafeMutablePointer fosse un protocollo, ma è una struttura.

Incoraggiato con la lettura dei valori direttamente (piuttosto che come una matrice), ho pensato che forse avrei potuto provare un altro tipo di struttura. Ho una struttura di 6 byte che assomiglia:

struct TreeDescription : Hashable { 
    var id:UInt32 = 0x00000000 
    var channel:UInt8 = 0x00 
    var rssi:UInt8 = 0x00 

    var hashValue:Int { 
     return Int(self.id) 
    } 
} 

Quale funziona realmente (dopo averci pensato non ha fatto, ma alla fine facendo un pulito che ha reso alcuni crash andare via)!

var tree = TreeDescription() 
anNSData.getBytes(&newTree, length: 6) 

Ma questo mi porta a preoccuparmi dei dettagli dell'imballaggio della struttura? Perché funziona? Cosa dovrei preoccuparmi di fare questo?

Questo mi sembra molto C-ish per me. Pensavo che Swift avesse portato la C fuori da ObjectiveC.

risposta

2

Si consiglia di verificare RawData che è davvero nuovo e questo ragazzo ha appena sperimentato un po 'con questa idea, quindi non pensare che sia stato testato bene o nulla, alcune funzioni non sono ancora implementate. È fondamentalmente un wrapper Swift-y in giro (hai indovinato) dati grezzi, una serie di byte.

Utilizzando questa estensione, è possibile inizializzare con un NSData esempio:

extension RawData { 
    convenience init(data: NSData) { 
     self.init(UnsafeMutableBufferPointer(start: UnsafeMutablePointer(data.bytes), count: data.length)) 
    } 
} 

You'de essere chiamata in questo modo:

let data = "Hello, data!".dataUsingEncoding(NSASCIIStringEncoding)! 

let rawData = RawData(data: data) 

EDIT: Per rispondere alle vostre domande:

Il fatto è che i dati possono essere grandi, molto grandi. Generalmente non vuoi copiare cose grandi, dato che lo spazio è prezioso. La differenza tra un array di valori [UInt8] e un'istanza NSData è che l'array viene copiato ogni volta, lo si assegna a una funzione -> nuova copia, si esegue un compito -> nuova copia. Questo non è molto desiderabile con i dati di grandi dimensioni.

1) Se si desidera che il modo sicuro e più nativa, senza le librerie 3a parte come quello citato, si può fare questo:

let data = UnsafeMutableBufferPointer(start: UnsafeMutablePointer(data.bytes), count: data.length) 

(lo so che non suona molto sicuro, ma credimi sia). Puoi usarlo quasi come un normale array:

let data = "Hello, data!".dataUsingEncoding(NSASCIIStringEncoding)! 

let bytes = UnsafeMutableBufferPointer(start: UnsafeMutablePointer<UInt8>(data.bytes), count: data.length) 

for byte in bytes {} 
bytes.indexOf(0) 
bytes.maxElement() 

e non copia i dati mentre li passi.

2) UnsafeMutablePointer<Void> è in effetti molto simile a C, in questo contesto rappresenta il valore iniziale (chiamato anche base) in una sequenza di puntatori. Il tipo Void deriva anche da C, significa che il puntatore non sa che tipo di valore sta memorizzando. Puoi lanciare tutti i tipi di puntatori al tipo che ti aspetti in questo modo: UnsafeMutablePointer<Int>(yourVoidPointer) (Questo non dovrebbe bloccarsi). Come accennato in precedenza, è possibile utilizzare UnsafeMutableBufferPointer per utilizzarlo come raccolta del proprio tipo. UnsafeMutableBufferPointer è solo un wrapper attorno al puntatore di base e alla lunghezza (questo spiega l'inizializzatore che ho usato).

Il tuo metodo di decodifica dei dati direttamente nella tua struct funziona davvero, le proprietà di una struct sono nel giusto ordine, anche dopo la compilazione, e la dimensione di una struct è esattamente la somma delle sue proprietà memorizzate. Per dati semplici come il tuo va benissimo. C'è un'alternativa: per utilizzare il protocollo NSCoding. Vantaggio: più sicuro Svantaggio: devi sottoclasse NSObject. Penso che dovresti rimanere fedele al modo in cui lo fai ora. Una cosa che cambierei comunque è mettere la decodifica della tua struct all'interno della struct stessa e usare sizeof. Hanno così:

struct TreeDescription { 
    var id:UInt32 = 0x00000000 
    var channel:UInt8 = 0x00 
    var rssi:UInt8 = 0x00 

    init(data: NSData) { 
     data.getBytes(&self, length: sizeof(TreeDescription)) 
    } 
} 

Un altro EDIT: È sempre possibile ottenere i dati sottostanti da un Unsafe(Mutable)Pointer<T> con il metodo memory cui tipo di ritorno è T. Se è necessario, puoi sempre spostare i puntatori (per ottenere il valore successivo ad esempio) semplicemente aggiungendo/sottraendo Int s ad esso.

MODIFICA rispondendo al tuo commento: Si utilizza & per passare una variabile inout, che può quindi essere modificata all'interno della funzione. Poiché una variabile inout equivale sostanzialmente a passare il puntatore, gli sviluppatori di Swift hanno deciso di rendere possibile il passaggio a &value per un argomento che prevede uno UnsafeMutablePointer. Dimostrazione:

func inoutArray(inout array: [Int]) {} 

func pointerArray(array: UnsafeMutablePointer<Int>) {} 

var array = [1, 2, 3] 

inoutArray(&array) 
pointerArray(&array) 

Questo funziona anche per structs (e forse alcune altre cose)

+0

io possa avere a dare un'occhiata a questo. Ma in realtà non mi aiuta a capire perché anche altre strutture (disposte in serie o meno) sembrano funzionare bene. –

+0

@TravisGriggs Aggiornato la mia risposta, dai un'occhiata – Kametrixom

+0

Bello. Sono quasi arrivato, penso. Ancora non è chiaro il motivo per cui un [UInt8] può essere inserito direttamente dove va un UnsafeMutablePointer? È la natura dell'operatore inout (&)? Ho notato che posso usare l'opposto della tecnica di cui sopra per costruire un 'NSData'. Per esempio. 'NSData (& myStruct, sizeof (myStruct))' e funziona correttamente. La mia osservazione ingenua è che la variabile & beforeVariable è simile a come è in C. –