App與Extensions間通訊共用資料

來源:互聯網
上載者:User

App與Extensions間通訊共用資料

   最近玩了玩Watch開發,而目前Watch的主要邏輯處理都是放在WatchKit Extension。真正的Host App,也就是WatchKit App只是用來在介面上顯示資料的。於是實踐了下containing app與app extension之間的通訊和資料共用。

  App Groups & Framework

  這兩樣兵器大家都很熟悉。想要共用資料就需要開啟App Groups,給group起一個風騷的名字,這樣無論是NSUserDefaults還是NSFileManager都能通過App Groups共用持久層資料了。Core Data也需要NSFileManager提供儲存的URL支援,而存取Core Data中的資料需要大量的模板代碼,在持久層檔案分享權限設定之後,代碼也應該做到共用,所以將能夠重用的代碼打包成Framework就顯得尤為重要。(除非是為了做畢設湊代碼量)

  還是以HardChoice為例,我建立了一個類型為Cocoa Touch Framework的target,名字叫DataKit。建立一個DataAccess.swift檔案並將以前AppDelegate.swift中自動產生的Core Data模版代碼轉移過來。得益於Swift1.2的改進,構造一個安全執行緒的單例模式變得無比簡單:

  private static let instance = DataAccess()

  public class var sharedInstance : DataAccess {

  return instance

  }

  需要注意Swift的許可權控制問題,我們需要在暴漏給架構使用者的公開介面和屬性前加上public關鍵字修飾。

  為了實現Core Data持久層共用,需要修改原先的applicationDocumentsDirectory屬性:

  lazy var applicationDocumentsDirectory: NSURL = {

  // The directory the application uses to store the Core Data store file. This code uses a directory named "com.yxy.iCloudCoreDataTest" in the application's documents Application Support directory.

  // let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)

  // return urls[urls.count-1] as! NSURL

  var sharedContainerURL:NSURL? = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier(appGroupIdentifier)

  return sharedContainerURL ?? NSURL()

  }()

  在這裡containerURLForSecurityApplicationGroupIdentifier方法起到了至關作用。

  同樣能夠共用程式碼就是Model層,它們都是NSManagedObject的子類,用於儲存Core Data中的資料執行個體。在把它們從原來的位置拖拽過來時別忘了更改下檔案的target:”File inspector”->”Target Membership”,選中DataKit。

  在處理iCloud與Core Data同步資料時,我對NSPersistentStoreCoordinatorStoresWillChangeNotification、NSPersistentStoreCoordinatorStoresDidChangeNotification和NSPersistentStoreDidImportUbiquitousContentChangesNotification這三個資料更新的通知進行了觀察和處理,但是寫在了persistentStoreCoordinator計算屬性的get方法中。現在使用lazy關鍵字進行惰性載入,導致對這三個資料更新通知的觀察延後,這會引發嚴重的錯誤。所以需要將那三個addObserverForName(name, object, queue, usingBlock)方法挪到init()方法中,在第一時間觀察通知。

  最後在AppDelegate.swift中添加import DataKit,替換掉中的application(application, didFinishLaunchingWithOptions) -> Bool方法中controller.managedObjectContext = managedObjectContext為controller.managedObjectContext = DataAccess.sharedInstance.managedObjectContext,也就是不再使用以前的模板代碼中的上下文執行個體,而是用DataAccess單例中的managedObjectContext。

  同理,applicationWillTerminate(application)方法中的saveContext()也要替換成DataAccess.sharedInstance.saveContext()。

  於是我們也可以在App Extensions中import進來DataKit,進行地存取Core Data中的資料啦。而且用的是同一段代碼,同一塊資料。簡直是同一個世界,同一個夢想啊。

  Container app 與 Extension的通訊

  要知道之前做的共用資料只能是主動擷取資料,並不能在資料變化時即時擷取通知。如果使用者在iPhone上更改了資料,我們需要在Watch上即時更改介面上資料的顯示。這點NSNotificationCenter是做不到的,因為它只在App內部工作而不會在兩個App之間發通知。同樣KVO也無能為力,自己手寫委託什麼的更是別想了(因為我試過了)。直到我在這篇文章找到了救世主,問題迎刃而解:

  CFNotificationCenterGetDarwinNotifyCenter

  這是CoreFoundation庫中一個系統級的通知中樞,蘋果的系統自己也在用它,看清了”Darwin”了沒有?哈哈!看了下CFNotificationCenter相關的API,跟NSNotificationCenter有點像。需要用到Toll-Bridge的知識與CoreFoundation相關的類進行橋接,這雖不常用但也不難。還需要注意下個別參數的使用。

  MMWormhole

  更有趣的是幾乎同時我也發現了MMWormhole這個開源庫,它專門用於在Container app 與 Extension間傳遞訊息。我讀了下它的代碼,雖然只有一個類,但是依然學到了很多。雖然在我的HardChoice上完全可以只用CFNotificationCenter進行通知就可以了,完全不需要使用MMWormhole來持久化資料和傳遞資料。但我覺得以後還可能會用到MMWormhole,於是我用Swift1.2重新寫了一個Wormhole.swift,放在了DataKit裡。

  Swift與CoreFoundation

  原來OC寫的兩百多行的MMWormhole被我用150行“清新優雅”的Swift代碼取代。之所以打上引號是因為Swift與CoreFoundation之間的橋接有些不愉快。因為CoreFoundation中都是C的API,C中的指標和類型轉換很出格,有安全隱患。Swift是一門安全的語言,但為了調用由曆史原因造成的不安全的C的API,Swift中引入了很多類型來映射C中的類型,參考Interacting with C APIs

  Swift中不用像OC那樣使用__bridge和類型轉換、記憶體管理交接,因為這些全都交給Swift了:如果Swift中存在類型映射到C的API所需的參數類型,那麼可以直接將其傳入API。此外記憶體管理也歸Swift中的ARC統一管理。於是Swift大大簡化了與CoreFoundation打交道的過程。

  我們最關心的是指標,UnsafePointer對應了const CType *,UnsafeMutablePointer對應了CType *。當然SwiftType與CType也是對應的:

  更多的轉換規則,在上面提到的官方文檔有很詳細的描述,這裡只說三個tips:

  在Swift中將self轉成UnsafePointer(也就是const void *)只需用這個函數:unsafeAddressOf(self)

  CoreFoundation庫中尾碼為”Ref”的類在Swift中已經去掉尾碼。

  Swift中函數指標被表示為CFunctionPointer,Type就是函數的類型,但還不允許你將Swift寫的函數或閉包轉化成CFunctionPointer,也就是乾脆沒提供建立CFunctionPointer執行個體的方法,只能通過外部引入C的函數。這就涉及到了Swift與OC混編,請戳Swift and Objective-C in the Same Project

  在Framework中混編OC

  我之所以需要做這種破壞工程純潔性的事兒,是因為要用到下面這個方法來對通知進行觀察:

  1func CFNotificationCenterAddObserver(center: CFNotificationCenter!, observer: UnsafePointer, callBack: CFNotificationCallback, name: CFString!, object: UnsafePointer, suspensionBehavior: CFNotificationSuspensionBehavior)

  除了類型為CFNotificationCallback的參數,其餘的都好說:

  1typealias CFNotificationCallback = CFunctionPointer Void)>

  於是就回到了CFunctionPointer這塊蛋疼地上了,只好在OC裡寫C函數然後調用之:

  static NSString * const WormholeNotificationName = @"WormholeNotificationName";

  @implementation HelpMethod

  - (instancetype)init

  {

  self = [super init];

  if (self) {

  _callback = wormholeNotificationCallback;

  }

  return self;

  }

  void wormholeNotificationCallback(CFNotificationCenterRef center,

  void * observer,

  CFStringRef name,

  void const * object,

  CFDictionaryRef userInfo) {

  NSString *identifier = (__bridge NSString *)name;

  [[NSNotificationCenter defaultCenter] postNotificationName:WormholeNotificationName

  object:nil

  userInfo:@{@"identifier" : identifier}];

  }

  @end

  然後在Swift中這樣寫就可以了:

  1CFNotificationCenterAddObserver(center, unsafeAddressOf(self), helpMethod.callback, identifier, nil, CFNotificationSuspensionBehavior.DeliverImmediately)

  在Swift中使用OC寫的類本來是一件很easy的事兒,但是到了Framework中就變得不尋常。我在DataKit中建立了HelpMethod類,並建立”DataKit-Bridging-Header.h”檔案,將HelpMethod.h標頭檔引入,然後在DataKit target下的”Build Settings” -> “Swift Complier-Code Generation” -> “Objective-C Bridging Header”下填入”DataKit-Bridging-Header.h”,編譯出錯:using bridging headers with framework targets is unsupported。

  在stackoverflow上找到瞭解決方案,於是刪除之前的”DataKit-Bridging-Header.h”檔案並清除”Build Settings”關於Bridging Header的引用;在DataKit.h添加#import "HelpMethod.h",並在HelpMethod.h檔案的 “File inspector”->”Target Membership”中DataKit右側將”project”修改為”public”(否則會出現include of non-modular header inside framework module ‘DataKit’的編譯錯誤)。

  至此,我們可以在HelpMethod類中實現一個函數指標,並在Wormhole.swift檔案中直接使用這個函數指標來為CFunctionPointer類型的參數傳值。

  總結

  來個:

  這是我第一次寫Watch的App(廢話誰不是第一次),經驗並不是很多,也因為Swift1.2還未正式發布,遇到了一些坑。好歹最後克服了,但也丟了貞操(畢竟不是純Swift的App了)。有不對的地方還請多多指教。隨著Swift的不斷完善,希望以後能夠支援建立CFunctionPointer對象,這樣它好我也好。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.