NSUbiquitousKeyValueStore is Shared between equipment provided by the apple key data solution. This article will do to its use in a simple introduction, mainly discussed how to used in SwiftUI NSUbiquitousKeyValueStore conveniently.

The original post was posted on my blog wwww.fatbobman.com

Welcome to subscribe my public account: [Elbow’s Swift Notepad]

What is a NSUbiquitousKeyValueStore

NSUbiquitousKeyValueStore version can be understood as UserDefaults network synchronization. It’s part of a service called CloudKit that allows you to share data across devices (with the same iCloud account) with a simple configuration.

NSUbiquitousKeyValueStore performance in most cases with UserDefaults very similar:

  • It’s all key-value based storage
  • You can only use strings as keys
  • You can use any Property List object types as values
  • Use similar read and write methods
  • They are the first to store data in memory, and the system will persist the data in memory at any time (usually without the intervention of the developer)

Even if you haven’t used UserDefaults, it only takes a few minutes to read the official documentation to get the basics.

And that’s the difference between UserDefaults

  • NSUbiquitousKeyValueStore registered a default value method are provided

    Using UserDefaults, developers can use the register (defaults: [String: Any]) is a key to set the default values, NSUbiquitousKeyValueStore does not provide a similar means. For types that do not return optional values, avoid shortcuts to get values.

    For example, an integer value with the key name “count” could be obtained using code similar to the following:

func getInt(key: String.defaultValue: Int) -> Int {
    guard let result = NSUbiquitousKeyValueStore.default.object(forKey: key) as? Int else {
        return defaultValue
    }
    return result
}

let count = getInt(key: "count", defaultValue: 30)

// The return value of longLong is not optional and should be avoided using shortcuts like the one below
/ / NSUbiquitousKeyValueStore. Default. LongLong (forKey: "count"), the default value is 0
Copy the code
  • NSUbiquitousKeyValueStore have more restrictions

    Apple does not recommend using NSUbiquitousKeyValueStore save large amount of data and changes frequently and is critical to the app running data.

    NSUbiquitousKeyValueStore the largest storage capacity of 1 MB (per user), storage keys to must not exceed 1024.

    NSUbiquitousKeyValueStore network synchronization efficiency, in the case of a smooth, a key/value pair data can complete synchronization in 10 to 20 seconds. If the data changes frequently, iCloud will automatically reduce the synchronization frequency, which can be extended to several minutes. When testing, developers will change the data many times in a short period of time, and there is a high probability of slow synchronization.

    Although NSUbiquitousKeyValueStore did not provide data synchronization support atom, but in most cases, NSUbiquitousKeyValueStore will try to ensure that the user switches up to after account, login again up to account, no network reconnected conditions such as data integrity. However, in some cases, data is not updated and devices are not synchronized, for example:

    When the app is running normally, the user chooses to disable iCloud synchronization of the app in the system Settings. Since all NSUbiquitousKeyValueStore changes in the app, even after users restore app up synchronization function, are not uploaded to the server.

  • NSUbiquitousKeyValueStore needs a developer account

    You need a developer account to enable iCloud syncing.

  • NSUbiquitousKeyValueStore has yet to provide SwiftUI convenient usage

    Since iOS 14, Apple has provided AppStorage for SwiftUI, which allows views to respond to changes in UserDefaults in the same way they do with @State.

In most cases, we can think of @AppStorage as a SwiftUI wrapper for UserDefaults, but in some cases @AppStorage does not behave exactly as UserDefaults does (not just in terms of supported data types).

configuration

In code using NSUbiquitousKeyValueStore before, we first need to certain configuration to enable the project up to the key value of storage capabilities.

  • In project TARGET’s Signing&Capabilities, set the right Team

  • In Signing&Capabilities, click in the upper left corner+CapabilityAdd iCloud functionality

  • In iCloud, select key-value storage

After selecting the key-value store, Xcode will automatically create Entitlements files for the project. $(TeamIdentifierPrefix)$(CFBundleIdentifier) for iCloud key-value Store

TeamIdentifierPrefix is your developer Team (need to be added at the end). , which can be obtained from the top right corner of developer account Certificates, Identifiers & Profiles (consisting of alphanumeric and dot XXXXXXXX) :

CFBundleIdentifier is the app’s BundleIdentifier.

If you want to use the same iCloud key-value Store on other apps or extensions, entitlements can manually modify the corresponding contents of the file.

The easiest way to get iCloud key-value stores for other apps is to add a Key to plist with the value $(TeamIdentifierPrefix)$(CFBundleIdentifier), Use bundle.main.object (forInfoDictionaryKey:) to check.

To be sure, under the same developer account, as long as you point to the same iCloud key-value Store, data can be synchronized between different apps and app extensions (the same iCloud account). I cannot test the situation that different developer accounts point to the same iCloud key-value Store, please help test it and let me know if you are qualified, thank you.

The use of NSUbiquitousKeyValueStore in SwiftUI view

This section, we will not use any third-party repository, realize real-time response to the change of NSUbiquitousKeyValueStore SwiftUI view.

NSUbiquitousKeyValueStore basic working process is as follows:

  • Will be key to save the NSUbiquitousKeyValueStore
  • NSUbiquitousKeyValueStore key values of the data stored in the memory
  • The system persists data to disk (developers can callsynchronize()Call this operation explicitly.)
  • The system sends the changed data to iCloud
  • ICloud and other devices will synchronize the changed data whenever possible
  • The device persists network-synchronized data locally
  • After the synchronization is complete, the message will be sentNSUbiquitousKeyValueStore.didChangeExternallyNotificationNotifications, reminders to developers

The workflow program is almost identical to UserDefaults except for the steps for network synchronization.

Under the condition of without using a third-party library, in SwiftUI view by bridging @ State data form, will NSUbiquitousKeyValueStore changes associated with the view.

The following code will create a key name in NSUbiquitousKeyValueStore for text strings, and with it the view the variables in the text links:

struct ContentView: View {
    @State var text = NSUbiquitousKeyValueStore().string(forKey: "text") ?? "empty"

    var body: some View {
        TextField("text:", text: $text)
            .textFieldStyle(.roundedBorder)
            .padding()
            .task {
                for await _ in NotificationCenter.default.notifications(named: NSUbiquitousKeyValueStore.didChangeExternallyNotification) {
                    if let text = NSUbiquitousKeyValueStore.default.string(forKey: "text") {
                        self.text = text
                    }
                }
            }
            .onChange(of: text, perform: { value in
                NSUbiquitousKeyValueStore.default.set(value, forKey: "text")}}}Copy the code

The code in task does the same thing as the code below. To see how to use Combine and async/await, see the article on collaboration between Combine and async/await:

.onReceive(NotificationCenter.default.publisher(for: NSUbiquitousKeyValueStore.didChangeExternallyNotification)) { _ in
    if let text = NSUbiquitousKeyValueStore.default.string(forKey: "text") {
        self.text = text
    }
}
Copy the code

In the userinfo didChangeExternallyNotification also contain other information, such as message prompt reasons and the change of key names, etc.

In fact, we can’t for each NSUbiquitousKeyValueStore key is used to drive the view above, in the next article we will try to use more convenient way to complete the integration work with SwiftUI.

Like @ AppStorage NSUbiquitousKeyValueStore use

Although the code in the previous section was a bit cumbersome, But it has pointed out the NSUbiquitousKeyValueStore would change with the direction of view linkage – NSUbiquitousKeyValueStore same can lead to view refresh data (State, ObservableObject, etc.), You can achieve the same effect as @AppStorage.

It’s not complicated in principle, but it still takes a lot of careful work to be able to support all types. But Tom Lokhorst has achieved all this for us, using his CloudStorage library development, we can use NSUbiquitousKeyValueStore very easily in the view.

The code in the previous section becomes:

@CloudStorage("text") var text = "empty"
Copy the code

The usage is the same as @AppStorage.

Many developers, when choosing support NSUbiquitousKeyValueStore third-party libraries might first think of Zephyr. Zephyr UserDefaults in processing with the linkage between NSUbiquitousKeyValueStore doing very good, but due to the uniqueness of the @ AppStorage (not real UserDefaults complete packaging), Zephyr support for @AppStorage is currently questionable and I don’t recommend it.

Centralized management of NSUbiquitousKeyValueStore key values

As the app created in UserDefaults, NSUbiquitousKeyValueStore of key/value pair in view one by one the way to let the data become unmanageable. Therefore, we need to find a way for SwiftUI to centrally configure and manage key/value pairs.

In @AppStorage research article, I have introduced how to unified management and centralized injection of @AppStorage. Such as:

class Defaults: ObservableObject {
    @AppStorage("name") public var name = "fatbobman"
    @AppStorage("age") public var age = 12
}

// In the view, inject centrally
@StateObject var defaults = Defaults(a).
Text(defaults.name)
TextField("name",text:defaults.$name)
Copy the code

So, can we use this idea to include @CloudStorage?

Unfortunately, I still don’t understand how @AppStorage implements a similar @Published behavior at the code level. So we have to do it in a relatively clumsy way.

I modified CloudStrorage a bit by adding notification mechanisms at several points in time of data changes, by using the ObservableObject compliant class, Respond to this notification and invoke objectwillchange.send () to emulate @AppStorage’s features.

You can download the modified CloudStroage code here.

Use code similar to the following to achieve unified management:

import CloudStorage
final class Storage: ObservableObject {
    // It is saved locally and no synchronization is required
    @AppStorage("name") var name = "fat"
    
    // The configuration to be synchronized
    @CloudStorage("cloud") var cloud = true
    @CloudStorage("text") var text = "Hello"

    init(a) {
        NotificationCenter.default.addObserver(forName: CloudStorageSync.updateSwiftUIViewForNSUbiquitousKeyValuesStoreChange, object: nil, queue: nil, using: update)
    }

    deinit {
        NotificationCenter.default.removeObserver(self, name: CloudStorageSync.updateSwiftUIViewForNSUbiquitousKeyValuesStoreChange, object: nil)}// Refresh the view
    private func update(notification: Notification) {
        objectWillChange.send()
    }
}
Copy the code

Code in the view:

struct ContentView: View {
    @StateObject var storage = Storage(a)var body: some View {
        VStack {
            TextField("name:", text: $storage.name) // @appStorage does not limit the Binding mode for storage.$name can also be used
            Text(storage.name)
            Toggle("cloud", isOn: $storage.cloud) // Notice how Binding is called
            Text(storage.cloud ? "true" : "false")
            TextField("text:", text: $storage.text) // Notice how Binding is called}}}Copy the code

Due to the special packaging of SwiftUI system components, when you use the above method to manage @AppStorage and @Cloudstorage data in a unified manner, pay special attention to how to invoke @CloudStorage Binding data in the view.

Stroage.$cloud will cause binding data to be unable to refresh wrappedValue, resulting in incomplete data updates on the view.

conclusion

NSUbiquitousKeyValueStore just as its name, let the app data everywhere. This feature can be added to your app with very little configuration, and friends who need it can take action!

I hope this article has been helpful to you.

The original post was posted on my blog wwww.fatbobman.com

Welcome to subscribe my public account: [Elbow’s Swift Notepad]