简体   繁体   中英

Preventing/Detecting iCloud migration of app data in UserDefaults and KeyChain

When a user gets a new iPhone, iCloud can restore app data from a different device, which copies info from UserDefaults and the Keychain.

This presents problems for my app when a user migrates from iPhone A -> iPhone B, because the app stores a device-specific security key that changes irregularly.

  1. The restored security key may be expired (an old backup).
  2. The user may continue using both iPhone A and iPhone B, causing their stored security keys get out-of-sync with rotations.

This would be easy to fix if I could detect the iCloud data restore, or an upgrade to a new device. This would allow me to reset the persisted device identifier and clear out the persisted old security key.

But I can find no way to do so, because Apple blocks accessing any unique device identifier so you can't tell if the app has moved to a new device . It also gives no callbacks about when an iCloud restore happened. I could check the hardware device model for changes, but sometimes a user replaces a phone with identical hardware when a phone is damaged or lost.

Is there any way to detect migration of an app to a new device and/or prevent cloning of iCloud backups of my app data from one device to another?

You can detect if an app is installed from iCloud backup by saving a file in the .applicationSupportDirectory . That directory is not backed up, so if your app crates a file there and doesn't see it, then that means it is (a) the first time your app has run or (b) the app was restored from backup.

You can use this as a flag to perform any special cleanup when a restore is detected.

And if you need to discern between a first time install and a restore, just save a second flag to UserDefaults . If the flag exists in UserDefaults but the flag file does not exist in .applicationSupportDirectory then you know it was an iCloud restore.

This technique has passed App Store review once as of this writing.

class RestoredAppDetector {
    func saveInstallationFlagFile() {
        if let applicationSupportDirectory = try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) {
            var flagFile = applicationSupportDirectory.appendingPathComponent("app_installed.txt", isDirectory: false)
            if (!FileManager.default.createFile(atPath: flagFile.path, contents: "true".data(using: .utf8)) ) {
                NSLog("Filed to create flag file")
            }
            var values = URLResourceValues()
            values.isExcludedFromBackup = true
            do {
                try flagFile.setResourceValues(values)
            }
            catch {
                NSLog("Failed to set resource value")
            }
        }
        else {
            NSLog("Could not create application support directory.")
        }
    }

    func installationFlagFileExists() -> Bool {
        if let applicationSupportDirectory = try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
            let flagFile = applicationSupportDirectory.appendingPathComponent("app_installed.txt", isDirectory: false)
            if (FileManager.default.fileExists(atPath: flagFile.path)) {
                NSLog("Flag file exists")
                return true
            }
            else {
                NSLog("Flag file does not exist")
            }
        }
        else {
            NSLog("Could not find application support directory.")
        }
        return false
    }
    
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM