2017-07-24 1 views
3

Dans une aire de jeux, le code suivant initialise données à l'aide d'un UnsafeBufferPointer, comme décrit dans le Apple Foundation DocumentationDonnées de la valeur Changement dans Swift Playground

let data = Data() 
let test = Array(0..<10) 
let pointer = UnsafeRawPointer(test).assumingMemoryBound(to: UInt8.self) 
data = Data.init(buffer: UnsafeBufferPointer(start: pointer, count: MemoryLayout.size(ofValue: test))) 
data[8] 

L'exécution de ce programme produit plusieurs fois des valeurs différentes pour les données [8]. Pourquoi la valeur change-t-elle?

+0

Est-ce le code exact? 'data' est une constante donc vous ne pouvez pas la ré-assigner ici:' data = Data.init (...) ' –

+0

Oui, c'est le code exact. Avant Swift 3, la raison en était que le let ne faisait référence qu'à la référence aux données, pas aux données elles-mêmes. Cependant, après Swift 3, les cours de Foundation ont été reliés aux structures de Swift. Donc je ne sais pas pourquoi ça marche, mais ça l'est toujours. –

+0

@Hamish Merci pour les informations sur MemoryLayout. Je ne sais vraiment pas quel est le comportement attendu. Idéalement, le code correspondrait à un nombre dans le tableau (je pense).J'essaie d'en apprendre plus sur le Foundation Framework, et à cette fin, j'ai créé des Playgrounds qui utilisent chaque partie de l'API pour chaque classe Foundation. Je l'ai fait à Data, je passais par les initialiseurs, je suis arrivé à celui-ci, et je suis maintenant à perte. Si vous savez comment/pourquoi quelqu'un utiliserait cet initialiseur, ou simplement connaître un bon guide pour UnsafePointer, UnsafeBuffer, etc. Je serais très reconnaissant. –

Répondre

1

MemoryLayout.size(ofValue: test) est équivalent à MemoryLayout<[Int]>.size (le paramètre n'est utilisé que pour indiquer le type générique d'espace réservé). Il ne vous donne pas la taille du tampon du tableau, il vous donne la taille du Array structure lui-même, qui est actuellement 1 mot (8 octets sur une machine 64 bits), car les éléments sont détenus indirectement.

Par conséquent, l'instance Data que vous construisez ne contient que 8 octets, donc l'accès à data[8] lira les données inutiles; C'est pourquoi vous obtenez des résultats inattendus. Cet accès hors limites sera en fait cause a runtime error in Swift 4 (à partir de la version fournie avec Xcode 9 beta 4).

Mais en ignorant tout cela, en utilisant UnsafeRawPointer(test) pour commencer est un comportement non défini, comme il utilise un pointeur vers un tampon qui est seulement valable pour la durée de l'appel initialiseur. Swift garantit uniquement que les arguments de pointeur générés automatiquement (par exemple lors du passage d'un tableau à un paramètre de pointeur constant) seront valides pour la durée de l'appel de fonction donné (voir le blog de l'équipe Swift sur Interacting with C Pointers).

Si vous voulez juste pour vider les octets de la mémoire tampon du tableau dans une instance Data, vous voulez simplement:

let test = Array(0 ..< 10) 
let data = test.withUnsafeBufferPointer(Data.init) 
// or let data = test.withUnsafeBufferPointer { Data(buffer: $0) } 

print(data as NSData) // bridge to NSData to get a full print-out of bytes 

// <00000000 00000000 
// 01000000 00000000 
// 02000000 00000000 
// 03000000 00000000 
// 04000000 00000000 
// 05000000 00000000 
// 06000000 00000000 
// 07000000 00000000 
// 08000000 00000000 
// 09000000 00000000> 

print(data[8]) // 1 

(64-bit petite machine endian)

qui utilise withUnsafeBufferPointer(_:) à obtenir une vue de pointeur de tampon immuable sur le tampon du tableau (et s'il n'est pas natif, par exemple NSArray, il devra être créé), et Datainit(buffer:) pour construire une nouvelle instance avec les octets du point de tampon donné er.

Si vous voulez que les octets correspondent 1: 1 aux éléments de la matrice, vous devez faire en sorte que chacun des éléments soit long de 1 octet.

Par exemple, en commençant par un [UInt8]:

let test = [UInt8](0 ..< 10) 
let data = test.withUnsafeBufferPointer(Data.init) 
print(data as NSData) // <00010203 04050607 0809> 
print(data[8]) // 8 

Et parce que vous travaillez maintenant avec une séquence de UInt8, vous pouvez réellement simplifier l'initialisation légèrement à l'aide Data « s sequence of UInt8 initialiser:

let data = Data(test) 
+0

Sérieusement, merci. Trouver des informations bien expliquées, à jour, sur les données (et beaucoup d'autres classes de Foundation, en fait) est presque impossible, donc c'était extrêmement utile. –

+0

@BrandonBradley heureux d'aider :) – Hamish