I had reason to investigate this properly today and ended up running a comprehensive set of tests here in my office. I figured that, once I’ve done all this work, I might as well share the results with everyone.
....
|
Accessible | Backed Up By | Restore On | Result |
Normal | iTunes | same device | restored |
| | different device | gone |
| iTunes, encrypted | same device | restored |
| | different device | restored |
| iCloud | same device | restored |
| | different device | gone |
ThisDeviceOnly | iTunes | same device | restored |
| | different device | gone |
| iTunes, encrypted | same device | restored |
| | different device | gone |
| iCloud | same device | restored |
| | different device | gone |
Some testing parameters:
- Xcode 10.1
- iOS 12 (12.0.1 on one device, 12.1.1 on the other)
- iCloud account has neither two-factor nor two-step authentication enabled
However, I don’t think any of these parameters make a difference. As far as I know things have always behaved this way.
Finally, my test code it pasted in below, just in case you want to try this yourself (in which case you’ll get very familiar with the iOS setup assistant :-).
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
| class MainViewController : UITableViewController { |
| |
| @IBOutlet var normalLabel: UILabel! |
| @IBOutlet var thisDeviceOnlyLabel: UILabel! |
| |
| override func viewWillAppear(_ animated: Bool) { |
| super.viewWillAppear(animated) |
| self.load() |
| } |
| |
| private func load(account: String, into label: UILabel) { |
| var copyResult: CFTypeRef? = nil |
| let err = SecItemCopyMatching([ |
| kSecClass: kSecClassGenericPassword, |
| kSecAttrService: "KeychainBackupTest", |
| kSecAttrAccount: account, |
| kSecReturnData: true, |
| ] as NSDictionary, ©Result) |
| switch err { |
| case errSecItemNotFound: |
| label.text = "-" |
| case errSecSuccess: |
| let resultData = copyResult! as! NSData as Data |
| let result = String(bytes: resultData, encoding: .utf8)! |
| label.text = result |
| default: |
| label.text = "\(err)" |
| } |
| } |
| |
| private func load() { |
| self.load(account: "normal", into: self.normalLabel) |
| self.load(account: "thisDeviceOnly", into: self.thisDeviceOnlyLabel) |
| } |
| |
| private func save(account: String, access: CFString, value: String) { |
| _ = SecItemAdd([ |
| kSecClass: kSecClassGenericPassword, |
| kSecAttrService: "KeychainBackupTest", |
| kSecAttrAccount: account, |
| kSecAttrAccessible: access, |
| kSecValueData: value.data(using: .utf8)! |
| ] as NSDictionary, nil) |
| } |
| |
| private func save() { |
| let value = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium) |
| |
| self.save(account: "normal", access: kSecAttrAccessibleWhenUnlocked, value: value) |
| self.save(account: "thisDeviceOnly", access: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, value: value) |
| self.load() |
| } |
| |
| private func reset() { |
| _ = SecItemDelete([ |
| kSecClass: kSecClassGenericPassword, |
| ] as NSDictionary) |
| self.load() |
| } |
| |
| override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { |
| switch (indexPath.section, indexPath.row) { |
| case (0, 0): break |
| case (0, 1): break |
| case (0, 2): self.save() |
| case (0, 3): self.reset() |
| default: fatalError() |
| } |
| self.tableView.deselectRow(at: indexPath, animated: true) |
| } |
| } |