Как справиться с первым запуском, когда требуется iCloud?

Я использую CloudKit для хранения общедоступных данных и новых NSPersistentCloudKitContainer как часть моего стека Core Data для хранения / синхронизации личных данных.

Когда пользователь открывает мое приложение, он находится в одном из 4 состояний:

  1. Это новый пользователь с доступом к iCloud.
  2. Это вернувшийся пользователь с доступом к iCloud.
  3. Это новый пользователь, но по какой-то причине у него нет доступа к iCloud.
  4. Они вернулись, но по какой-то причине у них нет доступа к 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

Другие вопросы по тегам