[Swift]Lecture aléatoire NSData dans un fichier
J'ai voulu faire la chose suivante : sauver un tableau de NSDatas dans un fichier, et ensuite relire une DATA spécifique sans recharger toutes les données en mémoire. Bref, faire ce qu'on appelait accès aléatoire à une époque.
J'ai adopté ce format de fichier :
- Un entête - la chaà®ne alphanumérique "Datas01.00".
- Un UInt32 contenant le nombre de NSData dans le fichier
- Une série d'UInt32 contenant la taille et l'offset des NSDatas dans le fichier
- Les NSDatas
J'ai une classe CreateStore pour créer un fichier de Datas.
let idFormat = "Datas01.00"
class CreateStore {
func create(array:[NSData], name:String) -> Bool {
// Tableau de NSRange
var arrayRange = [NSRange]()
var offset = 0
for data in array {
// Stockage taille et position
let size = data.length
var range = NSRange()
range.location = offset
range.length = size
arrayRange.append(range)
offset += size
}
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
let fileURL = documentsURL.URLByAppendingPathComponent(name)
// Création fichier vide
let dataEmpty = " ".dataUsingEncoding(NSUTF8StringEncoding)
dataEmpty?.writeToURL(fileURL, atomically: false)
// Création fichier
let file = try? NSFileHandle(forWritingToURL: fileURL)
if let file = file {
// Enregistrement version du format
let dataFormat = idFormat.dataUsingEncoding(NSUTF8StringEncoding)
file.writeData(dataFormat!)
// Enregistrement nombre de NSData
let numbersOfData = UInt32(array.count)
file.writeUInt32(numbersOfData)
// Enregistrement taille et offset des NSDatas
for range in arrayRange {
let location = UInt32(range.location)
let length = UInt32(range.length)
file.writeUInt32(location)
file.writeUInt32(length)
}
// Enregistrement des NSData
for data in array {
file.writeData(data)
}
file.closeFile()
return true
}
return false
}
}
Exemple d'utilisation :
let array = ["Sushis", "Maki", "Wasabi Power", "Saumons", "avocats"]
let arrayDatas = encodeStrings(array)
let fileName = "sushis.bin"
let createFile = CreateStore()
createFile.create(arrayDatas, name: fileName)
encodeStriings est une moulinette pour transformer un tableau de String en tableau de NSDatas, pour le test.
func encodeStrings(array :[String]) -> [NSData] {
var arrayData = [NSData]()
for str in array {
let data = str.dataUsingEncoding(NSUTF8StringEncoding)
arrayData.append(data!)
}
return arrayData
}
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Le fichier se lit avec la classe DatasStore. Exemple :
Le source de la classe :
ça marche, mais je me pose des questions sur le fonctionnement du système.
Pour commencer, je maintiens ouvert en permanence un NSFileHandle ne disparaissant qu'à la destruction de l'objet DatasStore (en utilisant le deinit). Je me demande s'il ne serait pas plus approprié d'ouvrir et de fermer le fichier à chaque lecture d'un objet. Mais cela risque peut-être d'augmenter le temps de lecture des données. Quoi que iOS doit certainement garder un cache sur les fichiers utilisés récemment.
C'est un premier jet. Vous avez certainement des remarques à faire sur le code et ma manière de procéder.
Quid des fichiers ouverts quand iOS plonge une application en background, avant de la réactiver ? Je présume qu'il faut fermer les fichiers pour éviter les problèmes (ce que mon code ne gère pas du tout pour le moment).
Il faut aussi que j'ajoute un NSCache pour éviter de recharger inutilement des données.
EDIT : Comme je l'ai dis dans un autre post, l'idée est d'utiliser un ViewCollection pour afficher une série de fiches, sans charger la totalité des informations en mémoire, juste ce qui est nécessaire pour une fiche bien précise.
Oups, j'ai oublié d'ajouter le source des extensions facilitant la convention UInit32 <=> NSData et la lecture/écriture de UInt32 dans un fichier binaire.
Ouais. ça a l'air de tourner comme code mais j'ai une question: pourquoi ?
- Si tu me réponds que c'est pour permettre une lecture aleÌatoire dans des gros fichiers je te dirai d'utiliser du SQLite qui fera du bien meilleur boulot.
- Si tu me réponds que Swift a été porteÌ sur ZxSpectrum je te dirai que c'est cool !
ApreÌ€s pour ce qui est du produit en dehors de ces consideÌrations je trouve dommage que dans ta quête du stockage le plus restreint possible tu te retrouve à stocker n fois les headers de NSData.
En plus ça c'est vraiment pour les données statiques. Ajout/modification s'apparentera à frapper aux portes de l'enfer.
D'autant que, vu ce que tu veux faire, CoreData avec son lazy-loading fera un bien meilleur travail et sera plus facile à maintenir/mettre à jour. Ou Realm aussi, je m'y suis un peu moins intéressé. Je pense qu'il est bon en 2016 de s'orienter vers ce genre de technologies plutôt que de vouloir conserver la compatibiliteÌ DOS.... ( )
Probablement, mais je ne connait rien à SQLite. Et je n'ai pas envie de passer des semaines ou des mois à apprendre une nouvelle technologie, pour une seule chose : accéder de manière aléatoire à un gros fichier de NSDATA.
Quelque que soit le système de stockage il faut bien conserver les headers des DATA, pour les lire de manière aléatoire. Et chaque header n'est stocké qu'une seule fois dans le fichier, pas n fois. Il y a aussi des headers dans un NSDictonary, même si tu n'y a pas accés.
C'est prévu pour des données purement statique, généré sur Mac, puis stocké sur un serveur, afin de pouvoir être téléchargé. Ou peut-être bien stocké dans un inapp-purchase.
Si j'ai bien compris CoreData permet de gérer une persistance des données au sein d'une application, pas de gérer plusieurs bases de données (des fichiers différents), pouvant être récupérés au fur et à mesure du temps.
Oui, si je voulais devenir développeur iOS professionnel, acquérir pleins de connaissances sur des technologies et vivre de mon travail en créant des tas d'applications, ce qui n'est pas mon but.
Oui j'entends bien tout ça. Mais y'a quand meÌ‚me un truc qui me chiffonne un peu, là c'est un peu comme si tu nous disait: "J'ai 20 meÌ€tres de bois à abattre mais je vais le faire à la hache uniquement, j'suis pas bucheron professionnel moi.".
Si on part du principe que tu fais ça par passion rien ne t'empêche d'apprendre au passage. Mais quand je vois l'énergie que tu emploi dans tes recherche tu peux employer cette énergie pour apprendre.
La seule diffeÌrence au final est que tu devras sortir de ta zone de confort. Mais je ne t'apprends rien en te disant que tu trouvera toujours de l'aide ici et plus facilement pour des technologies nouvelles que pour les fichiers binaires ou indexeÌs avec un backend en Cobol.
Et au passage si tu veux utiliser Realm ou CoreData pour un cas aussi simple que le tien c'est pas des semaines qu'il va te falloir mais deux/trois aprem/soireÌes. T'as les connaissances suffisantes pour tout comprendre et c'est dommage que tu passe à coÌ‚teÌ de ça. D'autant que le SDK iOS n'est pas adapteÌ aux choses que tu veux faire.
Voilà mon avis, je ne t'embêterai pas plus, promis.
Il est possible de travailler sur plusieurs bases Core Data simultanément ou de merger des données d'une base dans une autre, mais ce n'est pas trivial.
Combien de bois doit abattre un bucheron professionnel pour acquérir son titre pendant sa formation ?
Que ce soit Swift ou Foundation, les deux sont tout à fait adaptés. Cf. la présence de NSFileHandle ou de UnsafePointer<>, etc.
Les données étant statiques, je trouve que ce n'est pas un mauvais choix de ne pas utiliser Core Data.
Dans les critères de choix, les adhérences à des librairies qui en font plus que nécessaire doivent être prises en compte.
Tu peux peut être tirer partie des options de gestion de mémoire virtuelle lors de la création du NSData représentant ton fichier (DataReadingMappedIfSafe, DataReadingMappedAlways). Si tu conserves la structure de fichier que tu as défini cela ne va pas enlever la gestion des offsets quit doit être réaliser, mais cela peut éviter d'avoir à utiliser les NSFileHandle.
Tu veux dire, lire l'intégralité du fichier dans un NSData en mode mémoire virtuelle avec ces deux attributs, puis utiliser subdataWithRange pour récupérer les données ?
Je ne vois pas trop la différence avec NSFileHandle. Dans les deux cas, le système maintient un fichier ouvert en permanence.
Pour le moment, je vais me contenter d'ouvrir et de fermer le fichier à chaque lecture d'un NSData, en conservant son URL. Je verrais bien si c'est suffisant à l'usage.
A force de vouloir faire toi-même ce stockage dans un fichier de façon la plus compact possible à la main octet par octet (dans l'autre thread) et maintenant à le relire de manière aléatoire... et à devoir tout re-coder toi-même du coup et surtout à ensuite te casser les dents sur des problèmes (File Handle toujours ouvert, pas de Mapping, ...) qui ont été résolus depuis belle lurette par les systèmes existants...
Tu te prends bien le chou tout ça pour éviter d'apprendre un truc existant ou pour éviter d'utiliser un truc déjà existant et éprouvé comme Realm (où il n'y a aucun besoin de s'y connaà®tre en BDD, SQL ou autre pour l'utiliser) ou autre.
Tu vas perdre énormément plus de temps à réinventer la roue toi-même + rencontrer des bugs que tu n'avais pas anticipés et des problématiques auxquelles tu n'avais pas pensé de prime abord (NSFileHandle open, ...), alors que tu passerais bien moins de temps, et en plus ce temps serait bien plus bénéfique, à apprendre un framework qui fait déjà le taf (je te parle de Realm car il est 100x plus simple à prendre en main que CoreData ou SQLLite, mais ce n'est pas forcément la seule solution possible), et en plus tu en profiterais pour apprendre de nouvelles choses...
* se planque derrière un tas de sable *
@Ali : realm.io c'est forcément que sur le device ? (ou il est possible d'utiliser ca un peu comme Parse)
Je pense qu'ils doivent être en train de faire un portage sous linux pour une utilisation avec swift.
Après je ne sais pas si realm est adapté pour une utilisation sur serveur avec potentiellement beaucoup de connexions en parallèle.
Donc effectivement ça n'a pas à l'origine été créé pour faire du clouding à la Parse. Si tu as besoin d'une base serveur pars plutôt sur des concurrents à Parse (genre Firebase & co).
Par contre si tu as besoin d'une base locale, genre pour gérer du cache et mode hors ligne, ou pour sérialiser des données et les enregistrer sur le disque de ton device pour les réouvrir plus tard, etc... alors Realm est exactement fait pour.