Note: I posted a lazy question before for converting code to Swift 3 (deleted it)
Apple has some sample code for managing files. It is an old guide and is all in Objective-C. I converted the snippet to Swift 3. My concern is over the error handling part. I'm nesting multiple do
/catch
blocks... just want to know if this is the optimal way of doing things??
There is a similar question/amswer to this here.
The document is: Apple File System Programming Guide, under section "Managing Files and Directories".
This is my code (converted to Swift 3):
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
do {
// Just try to copy the directory.
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch CocoaError.fileWriteFileExists {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
do {
try fileManager.removeItem(at: backupDir)
} catch let error {
// If the operation failed again, abort for real.
print("Operation failed again, abort with error: \(error)")
}
} catch let error {
// If the operation failed again, abort for real.
print("Other error: \(error)")
}
}
}
This is Apple's code in their docs which I converted:
- (void)backupMyApplicationData {
// Get the application's main data directory
NSArray* theDirs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
if ([theDirs count] > 0)
{
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
NSURL* appSupportDir = (NSURL*)[theDirs objectAtIndex:0];
NSString* appBundleID = [[NSBundle mainBundle] bundleIdentifier];
NSURL* appDataDir = [[appSupportDir URLByAppendingPathComponent:appBundleID]
URLByAppendingPathComponent:@"Data"];
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
NSURL* backupDir = [appDataDir URLByAppendingPathExtension:@"backup"];
// Perform the copy asynchronously.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
NSFileManager* theFM = [[NSFileManager alloc] init];
NSError* anError;
// Just try to copy the directory.
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
if ([theFM removeItemAtURL:backupDir error:&anError]) {
// If the operation failed again, abort for real.
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
// Report the error....
}
}
}
});
}
}
Any thoughts?
You forgot to retry the copy operation after deleting an existing backup. Also, "catch let error" can be written as just "catch" because the error will be automatically assigned to a constant named "error" if you don't specify a catch pattern. Here is your code with these changes:
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard
directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier
else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
do {
// Just try to copy the directory.
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch CocoaError.fileWriteFileExists {
// Error occurred because a previous backup directory
// already exists. Delete the old directory and try again.
do {
try fileManager.removeItem(at: backupDir)
} catch {
// The delete operation failed, abort.
print("Deletion of existing backup failed. Abort with error: \(error)")
return
}
do {
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch {
// The copy operation failed again, abort.
print("Copy operation failed again. Abort with error: \(error)")
}
} catch {
// The copy operation failed for some other reason, abort.
print("Copy operation failed for other reason. Abort with error: \(error)")
}
}
}
If you want a translation that's closer to the Objective-C original, where there's only one error output, try this:
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard
directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier
else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
// Just try to copy the directory.
if (try? fileManager.copyItem(at: appDataDir, to: backupDir)) == nil {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
if (try? fileManager.removeItem(at: backupDir)) != nil {
do {
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch {
// The copy retry failed.
print("Failed to backup with error: \(error)")
}
}
}
}
}