3
\$\begingroup\$

I'm using react native to build an iOS app, but have little to no experience in obj C or iOS development. The app I'm building has a bunch of content files that aren't user specific, will be downloaded from some CDN, and will (mostly) be behind a paywall. As I understand it, the best place for this would be somewhere within Application Support (the Cache dir doesn't seem to fit the bill because this is more about allowing users to download content for offline use than caching per se. Also, I think there's no guarantees about the lifetime of files in the Cache dir(?).). Also, I want to make sure that those content files aren't backed up to iCloud or anywhere else; and that they're not accessible by the user or other apps.

I have two questions:

  1. Does that placement for the content fit with iOS recommendations/best practices?

  2. Because I have little obj c/iOS experience, I'd be super grateful if you lovely people could cast your eyes over the code and review it?

// *** blah.h
#import <React/RCTBridgeModule.h>

@interface Blah : NSObject <RCTBridgeModule>
@end


// *** blah.m
#import "Blah.h"

@implementation Blah

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(createAppDataPath:(NSString *)namespace findEventsWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){

    NSUInteger length = [namespace length];

    if (length < 1) {
        reject(@"namespace_invalid", @"Namespace param should be non-empty string", nil);
        return;
    }

    // ref: https://stackoverflow.com/a/17430820/1937302

    @try {

        NSString *appSupportDir = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject];
        NSString *cacheDir = [appSupportDir stringByAppendingString: [NSString stringWithFormat:@"/%@", namespace]];

        // check for existence of directory:
        BOOL isDir;
        BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:cacheDir isDirectory:&isDir];

        if (!exists) {
            NSError *error = nil;
            // if not present, create it:
            BOOL result = [[NSFileManager defaultManager] createDirectoryAtPath:cacheDir withIntermediateDirectories:YES attributes:nil error:&error];
            if (!result) {
                reject(@"cant_create", @"Can't create directory", error);
                return;
            }
        } else if (!isDir) {
            reject(@"non_directory_path_exists", @"Path already exists as a non-directory", nil);
            return;
        }

        // now mark the directory as excluded from iCloud backups:
        NSURL *url = [NSURL fileURLWithPath:cacheDir];
        NSError *error = nil;
        BOOL result = [url setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&error];

        if (!result) {
            reject(@"cant_exclude_from_backups", @"Can't exclude from backups", error);
            return;
        }

        resolve(cacheDir);

    }
    @catch(NSException *exception) {
        // TODO: capture information from exception and pass to rejection?
        // ref: https://stackoverflow.com/questions/43561531/how-to-convert-an-exception-into-an-nserror-object\
        // ref: https://stackoverflow.com/a/4654759/1937302
        // ref: https://developer.apple.com/documentation/foundation/nsexception
        reject(@"exception", @"An exception has occurred", nil);
    }
}

@end
\$\endgroup\$
0
\$\begingroup\$

File System Basics tells us:

The contents of the Library directory (with the exception of the Caches subdirectory) are backed up by iTunes and iCloud.

And

Put data cache files in the Library/Caches/ directory. Cache data can be used for any data that needs to persist longer than temporary data, but not as long as a support file. Generally speaking, the application does not require cache data to operate properly, but it can use cache data to improve performance. Examples of cache data include (but are not limited to) database cache files and transient, downloadable content. Note that the system may delete the Caches/ directory to free up disk space, so your app must be able to re-create or download these files as needed.

If this content is readily re-downloadable, Caches might actually be a reasonable idea. It makes your app a “good citizen”. If the user is running low on storage, it might be better to allow re-downloadable content to be removed rather than tempting the user, looking at their storage taken up by various apps, to contemplate removing your app because it’s taking up a lot of unreclaimable space.

If you want to ensure the content will never be deleted when the device runs low on space, then go ahead and put it in the Application Support directory, but manually exclude it from backups, like you have done.

Feel free to refer the iOS Storage Best Practices video (and that page has useful links).


You said:

I think there's no guarantees about the lifetime of files in the Cache dir

Correct. In practice, when (a) the device runs low on space; and (b) your app is not running, the OS can purge this directory. It starts with apps that have been use less recently.

Also, I want to make sure that those content files aren't backed up to iCloud or anywhere else; and that they're not accessible by the user or other apps.

By the way, whether it’s backed-up or not and whether it’s accessible by the user are two different questions. E.g., files in the Application Support directory are backed-up (other than, obviously, content of the Caches folder or those files explicitly designated to not be backed-up). But the contents are not visible to the user. Only user documents stored in Documents folder or iCloud Drive are visible to the end-user.

So, if your content is not readily re-downloadable, but you don’t want to expose them to the individual files, Application Support (without specifying NSURLIsExcludedFromBackupKey) might be prudent. It’s your call.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.