[iOS] iOs Data Storage guidelines reject (2.23 – App Store Review Guidelines)

Brutta parola, “Apple reject“. La strada per farsi approvare un’app da Apple per il rilascio su Apple Store è assai tortuosa. Occorre seguire per filo e per segno ciascun punto delle App Store Review Guidelines. C’è un punto (il 2.23) che riguarda le iOS Data Storage Guidelines che onestamente non riesco a “disambiguare”, visto che su vari blog si danno suggerimenti e pareri assai discordi su come rispettarle. Il mio grosso quesito è capire dove è corretto salvare i contenuti che vengono scaricati tramite la mia app, considerando che questi vengono prima acquistati (con il sistema In-App Purchases di Apple) e poi sincronizzati manualmente dagli utenti.

Nell’ultima app che ho sottoposto al processo di approvazione di Apple, mi è stato risposto con un messaggio di rigetto riguardo il punto 2.23 e che vi incollo di seguito:

2.23
We also found that your app does not follow the iOS Data Storage Guidelines, which is required per the App Store Review Guidelines.

In particular, we found that after downloading one content, your app stores 13.8 MB. To check how much data your app is storing:

– Install and launch your app
– Go to Settings > iCloud > Storage & Backup > Manage Storage
– If necessary, tap “Show all apps”
– Check your app’s storage

The iOS Data Storage Guidelines indicate that only content that the user creates using your app, e.g., documents, new files, edits, etc., may be stored in the /Documents directory – and backed up by iCloud.

Temporary files used by your app should only be stored in the /tmp directory; please remember to delete the files stored in this location when the user exits the app.

Data that can be recreated but must persist for proper functioning of your app – or because customers expect it to be available for offline use – should be marked with the “do not back up” attribute. For NSURL objects, add the NSURLIsExcludedFromBackupKeyattribute to prevent the corresponding file from being backed up. For CFURLRef objects, use the corresponding kCFURLIsExcludedFromBackupKeyattribute.

For more information, please see Technical Q&A 1719: How do I prevent files from being backed up to iCloud and iTunes?

In poche parole, Apple dice che quando l’app permette agli utenti di scaricare contenuti (downloadable contents), che siano essi immagini, video, audio, pdf, ecc., questo potrebbe far diminuire lo spazio sul dispositivo (e fin qui nessun problema); ma la cosa indesiderata è che il nuovo sistema iCloud potrebbe backuppare l’app con tutti i file scaricati e consumare lo spazio dell’account (che come si sa, è gratuito fino ai primi 5 giga, e a pagamento annuale in base alle soglie di spazio richiesto). A questo punto è iniziata la mia indagine e ho studiato un pò il caso per capire meglio come risolvere questo grosso dilemma e magari aiutarvi ad affrontarlo alla meglio.

Nel rispetto dei requisiti di storage dei dati sugli iDevice, Apple ha stilato le iOS Data Storage Guidelines in cui si legge questo (NOTA. La traduzione è ad interpretazione mia e mi scuso per eventuali errori):

iOS Data Storage Guidelines. iCloud include un sistema di backup che automaticamente effettua il salvataggio dei dati presenti sui device iOs degli utenti in modalità WI-FI. Tutto quello che si trova nella Home Directory della propria app viene backuppato, ad eccezione dell’application bundle, delle directory di cache e dei file temporanei.
Le musiche acquistate (su iTunes), le apps, le Camera Roll (le foto inserite nelle categorie, dette “rullini”), le impostazioni del telefono, la home screen e l’organizzazione delle app, i messaggi e le suonerie vengono automaticamente backuppate.

Poichè i backup vengono eseguiti in modalità wireless e memorizzati in iCloud per ciascun utente, occorre minimizzare la quantità di dati che le apps memorizzano. File grandi allungano i tempi di backup e consumano spazio di storage sull’account iCloud.

Affinchè i backup siano il più possibile efficienti, occorre assicurare che l’app memorizzi i dati in accordo con le seguenti linee guida:

  1. Solo i documenti o altri dati che vengono generati dall’utente (user-generated), o che non possono essere altrimenti ricreati dall’app, devono essere memorizzati nella directory /Documents e verranno automaticamente backuppati da iCloud.
  2. I dati che possono essere scaricati più volte o rigenerati dovrebbero essere memorizzati nella directory /Library/Caches. Esempi di file che potrebbero essere salvati nella directory Caches includono i file di cache del database e contenuto scaricabile (downloadable content), come magazine, newspaper e map applications.
  3. I dati che vengono usati soltanto temporaneamente dovrebbero essere memorizzati nella directory /tmp. Sebbene questi file non vengono backuppati da iCloud, occorre ricordare di cancellarli quando non servono più così da non consumare spazio sul dispositivo dell’utente.
  4. Usare l’attributo “do not back up per specificare i file che dovrebbero rimanere sul device, anche in situazioni di basso storage. Questo attributo si usa con i dati che possono essere ricreati ma che occorre siano persistenti, anche in situazioni di basso storage, per far funzionare propriamente l’app o perchè gli utenti si aspettano di averli disponibili anche durante l’uso offline. L’attributo funziona sui file marcati a prescindere dalla directory in cui si trovano, inclusa la directory Documents. Questi file non verranno eliminati o non verranno inclusi nell’iCloud dell’utente o di backup di iTunes. Poichè questi file utilizzano spazio di archiviazione sul device, la vostra app è responsabile del controllo e dell’eliminazione periodica di questi file.

Per maggiori dettagli vedi: Technical Q&A QA1719-How do I prevent files from being backed up to iCloud and iTunes?

Prima di entrare nel meglio della trattazione tecnica e del codice di programmazione, riporto quanto estratto dal documento di App Backup Best Practices.

App Backup Best Practices. I backup vengono effettuati via iCloud o quando l’utente sincronizza il dispositivo con iTunes. Durante l’operazione di backup, i file vengono trasferiti dal device al computer dell’utente o nell’account iCloud. Le location (percorsi o paths) dei file nella sandbox dell’app determinano se questi file devono essere (o meno) backuppati e restorati. Se la tua applicazione crea file troppo grandi che vengono cambiati regolarmente e questi vengono salvati in una location dove è previsto il backup, l’operazione potrebbe risultare lenta. Occorre scrivere codice per gestire i file tenendo ben in mente questa situazione.

Non occorre preparare l’app per le operazioni di backup o di restore. I dispositivi con un account iCloud attivo mantengono salvati i dati delle apps al momento appropriato. E per i dispositivi connessi al computer, iTunes effettua un backup incrementale dei dati delle apps. Comunque, iCloud e iTunesnon effettuano il backup dei contenuti che si trovano nelle seguenti directory:

  • /AppName.app
  • /Library/Caches
  • /tmp

Per prevenire che il processo di sincronizzazione sia assai lento, occorre scegliere bene dove posizionare i file all’interno della “home directory” della propria app. Le apps che memorizzano file troppo grossi possono rallentare il processo di backup di iTunes o iCloud e possono consumare troppo spazio di storage, incoraggiando l’utente a cancellare l’app o a disabilitare il backup dei suoi dati su iCloud. Tenendo in mente quanto detto, occorre memorizzare i dati dell’app in accordo con le seguenti linee guida:

  • i dati critici (data critical) dovrebbero essere memorizzati nella directory /Documents. I dati critici consistono di qualsiasi dato che non possono essere ricreati attraverso l’app, come i documenti utente o altri contenuti generati dall’utente (user-generated content)
  • I file di supporto (support files) includono i file che l’app scarica o genera e che l’app stessa può ricreare se necessario. La location per la memorizzazione dei file di supporto dell’app dipende dalla versione corrente dell’iOS:
      • in iOS 5.1 e successivi, la memorizzazione dei file di supporto va effettuata nella directory /Library/Application Support e va aggiunto l’attributo “NSURLIsExcludedFromBackupKey” in corrispondenza dell’oggetto NSURL usando il metodo setResourceValue:forKey:error: (se si sta usando la Core Foundation, aggiungere la key kCFURLIsExcludedFromBackupKey all’oggetto CFURLRef usando la funzione CFURLSetResourcePropertyForKey). Applicando questo attributo si fa in modo che i file marchiati non vengano backuppati da iTunes o iCloud. Se si ha un numero molto grandi di questi file di supporto, è possibile memorizzarli in una sottodirectory custom e applicare l’attributo esteso a questa directory.
      • in iOS 5.0 e precedenti, la memorizzazione dei file di supporto va effettuata nella directory /Library/Caches dove non verranno mai backuppati. Se si vuole distribuire per iOS 5.0.1, vedere il link How do I prevent files from being backed up to iCloud and iTunes? per informazioni su come escludere i file dal backup.
  • I dati “cached” dovrebbero essere memorizzati nella directory /Library/Caches. Esempi di files che dovrebbero essere memorizzati in questa directory sono (ma non si è obbligati a metterli lì) file di cache del database e contenuto scaricabile (downloadable content), come magazine, newspaper e map apps. L’app dovrebbe essere in grado di gestire correttamente le situazioni in cui i dati memorizzati nella cache vengano eliminati dal sistema (per vari motivi, tra cui mancanza di risorse) per liberare spazio su disco.
  • I dati temporanei dovrebbero essere memorizzati nella directory /tmp. I dati temporanei comprendono tutti quelli che non occorre rendere persistente per un periodo esteso di tempo. Occorre ricordare di rimuovere questi file quando non servono più per non consumare ulteriore spazio sul device dell’utente.

Sebbene iTunes effettui il backup dell’app bundle (la cartella .app con il codice sorgente ed eseguibile dell’app sul device), questa operazione non lo fa ad ogni operazione di sincronizzazione. Le apps acquistate (apps purchased) direttamente da device sono backuppate quando il device viene sincronizzato con iTunes. Le apps che non vengono backuppate dopo una prima sincronizzazione sono quelle per le quali l’app bundle non è cambiato. Viene fatto, in poche parole, un backup incrementale (solo quando ci sono state modifiche degli app bundle).

Files Saved During App Updates. Quando un utente scarica un aggiornamento di un’app, iTunes installa l’update in una nuova app directory. Poi muove i file dei dati dalla vecchia directory di installazione dell’app sulla nuova directory, cancellando ovviamente la prima. I file che si trovano nelle seguenti directory vengono preservati durante il processo di update:

  • /Documents
  • /Library

Anche se i file presenti in altre directory dell’utente potrebbero essere spostate, non si dovrebbe troppo far affidamento alla loro presenza dopo un aggiornamento (quelle per cui Apple fornisce una garanzia sono le precedenti).

Ho riportato la traduzione e le miei considerazioni anche della Q&A precedentemente linkata: Come prevenire che i file vengano backuppati da iCloud e iTunes?

Domanda: La mia app ha un certo numero di file che occorre avere memorizzati sul dispositivo permanentemente per poter funzionare in modalità offline. Comunque, questi dati non contengono dati utente e non è necessario backupparli. Come posso prevenire il loro backup?

Risposta: su iOs, le apps devono assicurare che solo i dati utente e non quelli dell’applicazione vengano backuppati su iCloud e iTunes. Gli step esatti da seguire variano in base alla versione di iOS, quindi questo Q&A descriverà il processo per ciascuna versione di iOS.

IMPORTANTE. Le app dovrebbero evitare di mischiare i dati applicativi (app data) e quelli utente (user data) nello stesso file. In questo modo non si fa che aumentare le dimensioni del backup e potrebbe violare le iOS Data Storage Guidelines.

In base a quanto scritto nel paragrafo “App Backup Best Practices” di questo articolo, esistono vari modi per salvare i dati di supporto a seconda della versione corrente dell’iOS (ricordiamo che iCloud è stato reso disponibile soltanto a partire dagli iOS 5.0 e successivi):

  • iOS 5.1 e successivi: a partire da iOS 5.1, le apps possono usare le “file properties” NSURLIsExcludedFromBackupKey oppure kCFURLIsExcludedFromBackupKey per contrassegnare i file da escludere dai backup. Queste API sono da preferire a quelle precedenti (della versione 5.0.1) poichè queste ultime sono deprecate in quanto settano direttamente un attributo esteso (extendend attribute). Tutte le apps che girano su iOS 5.1 dovrebbero usa queste API per escludere i file dai backups.

LISTING 1. Escludere un file dai Backups su iOS 5.1

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{

    assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);

    NSError *error = nil;

    BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]

                                  forKey: NSURLIsExcludedFromBackupKey error: &error];

    if(!success){

        NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);

    }

    return success;

}
  • iOS 5.0.1: se l’app deve supportare iOs 5.0.1, si può usare il seguente metodo per settare l’attributo “do not backup” esteso (“do not back up” extended attribute). Ogni volta che viene creato un file o una directory che non deve essere backuppata, occorre chiamare il seguente metodo, passandogli la URL del file.

LISTING 2: Settare l’Extended Attribute su iOS 5.0.1

Warning The code that follows has been deprecated and should only be used on iOS 5.0.1 or earlier. When running in iOS 5.1, apps should use the NSURL and CFURL keys described above.

#import <sys/xattr.h>

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{

    assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);

    const char* filePath = [[URL path] fileSystemRepresentation];

    const char* attrName = "com.apple.MobileBackup";

    u_int8_t attrValue = 1;

    int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);

    return result == 0;

}
  • iOS 5.0: Non è possibile escludere i dati dal backup su iOS 5.0 (e precedenti). Se la tua app deve supportare iOS 5.0, occorre memorizzare i dati applicativi nella cartella Caches per evitare che essi vengano backuppati. iOS cancellerà questi file da Caches quando necessario, quindi l’app dovrà poterli recuperare o segnalare (e, qualora servano, farli riscaricare) (cfr. “so your app will need to degrade gracefully if it’s data files are deleted“)

Sempre più confusi, vero??? Da quanto si legge nella guida “File System Programming“, la nuova cartella “candidata” (dalle versioni di iOs superiori alla 5.0.1) è la seguente:

<Application_Home>/Library/Application Support. Use this directory to store all app data files except those associated with the user’s documents. For example, you might use this directory to store app-created data files, configuration files, templates, or other fixed or modifiable resources that are managed by the app. An app might use this directory to store a modifiable copy of resources contained initially in the app’s bundle. A game might use this directory to store new levels purchased by the user and downloaded from a server.

All content in this directory should be placed in a custom subdirectory whose name is that of your app’s bundle identifier or your company (???).

In iOS, the contents of this directory are backed up by iTunes.

Vi incollo un pò di post che ho navigato nella mia tediosa ricerca di una risposta univoca:

  - (NSString *)applicationDocumentsDirectory {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);

	NSString *documentsDirectory = [paths objectAtIndex:0];

       NSURL *pathURL= [NSURL fileURLWithPath:documentsDirectory];
    [self addSkipBackupAttributeToItemAtURL:pathURL];
    return documentsDirectory;

}

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
//for iOS>=5.1
{ if (NSURLIsExcludedFromBackupKey) {
    assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);

    NSError *error = nil;

    BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES] forKey: NSURLIsExcludedFromBackupKey error: &error];

    if(!success){
        NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);

    }

    return success;
}
//for ioS 5.0.1 and above
else {
    assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);

    const char* filePath = [[URL path] fileSystemRepresentation];

    const char* attrName = "com.apple.MobileBackup";

    u_int8_t attrValue = 1;

    int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);

    return result == 0;
}

}

In tale codice si salvano i file nella directory <Application_Home>/Library e si applica ai singoli file l’attributo ‘do not backup’ (a seconda della versione di iOS).

Ma nel post #15 si legge anche questo:

Like the Q&A says, you can use a subfolder of your app’s Library folder (like “Application_Support” or “Private Documents”) marked with the “do not backup” attribute. You use either NSURLIsExcludedFromBackupKey for iOS 5.1+ or com.apple.MobileBackup for iOS 5.0.1. For iOS 5.0 support, you’ve got no option but to store in your app’s Library/Caches folder, and prepare for those files to potentially be gone when your app’s relaunched.

e ancora nel post #17:

It’s been my experience that there are very few people running iOS 5.0 (most are at least 5.0.1), but since we wanted to support iOS 4 we had to support iOS 5.0 and store our files in Caches for that OS only. So, we check for missing files and redownload after launch, and put up a simple error message if the process doesn’t complete with a recommendation to upgrade. Upon upgrading to iOS 5.0.1 or higher we move our files to a library subfolder and use the “do not backup” key/attribute.

 

La mia soluzione (con attributo “do not backup” per iOS>=5.0.1). Dopo tutto questo sbattimento di post e di documentazione letta e riletta, stando attenti ad ogni condizionale o imperativo (nella doc di Apple ci sono un bel pò di condizionali, eppure vanno applicati tutti i punti !?!? ), riporto gli ultimi riferimenti che mi hanno portato alla soluzione. Se vi siete persi, il mio problema è “Dove memorizzo i file audio scaricati (a pagamento) tramite la mia app, senza violare le iOs Data Storage Guidelines e passare l’approvazione di Apple?“.

As per Apple guideline: Important: The new “do not back up” attribute will only be used by iOS 5.0.1 or later. On iOS 5.0 and earlier, applications will need to store their data in /Library/Caches to avoid having it backed up. Since this attribute is ignored on older systems, you will need to insure your app complies with the iOS Data Storage Guidelines on all versions of iOS that your application supports.

 

Where You Should Put Your App’s Files

To prevent the syncing and backup processes on iOS devices from taking a long time, be selective about where you place files inside your app’s home directory. Apps that store large files can slow down the process of backing up to iTunes or iCloud. These apps can also consume a large amount of a user’s available storage, which may encourage the user to delete the app or disable backup of that app’s data to iCloud. With this in mind, you should store app data according to the following guidelines:

    • Put user data in the <Application_Home>/Documents/. User data is any data that cannot be recreated by your app, such as user documents and other user-generated content.
    • Handle support files—files your application downloads or generates and can recreate as needed—in one of two ways:
      • In iOS 5.0 and earlier, put support files in the <Application_Home>/Library/Caches directory to prevent them from being backed up
      • In iOS 5.0.1 and later, put support files in the <Application_Home>/Library/Application Support directory and apply the com.apple.MobileBackup extended attribute to them. This attribute prevents the files from being backed up to iTunes or iCloud. If you have a large number of support files, you may store them in a custom subdirectory and apply the extended attribute to just the directory.
    • Put data cache files in the <Application_Home>/Library/Caches directory. Examples of files you should put in this directory include (but are not limited to) database cache files and downloadable content, such as that used by magazine, newspaper, and map apps. Your app should be able to gracefully handle situations where cached data is deleted by the system to free up disk space.

 

Quindi, la mia scelta è stata la seguente:

  • la mia app salva tutti i contenuti scaricabili (file audio) nella cartella Library/Caches per le versioni di iOS<=5.0; invece, per le versioni iOS>=5.0.1 salvo il tutto nella cartella Library/Application Support
  • Poiché per i device con iOS<=5.0 non vi è modo di contrassegnare i file come “do not backup”, occorre per forza salvarli in Caches, ma lo sviluppatore deve assicurare che, se tale cartella viene svuotata dal sistema (per spazio insufficiente o perché si è ripristinato il device – e iTunes e iCloud non backuppano tale cartella), venga reso possibile la risincronizzazione dei contenuti scaricabili o, quantomeno, si informi l’utente della loro perdita e possibilità di riscaricarli.
  • Per i device con iOs>=5.0.1, vengono applicati i due metodi visti nella guida suddetta “Come prevenire che i file vengano backuppati da iCloud e iTunes?  “

Ecco il mio codice:

  • la funzione statica pathOfDownloadDirectory, mi restituisce la directory di download dei contenuti a seconda della versione dell’iOS che cui viene eseguita l’app
  • la funzione statica addSkipBackupAttributeToItemAtURL va applicata per ciascun file da escludere dal backup di iCloud o iTunes (viene fatto un controllo di versione iOS, e tale operazione non viene applicata per iOS<=5.0 per i motivi su esposti)
/*funzione che ritorna il path della directory di download in base alla versione di iOS (per il rispetto delle iOS Data Storage Guidelines e l'uso dell'attributo 'do not backup' sui file da 
escludere dal backup di iCloud e iTunes)
*/

#import <sys/xattr.h>

+ (NSString*) pathOfDownloadDirectory{

    NSString *reqSysVer = @"5.0.1";
    NSString *currSysVer = [[UIDevice currentDevice] systemVersion];

    //if iOS>=5.0.1
    if ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending)
    {
        DLog(@"currSysVer %@",currSysVer);

        NSString *applicationSupport = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)objectAtIndex:0];
        //NSString *libraryDirectory = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
        //NSString *downloadLocation = [applicationSupport stringByAppendingPathComponent:@"MMP_Products"];

        NSString *downloadLocation = applicationSupport;

        //marchiare la cartella come non backuppabile
        NSError *error = nil;
        if (![[NSFileManager defaultManager] fileExistsAtPath:downloadLocation])
            [[NSFileManager defaultManager] createDirectoryAtPath:downloadLocation withIntermediateDirectories:NO attributes:nil error:&error]; //Create folder

        DLog(@"DOWNLOAD LOCATION %@",downloadLocation);

        return downloadLocation;

    }

    //if iOS<=5.0 (tutti quelli precedenti alla 5.0.1)
    else{

        DLog(@"currSysVer %@",currSysVer);
         NSString *cachesDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
         NSString *downloadLocation = [cachesDirectory stringByAppendingPathComponent:@"MMP_Products"];

        NSError *error = nil;
        if (![[NSFileManager defaultManager] fileExistsAtPath:downloadLocation])
            [[NSFileManager defaultManager] createDirectoryAtPath:downloadLocation withIntermediateDirectories:NO attributes:nil error:&error]; //Create folder

        DLog(@"DOWNLOAD LOCATION %@",downloadLocation);

        return downloadLocation;

    }
}

/*
funzione che effettua lo skip del backup dei file a seconda della versione di iOS
*/
+ (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{

    DLog(@"---- addSkipBackupAttributeToItemAtURL ---");

    NSString *currSysVer = [[UIDevice currentDevice] systemVersion];

    //if iOS>=5.1 o successive
    if ([currSysVer compare:@"5.1" options:NSNumericSearch] != NSOrderedAscending)
    {

       DLog(@"CURRENT VERSION 5.1 o superiori"); 

        //assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
        DLog(@"...Excluding file from backup %@",URL);

        NSError *error = nil;
        BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
                                      forKey: NSURLIsExcludedFromBackupKey error: &error];
        if(!success){
            DLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
        }

        if (error) {
            DLog(@"Error: %@", [error description]);
        }

        return success;

    }

    //if iOS>=5.0.1 (<5.1 vincolato dall'if precedente)
    else if ([currSysVer compare:@"5.0.1" options:NSNumericSearch] != NSOrderedAscending){

        DLog(@"CURRENT VERSION 5.0.1 o superiori"); 

        //assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);

        const char* filePath = [[URL path] fileSystemRepresentation];

        const char* attrName = "com.apple.MobileBackup";
        u_int8_t attrValue = 1;

        int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
        return result == 0;

    }

    //if iOS<=5.0
    else{

        DLog(@"CURRENT VERSION 5.0 o inferiori");

        return 0;
    }
}

 

Il processo di approvazione della mia app MMPlay. L’app MMPlay permette di scaricare lezioni con brani audio (a pagamento) e di sincronizzarle in un player “built-in”. Il mio grosso problema, dunque, è quello di capire dove memorizzare questi file audio.

Nella versione 1.0, MMPlay scarica i brani nella cartella <Application_Home>/Library/Caches e quelli temporanei in <Application_Home>/tmp (la funzione di download sposta prima il file progressivo in tmp e poi lo copia in Caches quando è intero). L’app controlla, quando si accede al singolo brano per il playing, se esiste nella directory locale, altrimenti avvisa l’utente che deve risincronizzarlo. Questo perché iOS potrebbe svuotare la dir di cache quando il sistema sta per finire lo spazio su disco, come citato da Apple:

“iOS will delete your files from the Caches directory when necessary, so your app will need to degrade gracefully if it’s data files are deleted.”

Su iOS<=5.0 non c’è modo di contrassegnare i file come “do not backup”, dunque vanno messi in Caches per evitare che iCloud e iTunes li backuppino, ma occorre avvisare l’utente sulla loro possibile cancellazione e la possibilità di risincronizzarli.

Mandata questa prima release in approvazione e spiegando, nelle Review Notes al team di Apple, che l’opzione “do not backup” per iOs>=5.0.1 non è stata ancora implementata, mi hanno accettato l’app.

Nella versione 1.1 di MMPlay, ho implementato quanto scritto nel paragrafo precedente La mia soluzione (con attributo “do not backup” per iOS>=5.0.1).

E’ ancora in corso di approvazione. Vi farò sapere il responso tra qualche giorno, ma sono fiducioso!

Upgrade del 26 Luglio 2012: Vi informo che la procedura su descritta per la versione 1.1 di MMPlay è stata approvata. Dunque, vanno bene entrambe le soluzioni. Ricapitolando:

  • Potete salvare i file scaricati dalle vostre app nella directory Library/Caches per tutte le versioni di iOs (sia quelle precedenti la 5.0.1 che successive). Solo che se lo spazio su disco del device è insufficiente, il sistema procederà a svuotare la directory di cache e dovrete fornire agli utenti un meccanismo che avvisi, controlli o permetta di recuperare/riscaricare i file cancellati
  • Per le versioni iOs>=5.0.1, potete mettere i file nella directory Library/Application_Support, marchiando però i file come “non backuppabili”). (Leggete sopra la procedura). Per gli iOs<=5.0 continuate a mettere tutto nella directory di cache, gestendo lo svuotamento della stessa come descritto al punto precedente. Per testare il buon funzionamento della procedura con l’attributo “do not backup” per ciascun file che salvate nella cartella Application_Support, la procedura consigliata da Apple utilizza l’app iCloud preinstallata sul device (nelle Impostazioni) ed è la seguente:

To check how much data your app is storing:

  • Install and launch your app
  • Go to Settings > iCloud > Storage & Backup > Manage Storage
  • If necessary, tap “Show all apps”
  • Check your app’s storage

14 pensieri su “[iOS] iOs Data Storage guidelines reject (2.23 – App Store Review Guidelines)

  1. Salve,

    anch’io ho avuto il tuo stesso problema. Avrei un paio di domande:
    Poiché devo salvare file non indispensabili che possono essere scaricati nuovamente, potrei risolvere il problema salvando tutto nella cache indipendentemente dalla versione (considera che l\’applicazione è valida dalla 5.0 in poi)?

    C’è comunque una maniera per testare il fatto che un file sia stato escluso dal backup correttamente?

    Grazie e saluti,
    Giordano

    • Ciao Giordano,

      grazie per avermi letto e contattato. Ho scritto un upgrade dell’articolo e ti incollo quello che ho aggiornato in risposta alla tua domanda:

      Upgrade del 26 Luglio 2012: Vi informo che la procedura su descritta per la versione 1.1 di MMPlay è stata approvata. Dunque, vanno bene entrambe le soluzioni. Ricapitolando:

      Potete salvare i file scaricati dalle vostre app nella directory Library/Caches per tutte le versioni di iOs (sia quelle precedenti la 5.0.1 che successive). Solo che se lo spazio su disco del device è insufficiente, il sistema procederà a svuotare la directory di cache e dovrete fornire agli utenti un meccanismo che avvisi, controlli o permetta di recuperare/riscaricare i file cancellati
      Per le versioni iOs>=5.0.1, potete mettere i file nella directory Library/Application_Support, marchiando però i file come “non backuppabili”). (Leggete sopra la procedura). Per gli iOs< =5.0 continuate a mettere tutto nella directory di cache, gestendo lo svuotamento della stessa come descritto al punto precedente. Per testare il buon funzionamento della procedura con l’attributo “do not backup” per ciascun file che salvate nella cartella Application_Support, la procedura consigliata da Apple utilizza l’app iCloud preinstallata sul device (nelle Impostazioni) ed è la seguente: To check how much data your app is storing: Install and launch your app Go to Settings > iCloud > Storage & Backup > Manage Storage
      If necessary, tap “Show all apps”
      Check your app’s storage

  2. Caro Francesco,

    grazie della tua risposta. Ne approfitto per farti un’altra domanda (l’ultima spero). Ho appena provato la mia applicazione su un iPad con iOS 5.0.1. Mi aspetto che i dati vengano scritti nella directory Application Support e questo avviene, ma non vengono etichettati con l’attributo “do not backup”. Ho potuto constatarlo sia da alcuni log sia dal check consigliato dalla Apple. Questo problema infatti non si evince dal simulatore perché il simulatore supporta 5.0 o 5.1 e non 5.0.1 (o sbaglio?). Cosa ne dici?

    Grazie di nuovo e ciao,
    Giordano

    • Se noti, c’è un log nella funzione addSkipBackupAttributeToItemAtURL. Se ti va in errore vuol dire che non è riuscito ad applicare il flag “do not backup” al singolo file e questo potrebbe essere già un modo per fare il debug. Se il tuo simulatore è un iPad o iPhone con versione di iOS< =5.0, allora non ti funzionerà il "do not backup" attribute (è possibile etichettarli così solo dalla 5.0.1 in poi). Se, invece, hai una versione di iOS 5.0.1 (come scrivi) mi pare molto strano che lo faccia. Ti consiglio di provare a salvare i file in un'altra directory (io per esempio ho provato a salvarli anche in una mia directory Library/MiaCartella). //if iOS>=5.1 o successive
      if ([currSysVer compare:@”5.1″ options:NSNumericSearch] != NSOrderedAscending)
      {

      DLog(@”CURRENT VERSION 5.1 o superiori”);

      //assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
      DLog(@”…Excluding file from backup %@”,URL);

      NSError *error = nil;
      BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
      forKey: NSURLIsExcludedFromBackupKey error: &error];
      if(!success){
      DLog(@”Error excluding %@ from backup %@”, [URL lastPathComponent], error);
      }

      if (error) {
      DLog(@”Error: %@”, [error description]);
      }

      return success;

      }

      Comunque, se vuoi installare varie versioni di iOS nel Simulatore, ti allego il seguente link. Poi basta switchare su nella select per scegliere su quale iOS testare:

      http://stackoverflow.com/questions/8529572/how-to-install-older-ios-simulators-in-xcode-4-2-1-sdk5-0

  3. La mia è un’applicazione newsstand ed ho corretto tutto salvando alcuni file nella directory cache. In ogni caso viene creato un file automaticamente com.apple.newsstand-library.plist di circa 8KB nella directory Library/Application Support. Non vorrei che questo file venisse considerato come i precedenti. E’ un file che non creo io ma di cui non ho trovato nessuna informazione in rete. Cosa ne pensi?

    • Tranquillo, la Apple rigetta l’app solo se vede che viene generato questo file dalla tua app e va ad ingrossare troppo la dimensione occupata dai contenuti. In poche parole, se la tua app non satura troppo spazio quando si fanno backup di iTunes e iCloud (visto che quest’ultimo si paga dopo 5 giga), allora rispetti le linee guida. Poichè il file plist è infinitesimo e nn va ad aumentare, lo puoi mettere dove ti pare, anche in Documents.

      Se metti tutto in Caches è ok. Un consiglio: nelle Review Notes di iTunes Connect, scrivi poche righe sulla soluzione che hai adottato (salvataggio dei newsstand in Caches e altre cose che reputi vada comunicato, tipo username e password di un utente test per fargli fare tutto il giro completo).

  4. Caro Francesco,

    tempo fa ho richiesto alcune cose sul problema di cui sopra perchè anche la mia applicazione era stata respinta. Ora che è tutto OK avrei una domanda. La mia App di tipo newsstand prevede una free subscription. Come ben sai se si attiva la subscription il sistema ti chiede se vuoi condividere i tuoi dati con il publisher. Ma questi dati dove vanno a finire? E come posso fare per averli?

    Grazie e ciao,
    Giordano

      • Ciao e Grazie della risposta,

        mi sembrava di aver letto da qualche parte che le informazioni venivano raccolte da Apple che poi le inviava al publisher che poi le utlizzava per fini di marketing. Ti risulta?

        Grazie e ciao,
        Giordano

          • Il link è il seguente:
            http://www.viggiosoft.com/blog/blog/2011/10/29/at-newsstand-and-subscriptions/

            In particolare ad un certo punto c’è scritto quanto segue:

            “Immediately after the purchase, the system will ask for the explicit permission to send some personal data to the publisher. This is another compromise between Apple and the publishers that accepted to provide discounted subscriptions but in return of the possibility to control this kind of data (useful for advertising purposes).”

            Quello che c’è scritto, ammesso che sia vero, lo interpreto come il fatto che l’utente invia i suoi dati al Publisher tramite la Apple. Il publisher si impegna a offrire sconti o promozioni in cambio di questi dati.

            Grazie e ciao,
            Giordano

  5. Ciao,
    Complimenti per il post.
    Io ho riscontrato la stessa cosa provando a mettere su AppStore una semplice app che dovrebbe leggere dati da un database.sqlite e mostrarli…niente di complicato.

    Invece quelli della Apple non l’hanno approvata dicendomi : “Temporary files used by your app should only be stored in the /tmp directory; please remember to delete the files stored in this location when the user exits the app.”

    Ho provato ad aggiungere il tuo codice nella classe AppDelegate.m del mio progetto pero mi da errore dicendomi: ” Use of undeclared identifier NSURLIsExcludedFromBackupKey “.

    Sicuramente la mia domanda sembra al quanto banale..però ammetto che su queste non sono proprio pratica di queste cose..Perché mi da questo errore? devo importare qualche libreria ?!

    Grazie.

    • Ciao Lexya,

      innanzitutto che versione dell’SDK stai usando e che firmware o versione del simulatore stai facendo girare? Quella classe funziona su iOS 5.1. Per cui occorre controllare la versione e gestire i tre casi elencati (iOS 5.0 e precedenti; iOS 5.0.1; iOS 5.1 e successivi).
      L’errore poi te lo da a runtime o in compilazione?

  6. Ciao, complimenti per l’articolo scusa se scrivo con così tanto ritardo ma la mia app ha sempre superato “l’esame” della revisione tranne che questa volta. In poche parole la mia è un app per rappresentati di una determinata azienda, l’app in poche parole scarica il catalogo prodotti e la lista dei clienti in un DB locale sqlite, e permette di visualizzarli ed effetturare ordini ecc ecc..
    In quest’ultimo aggiornamento non ho fatto altro che aggiungere un tasto per eliminare dei dati dal DB ma mi è stata rigettata con il seguente errore:

    Before you Submit

    On launch and content download of Aggiornamento, your app stores 45.88 MB on the user’s iCloud, which does not comply with the iOS Data Storage Guidelines.

    Next Steps

    Please verify that only the content that the user creates using your app, e.g., documents, new files, edits, etc. is backed up by iCloud as required by the iOS Data Storage Guidelines. Also, check that any temporary files used by your app are only stored in the /tmp directory; please remember to remove or delete the files stored in this location when it is determined they are no longer needed.

    Data that can be recreated but must persist for proper functioning of your app – or because users expect it to be available for offline use – should be marked with the “do not back up” attribute. For NSURL objects, add the NSURLIsExcludedFromBackupKey attribute to prevent the corresponding file from being backed up. For CFURLRef objects, use the corresponding kCRUFLIsExcludedFromBackupKey attribute.

    Adesso io ho letto la tua guida e vedo un pò di luce, ma ancora non riesco a capire bene cosa dovrei fare? il DB è memorizzato nella cartella DOCUMENTS.

    Spero tu possa ancora aiutarmi, ti ringrazio in anticipo

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *


8 + quattro =