Как справиться с первым запуском, когда требуется iCloud?
Я использую CloudKit
для хранения общедоступных данных и новых NSPersistentCloudKitContainer
как часть моего стека Core Data для хранения / синхронизации личных данных.
Когда пользователь открывает мое приложение, он находится в одном из 4 состояний:
- Это новый пользователь с доступом к iCloud.
- Это вернувшийся пользователь с доступом к iCloud.
- Это новый пользователь, но по какой-то причине у него нет доступа к iCloud.
- Они вернулись, но по какой-то причине у них нет доступа к iCloud.
Состояния 1 и 2 представляют мои счастливые пути. Если это новый пользователь, я хотел бы заполнить частное хранилище пользователя некоторыми данными, прежде чем показывать начальное представление. Если они возвращаются, я хотел бы получить данные из Core Data для перехода к начальному просмотру.
Определение нового / старого пользователя: Мой план - использоватьNSUbiquitousKeyValueStore
. Мое беспокойство по поводу этого касается случая, когда они:загрузили приложение -> были записаны как запустившие приложение раньше -> удалили и переустановили / установили приложение на новом устройстве, я полагаюNSUbiquitousKeyValueStore
получение обновлений займет некоторое время, поэтому мне нужно дождаться завершения синхронизации, прежде чем перейти к начальному представлению. Тогда возникает вопрос, что произойдет, если у них нет доступа к iCloud? Как можетNSUbiquitousKeyValueStore
Скажите, если они вернулись, если он не может получать обновления?
Определение доступа к iCloud: на основании проведенного мной исследования я могу проверить,FileManager.default.ubiquityIdentityToken
является nil
чтобы узнать, доступен ли iCloud, но это не скажет мне, почему. Я бы использовалCKContainer.default().accountStatus
чтобы узнать, почему iCloud недоступен. Проблема в том, что это асинхронный вызов, и мое приложение перешло бы дальше, прежде чем узнало бы, каков статус их учетной записи.
Я действительно ломаю голову над этим. Как лучше всего изящно убедиться, что все эти состояния обрабатываются?
3 ответа
улучшить решение Вистлера по пунктам 3 и 4,
- Это новый пользователь, но по какой-то причине у него нет доступа к iCloud.
- Они являются постоянными пользователями, но по какой-то причине не имеют доступа к iCloud.
следует также использовать, чтобы он охватывал пользователей в автономном режиме и имел лучшую производительность, пропуская сетевые подключения, когда они не нужны, то есть каждый раз после первого раза .
решение
func isFirstTimeUser() async -> Bool {
if UserDefaults.shared.bool(forKey: "hasSeenTutorial") { return false }
let db = CKContainer.default().privateCloudDatabase
let predicate = NSPredicate(format: "CD_entityName = 'Item'")
let query = CKQuery(recordType: "CD_Container", predicate: predicate)
do {
let items = (try await db.records(matching: query)).matchResults
return items.isEmpty
} catch {
return false
// this is for the answer's simplicity,
// but obviously you should handle errors accordingly.
}
}
func showTutorial() {
print("showing tutorial")
UserDefaults.shared.set(true, forKey: "hasSeenTutorial")
}
Как видно, после первого пользовательского заданияshowTutorial()
,UserDefaults
Значение bool для ключа hasSeenTutorial установлено в true, так что больше не нужно звонить дорогоCK...
после.
Применение
if await isFirstTimeUser() {
showTutorial()
}
Здесь нет "правильного" ответа, но я не считаю, что NSUbiquitiousKeyValueStore в любом случае является победой - как вы сказали, если они не вошли в iCloud или не имеют доступа к сети, это все равно для них не сработает. У меня есть кое-что, связанное с совместным использованием, сделано с помощью NSUbiquitiousKeyValueStore в настоящее время, и в следующий раз я не буду делать это так. Я очень надеюсь, что NSPersistentCloudKitContainer поддерживает совместное использование в iOS 14, и я могу просто стереть большую часть своего кода CloudKit одним махом.
Если ваше приложение не работает без доступа к облаку, вы, вероятно, можете просто добавить экран с указанием этого, хотя в целом это не очень удовлетворительный пользовательский опыт. Я считаю, что синхронизация iCloud действительно асинхронна (что так и есть). Итак, я разрешаю пользователю начать использовать приложение. Затем вы можете позвонить в accountStatus, чтобы узнать, доступен ли он в фоновом режиме. Если это так, запустите синхронизацию, если нет, подождите, пока это произойдет, а затем запустите процесс.
Таким образом, пользователь может использовать приложение на неопределенное время автономно на устройстве, и в то время, когда он подключается к Интернету, все, что он делал на любом другом устройстве, объединяется с тем, что он делал на этом новом устройстве.
Совсем недавно я тоже боролся с этой проблемой. Решение, которое я придумал, заключалось в том, чтобы запросить iCloud напрямую с помощью CloudKit и посмотреть, инициализирован ли он. На самом деле это очень просто:
public func checkRemoteData(completion: @escaping (Bool) -> ()) {
let db = CKContainer.default().privateCloudDatabase
let predicate = NSPredicate(format: "CD_entityName = 'Root'")
let query = CKQuery(recordType: .init("CD_Container"), predicate: predicate)
db.perform(query, inZoneWith: nil) { result, error in
if error == nil {
if let records = result, !records.isEmpty {
completion(true)
} else {
completion(false)
}
} else {
print(error as Any)
completion(false)
}
}
}
Этот код иллюстрирует более сложный случай, когда у вас есть экземпляры Container
сущность с производной моделью, в данном случае называется Root
. У меня было что-то подобное, и я мог использовать наличие корня как доказательство того, что данные были настроены.
См. Документацию из первых рук о том, как информация Core Data передается в iCloud: https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit/reading_cloudkit_records_for_core_data