前言

作为一个客户端开发者,后端开发一直是我的弱项。尽管GPT的横空出世,让我对后端的开发有一点端倪。可是现实是,能不触碰就不触碰,因为人的精力是有限,怎么在有限的时间里发挥最大的效果一直是我的一个追求。所以回到本次主题,我自己上线的一个产品,现在已经成功上线了iOS以及Mac端,怎么在不开发后端的情况下完成同步轻量级数据呢?答案是使用Cloud Kit。Cloud Kit是苹果官方API,用于同步同一iCloud账号下设备的数据,包含Key Value Storage、云dataBase、云document等等。经过翻阅pub类似的库,我发现了有以下几个pub包是能够参阅的cloud_kit、icloud_storage,可是他们都不是我想要的,要么是不支撑Mac、要么是只能同步存储文件,可是我想要同步的其实只是轻量级耐久化数据(也是就是iOS下的UserDefault,基于键值存储)。所以没办法了,肝了一晚上我手撸了一个Flutter插件icloud_kv_storage,完美的支撑了跨苹果设备iOS、Mac、iPad等的轻量级Key Value数据同步。

开发过程全解析

  • 创建Flutter插件项目,指定支撑iOS、Mac平台

怎么创建插件尽管已经很简略,这儿我仍是点一下。


flutter create icloud_kv_storage -t plugin
cd ./icloud_kv_storage
//指定platforms
flutter create ./ -t plugin --platforms ios
flutter create ./ -t plugin --platforms macos
  • 先规划Flutter Channel接口

由所以肝了一晚上的产品,十分赶,第一版先只支撑同步String数据,后续再支撑别的根底数据,如int、double、bool等。


///获取真正的Key,我内部Key都加了flutter前缀,假如要拿实在的Key能够使用此办法
String getRealKey(String key) {
throw UnimplementedError('getRealKey()) has not been implemented.');
}
///原生的CallBack 用于实时改写数据(假如多台苹果设备都在线,支撑实时同步
void setNativeCallBack({required GetNativeCallBackFuture onCallBack}) {
}
///保存数据接口,这儿用范型便利后续拓宽,尽管第一版只支撑String
Future<void> write<T>({required String key,required T value}) {
throw UnimplementedError('write({required String key,required String value}) has not been implemented.');
}
///读取一个Key的数据接口,同理也是范型
Future<T?> read<T>({required String key}) {
throw UnimplementedError('read({required String key}) has not been implemented.');
}
///删去一个key
Future<void> delete({required String key}) {
throw UnimplementedError('delete({required String key}) has not been implemented.');
}
  • 原生Swift接口的完成

iOS和Mac实践都是共用Foundation框架,所以咱们只需要编写同一套Swift完成,即可一起满意iOS以及Mac。

1.首要定义Channel完成协议


enum CKCommandType: String {
case DELETE_VALUE
case GET_VALUE
case SAVE_VALUE
case EMPTY = ""
}
protocol CKCommandHandlerProtocol {
var COMMAND_NAME: CKCommandType { get }
func evaluateExecution(command: String) -> Bool
func handle(command: String, arguments: Dictionary<String, Any>, result: @escaping FlutterResult) -> Void
}

2.完成增删改查对应methodName的协议

查询一个key对应的协议完成


class CKGetValueHandler: CKCommandHandlerProtocol {
var COMMAND_NAME: CKCommandType = .GET_VALUE
func evaluateExecution(command: String) -> Bool {
return CKCommandType(rawValue: command) == COMMAND_NAME
}
func handle(command: String, arguments: Dictionary<String, Any>, result: @escaping FlutterResult) {
if (!evaluateExecution(command: command)) {
return
}
if let key = arguments["key"] as? String {
let store = NSUbiquitousKeyValueStore.default
result(store.object(forKey: key))
} else {
result(FlutterError.init(code: "Error", message: "Cannot pass key and value parameter", details: nil))
}
}
}

删去一个Key的完成


class CKDeleteValueHandler: CKCommandHandlerProtocol {
var COMMAND_NAME: CKCommandType = .DELETE_VALUE
func evaluateExecution(command: String) -> Bool {
return CKCommandType(rawValue: command) == COMMAND_NAME
}
func handle(command: String, arguments: Dictionary<String, Any>, result: @escaping FlutterResult) {
if (!evaluateExecution(command: command)) {
return
}
if let key = arguments["key"] as? String {
let store = NSUbiquitousKeyValueStore.default
store.removeObject(forKey: key)
result(true)
} else {
result(FlutterError.init(code: "Error", message: "Cannot pass key parameter", details: nil))
}
}
}

写入保存一个Key的完成


class CKSaveValueHandler: CKCommandHandlerProtocol {
var COMMAND_NAME: CKCommandType = .SAVE_VALUE
func evaluateExecution(command: String) -> Bool {
return CKCommandType(rawValue: command) == COMMAND_NAME
}
func handle(command: String, arguments: Dictionary<String, Any>, result: @escaping FlutterResult) {
if (!evaluateExecution(command: command)) {
return
}
if let key = arguments["key"] as? String, let value = arguments["value"] as? String{
let store = NSUbiquitousKeyValueStore.default
store.set(value, forKey: key)
result(true)
} else {
result(FlutterError.init(code: "Error", message: "Cannot pass key and value parameter", details: nil))
}
}
}
  • 多设备实时改写的完成

这儿其实完成一下原生的告诉监听,返回到Flutter就行,代码如下。


let keyValueStore = NSUbiquitousKeyValueStore.default
// 监听数据改变
NotificationCenter.default.addObserver(self, selector: #selector(keyValueStoreDidChange), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: keyValueStore)
keyValueStore.synchronize()
@objc func keyValueStoreDidChange(notification: Notification) {
// 处理数据改变
if let userInfo = notification.userInfo as? [String: Any],
let reasonForChange = userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] as? NSNumber {
var reason = -1
reason = reasonForChange.intValue
if (reason == NSUbiquitousKeyValueStoreServerChange || reason == NSUbiquitousKeyValueStoreInitialSyncChange) {
guard let changedKeys = userInfo[NSUbiquitousKeyValueStoreChangedKeysKey] as? [String] else {
return
}
let store = NSUbiquitousKeyValueStore.default
for key in changedKeys {
let value = store.object(forKey: key)
self.channel?.invokeMethod("icloud_key_update", arguments: [key:value])
}
}
}
}
  • Plugin Channel通讯的处理

上面一个一个的协议完成结束,咱们只需要扔到Flutter Plugin handle的协议办法里就可。代码如下


public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let callArguments: Dictionary<String, Any> = call.arguments as! Dictionary<String, Any>
CKGetValueHandler().handle(command: call.method, arguments: callArguments, result: result)
CKSaveValueHandler().handle(command: call.method, arguments: callArguments, result: result)
CKDeleteValueHandler().handle(command: call.method, arguments: callArguments, result: result)
}

就这样一个支撑跨苹果全家桶同步耐久化Key Value数据的Flutter插件就完全了,是不是很简略呢?

用法

首要咱们要在iOS或Mac项目敞开iCloud Key Value Storge 服务

手撸一个Flutter插件实现跨苹果全家桶云同步持久化Key Value数据

然后导入icloud_kv_storage享用它吧~


flutter pub add icloud_kv_storage
dependencies:
icloud_kv_storage: ^0.0.1

简略写法如下


import 'package:icloud_kv_storage/icloud_kv_storage.dart';
var iCloudStorage = CKKVStorage();

Update A Key


void _incrementCounter() {
setState(() {
_counter++;
iCloudStorage.writeString(key: key, value: _counter.toString());
});
}

Read A Key


iCloudStorage.getString('k_storage_count').then((value) {
if (value != null) {
setState(() {
_counter = int.parse(value);
});
}
});

Delete A Key


void _clearCounter() {
setState(() {
_counter = 0;
iCloudStorage.delete(key);
});
}

实时改写CallBack


iCloudStorage.onCloudKitKVUpdateCallBack(
onCallBack: (kvMap) {
print('receive icloud_key_update map $kvMap');
//if receive remove key will rec {flutter.k_storage_count: null}
//if receive update key will rec {flutter.k_storage_count: 1}
//because have prefix flutter. so need use my method to get real key.
var key = iCloudStorage.getRealKey('k_storage_count');
if (kvMap.containsKey(key)) {
String? value = kvMap[key];
setState(() {
if (value != null) {
_counter =
int.parse(kvMap[iCloudStorage.getRealKey('k_storage_count')]);
} else {
_counter = 0;
}
});
}
},
);

下载地址

Github

icloud_kv_storage

flutter pub

icloud_kv_storage

结束

由所以1.0版本,十分肝。现在只支撑同步String类型的数据,后续其他根底类型数据也会更新上。假如这个库对你有用,那就Star一个吧,感谢