• 优质范文
  • 工作总结
  • 工作计划
  • 作文大全
  • 心得体会
  • 述职报告
  • 实习报告
  • 写作方案
  • 教案反思
  • 演讲稿
  • 发言稿
  • 读书笔记
  • 精美散文
  • 读观后感
  • 当前位置: 博通范文网 > 发言稿 > 正文

    [App与Extensions间通信共享数据]共享单车App

    时间:2018-08-16 来源:博通范文网 本文已影响 博通范文网手机站

    最近玩了玩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也是对应的:

    App与Extensions间通信共享数据1

    更多的转换规则,在上面提到的官方文档有很详细的描述,这里只说三个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类型的参数传值。

    总结

    来个效果图:

    App与Extensions间通信共享数据2

    这是我第一次写Watch的App(废话谁不是第一次),经验并不是很多,也因为Swift1.2还未正式发布,遇到了一些坑。好歹最后克服了,但也丢了贞操(毕竟不是纯Swift的App了)。有不对的地方还请多多指教。随着Swift的不断完善,希望以后能够支持创建CFunctionPointer对象,这样它好我也好。

    推荐访问:通信 共享 数据 App与Extensions间通信共享数据 appstore账号共享 appstore收入数据

    • 读/观后感
    • 精美散文
    • 读书笔记
    • 演讲
    • 反思
    • 方案
    • 心得体会