NSUserDefaults
NSUserDefaults是快速共享信息的途径。它适合存储各种快速访问和计算的小型数据,比如用户名与档案信息。如果希望使用UserDefaults,请用于静态数据这样用户不必考虑数值的变化。
你需要设定App Group来让设备通过共享容器来实现数据共享,确保手表扩展和ios target都已如此设置。基本上就是针对两个设备创建一个统一的App Group标识符。如果需要删除它,可以以类似的方法进行。
你可以通过之前创建的App Group名来使用defaults,基本上就是为特定的key键值设置对象。在iPhone上,用户输入了文本,保存,文本就存到了应用共享的UserDefaults里。在Watch上,你可以从AppGroup得到defaults然后进行手表显示内容的更新。
- // on the iPhone app
- letdefaults=NSUserDefaults(suiteName:"group.com.natashatherobot.userdefaults")
- letkey="userInput"
- overridefuncviewDidLoad(){
- super.viewDidLoad()
- textLabel.text=defaults?.stringForKey(key)??"Type Something..."
- }
- @IBActionfunconSaveTap(sender:AnyObject){
- letsharedText=textField.text
- textLabel.text=sharedText
- defaults?.setObject(sharedText,forKey:key)
- defaults?.synchronize()
- }
- // WatchKit
- classInterfaceController:WKInterfaceController{
- @IBOutletweakvartextLabel:WKInterfaceLabel!
- letdefaults=NSUserDefaults(suiteName:
- "group.com.natashatherobot.userdefaults")
- varuserInput:String?{
- defaults?.synchronize()
- returndefaults?.stringForKey("userInput")
- }
NSFileCoordinator
对更大型的数据来说,NSFileCoordinator是管理应用和watch扩展的共享空间里文件的方式之一。对于有限列表的内容它很合适,同时也适用于图像文件。
下面的例子是个简单的代办事项列表app,在手机上增加任务然后暑假传输到WatchKit扩展并在手表上显示。你的视图控制器需要遵循NSFilePresenter协议,除了实现两个必需方法,其它不是很关键。FilePresenter协议有一个item URL,就是填你的AppGroup标识符的地方。通过URL,你在对应目录建立一个文件。有必要的话你也可以通过操作队列来控制多线程访问。
另外,presentedItemDidChange这个代理方法,在FilePresenter里通知你是否一个对象发生了改变,来让你更新app数据而无需用户手动刷新。
然而这里还是有个关于NSFileCoordinator与NSFilePresenter 的bug而不方便在扩展里使用。具体可参见Natasha的网站。
在代办事项数组里利用FileCoordinator写入一个文件,可以通过读写文件以实现打包和解包事项的数据到事项数组,接下来可以依据文件里的事项数据计算生成表格。需要注意的是如果你设计了删除功能,而watch扩展和iPhone应用都能修改文件,会遇到线程同步的麻烦。
- // iPhone app
- privatefuncsaveTodoItem(todoItem:String){
- // write item into the todo items array
- ifletpresentedItemURL=presentedItemURL{
- fileCoordinator.coordinateWritingItemAtURL(presentedItemURL,options:nil,error:nil)
- {[unownedself](newURL)->Voidin
- self.todoItems.insert(todoItem,atIndex:0)
- letdataToSave=NSKeyedArchiver.archivedDataWithRootObject(self.todoItems)
- letsuccess=dataToSave.writeToURL(newURL,atomically:true)
- }
- }
- }
- // in the Watch
- // MARK: Populate Table From File Coordinator
- privatefuncfetchTodoItems(){
- letfileCoordinator=NSFileCoordinator()
- ifletpresentedItemURL=presentedItemURL{
- fileCoordinator.coordinateReadingItemAtURL(presentedItemURL,options:nil,error:nil)
- {[unownedself](newURL)->Voidin
- ifletsavedData=NSData(contentsOfURL:newURL){
- self.todoItems=NSKeyedUnarchiver.unarchiveObjectWithData(savedData)as[String]
- self.populateTableWithTodoItems(self.todoItems)
- }
- }
- }
- }
Frameworks
“If the code appears more than once, it probably belongs in a framework.(如果代码出现超过一次,应该考虑能否放到框架里)”
-WWDC 2014, Building Modern Frameworks
框架对于业务逻辑、CoreData、可重用UI组件来说很棒。就像WWDC里说的那样,你可以将重复代码放到框架里。在FileCoordinator的例子里,我们获取和读写文件的代码出现了两次,可以把它们提取到一个framework框架里。建立框架很简单:建立新target,选择Cocoa Touch framework,然后命名。它会在你的iOS应用里自动链接,因此也不要忘了在WatchKit扩展里进行链接。
关键的一点,特别是对于Swift语言来说,应该把框架认作一个API。它需要声明为公共的(public),因为这是iOS应用和watchkit扩展共用的接口。因此如果你在建立对象类,确保public关键字也加上了。这样在手机和手表应用里你导入了框架就可以访问任何公共内容。
- importWatchKit
- importMySharedDataLayer
- classInterfaceController:WKInterfaceController{
- @IBOutletweakvarfavoriteThingsLabel:WKInterfaceLabel!
- overridefuncawakeWithContext(context:AnyObject?){
- super.awakeWithContext(context)
- letmyFavoriteThings=MySharedData().myFavoriteThings
- letfavoriteThingsString=", ".join(myFavoriteThings)
- favoriteThingsLabel.setText("My favorite things are \(favoriteThingsString)")
- }
- }
Keychain Sharing
钥匙链共享是针对更高安全性要求的数据的。UserDefaults提供的安全性不满足时,你可以用钥匙链共享来保障信息安全及跨扩展的共享能力。
WatchKit目前的一个大问题是没有认证机制。苹果提供了KeychainIteamWrapper的示例,但API太老不支持ARC。我推荐使用这个版本https://gist.github.com/dhoerl/1170641,它基于ARC并有清晰的接口。
根据问题是如何通过access group初始化KeychainItemWrapper。与AppGroup的概念类似,设备之间有共享空间。你在iOS和WatchKit扩展中都需要钥匙链来访问用户数据。通过键值存储体系,你设定用户名和密码并用同一个标识符建立同一类型的keychain项。这个例子里仅展示了当用户填好用户名密码时WatchKit扩展展示数据的这一工作过程。
- // iPhone app
- @IBActionfunconSaveTap(sender:AnyObject){
- letusername=usernameTextField.text
- letpassword=passwordTextField.text
- letkeychainItem=KeychainItemWrapper(identifier:"SharingViaKeychain",accessGroup:"W6GNU64U6Q.com.natashatherobot.SharingViaKeychain")
- // WatchKit extension
- letkeychainItem=KeychainItemWrapper(identifier:"SharingViaKeychain",accessGroup:"W6GNU64U6Q.com.natashatherobot.SharingViaKeychain")
- letpasswordData=keychainItem.objectForKey(kSecValueData)asNSData
- letpassword=NSString(data:passwordData,encoding:NSUTF8StringEncoding)
- letusername=keychainItem.objectForKey(kSecAttrAccount)as?String