zoukankan      html  css  js  c++  java
  • WWDC2014之App Extensions学习笔记

    本文转载至 http://www.cocoachina.com/industry/20140627/8960.html

    extension是iOS8新开放的一种对几个固定系统区域的扩展机制,它可以在一定程度上弥补iOS的沙盒机制对应用间通信的限制。

     

    转自王中周的技术博客

     
    一、关于App Extensions
     
    extension是iOS8新开放的一种对几个固定系统区域的扩展机制,它可以在一定程度上弥补iOS的沙盒机制对应用间通信的限制。
     
    extension的出现,为用户提供了在其它应用中使用我们应用提供的服务的便捷方式,比如用户可以在Today的widgets中查看应用展示的简略信息,而不用再进到我们的应用中,这将是一种全新的用户体验;但是,extension的出现可能会减少用户启动应用的次数,同时还会增大开发者的工作量。
     
    几个关键词
     
    extension point
    系统中支持extension的区域,extension的类别也是据此区分的,iOS上共有Today、Share、Action、Photo Editing、Storage Provider、Custom keyboard几种,其中Today中的extension又被称为widget。
     
    每种extension point的使用方式和适合干的活都不一样,因此不存在通用的extension。
     
    app extension
    即为本文所说的extension。extension并不是一个独立的app,它有一个包含在app bundle中的独立bundle,extension的bundle后缀名是.appex。其生命周期也和普通app不同,这些后文将会详述。
     
    extension不能单独存在,必须有一个包含它的containing app。
     
    另外,extension需要用户手动激活,不同的extension激活方式也不同,比如: 比如Today中的widget需要在Today中激活和关闭;Custom keyboard需要在设置中进行相关设置;Photo Editing需要在使用照片时在照片管理器中激活或关闭;Storage Provider可以在选择文件时出现;Share和Action可以在任何应用里被激活,但前提是开发者需要设置Activation Rules,以确定extension需要在合适出现。
     
    containing app
    尽管苹果开放了extension,但是在iOS中extension并不能单独存在,要想提交到AppStore,必须将extension包含在一个app中提交,并且app的实现部分不能为空,这个包含extension的app就叫containing app。
     
    extension会随着containing app的安装而安装,同时随着containing app的卸载而卸载。
     
    host app
    能够调起extension的app被称为host app,比如widget的host app就是Today。
     
    二、extension和containing app、host app
     
    2.1 extension和host app
    extension和host app之间可以通过extensionContext属性直接通信,该属性是新增加的UIViewController类别:
    1. @interface UIViewController(NSExtensionAdditions) <NSExtensionRequestHandling> 
    2.  
    3. // Returns the extension context. Also acts as a convenience method for a view controller to check if it participating in an extension request. 
    4. @property (nonatomic,readonly,retain) NSExtensionContext *extensionContext NS_AVAILABLE_IOS(8_0); 
    5.  
    6. @end 
    实际上extension和host app之间是通过IPC(interprocess communication)实现的,只是苹果把调用接口高度抽象了,我们并不需要关注那么底层的东西。
     
    2.2 containing app和host app
    他们之间没有任何直接关系,也从来不需要通信。
     
    2.3 extension和containing app
    这二者之间的关系最复杂,纠纠缠缠扯不清关系。
     
    不能直接通信
     
    首先,尽管extension的bundle是放在containing app的bundle中,但是他们是两个完全独立的进程,之间不能直接通信。不过extension可以通过openURL的方式启动containing app(当然也能启动其它app),不过必须通过extensionContext借助host app来实现:
    1. //通过openURL的方式启动Containing APP 
    2. - (void)openURLContainingAPP 
    3.     [self.extensionContext openURL:[NSURL URLWithString:@"appextension://123"] 
    4.                  completionHandler:^(BOOL success) { 
    5.                      NSLog(@"open url result:%d",success); 
    6.                  }]; 
    extension中是无法直接使用openURL的。
     
    可以共享Shared resources
     
    extension和containing app可以共同读写一个被称为Shared resources的存储区域,这是通过App Groups实现的,后文将会详述。
     
    三者间的关系可以通过官网给的两张图片形象地说明:
     
    containing app能够控制extension的出现和隐藏
    通过以下代码,containing app可以让extension出现或隐藏(当然extension也可以让自己隐藏):
    1. //让隐藏的插件重新显示 
    2. - (void)showTodayExtension 
    3.     [[NCWidgetController widgetController] setHasContent:YES forWidgetWithBundleIdentifier:@"com.wangzz.app.extension"]; 
    4.  
    5. //隐藏插件 
    6. - (void)hiddeTodayExtension 
    7.     [[NCWidgetController widgetController] setHasContent:NO forWidgetWithBundleIdentifier:@"com.wangzz.app.extension"]; 
     
    三、App Groups
     
    这是iOS8新开放的功能,在OS X上早就可用了。它主要用于同一group下的app共享同一份读写空间,以实现数据共享。
     
    extension和containing app共同读写一份数据是很合理的需求,比如系统的股市应用,widget和app中都需要展示几个公司的股票数据,这就可以通过App Groups实现。
     
    3.1 功能开启
     
    为了便于后续操作,请先确保你的开发者账号在Xcode上处于登录状态。
     
    在app中开启
    App Groups位于:
    1. TARGETS-->AppExtensionDemo-->Capabilities-->App Groups 
    找到以后,将App Groups右上角的开关打开,然后选择添加groups,比如我的是group.wangzz,当然这是为了测试随便起得名字,正规点得命名规则应该是:group.com.company.app。
     
    添加成功以后如下图所示:
    在extension中开启
    我创建的是widget,target名称为TodayExtension,对应的App Groups位于:
    1. TARGETS-->TodayExtension-->Capabilities-->App Groups 
    开启方式和app中一样,需要注意的是必须保证这里地App Groups名称和app中的相同,即为group.wangzz。
     
    四、extension和containing app数据共享
     
    App Groups给我们提供了同一group内app可以共同读写的区域,可以通过以下方式实现数据共享:
     
    4.1 通过NSUserDefaults共享数据
     
    存数据
    通过以下方式向NSUserDefaults中保存数据:
    1. - (void)saveTextByNSUserDefaults 
    2.     NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"]; 
    3.     [shared setObject:_textField.text forKey:@"wangzz"]; 
    4.     [shared synchronize]; 
    需要注意的是:
     
    1.保存数据的时候必须指明group id;
     
    2.而且要注意NSUserDefaults能够处理的数据只能是可plist化的对象,详情见Property List Programming Guide。
     
    3.为了防止出现数据同步问题,不要忘记调用[shared synchronize];
     
    读数据
    对应的读取数据方式:
    1. - (NSString *)readDataFromNSUserDefaults 
    2.     NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"]; 
    3.     NSString *value = [shared valueForKey:@"wangzz"]; 
    4.  
    5.     return value; 
     
    4.2 通过NSFileManager共享数据
     
    NSFileManager在iOS7提供了containerURLForSecurityApplicationGroupIdentifier方法,可以用来实现app group共享数据。
     
    保存数据
    1. - (BOOL)saveTextByNSFileManager 
    2.     NSError *err = nil; 
    3.     NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; 
    4.     containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"]; 
    5.  
    6.     NSString *value = _textField.text; 
    7.     BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&err]; 
    8.     if (!result) { 
    9.         NSLog(@"%@",err); 
    10.     } else { 
    11.         NSLog(@"save value:%@ success.",value); 
    12.     } 
    13.  
    14.     return result; 
     
    读数据
    1. - (NSString *)readTextByNSFileManager 
    2.     NSError *err = nil; 
    3.     NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; 
    4.     containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"]; 
    5.     NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&err]; 
    6.  
    7.     return value; 
     
    在这里我试着保存和读取的是字符串数据,但读写SQlite我相信也是没问题的。
     
    数据同步
    两个应用共同读取同一份数据,就会引发数据同步问题。WWDC2014的视频中建议使用NSFileCoordination实现普通文件的读写同步,而数据库可以使用CoreData,Sqlite也支持同步。
     
    五、extension和containing app代码共享
     
    和数据共享类似,extension和containing app很自然地会有一些业务逻辑上可以共用的代码,这时可以通过iOS8中刚开放使用的framework实现。苹果在App Extension Programming Guide中是这样描述的:
     
    In iOS 8.0 and later, you can use an embedded framework to share code between your extension and its containing app. For example, if you develop image-processing code that you want both your Photo Editing extension and its containing app to share, you can put the code into a framework and embed it in both targets.
     
    即将framework分别嵌入到extension和containing app的target中实现代码共享。但这样岂不是需要分别要将framework分别copy到extension和containing app的main bundle中?
     
    参考extension和containing app数据共享,我试想能不能将framework只保存一份放在App Groups区域?
     
    5.1 copy framework到App Groups
     
    在app首次启动的时候将framework放到App Groups区域:
    1. - (BOOL)copyFrameworkFromMainBundleToAppGroup 
    2.     NSFileManager *manager = [NSFileManager defaultManager]; 
    3.     NSError *err = nil; 
    4.     NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; 
    5.     NSString *sorPath = [NSString stringWithFormat:@"%@/Dylib.framework",[[NSBundle mainBundle] bundlePath]]; 
    6.     NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path]; 
    7.  
    8.     BOOL removeResult = [manager removeItemAtPath:desPath error:&err]; 
    9.     if (!removeResult) { 
    10.         NSLog(@"%@",err); 
    11.     } else { 
    12.         NSLog(@"remove success."); 
    13.     } 
    14.  
    15.     BOOL copyResult = [[NSFileManager defaultManager] copyItemAtPath:sorPath toPath:desPath error:&err]; 
    16.     if (!copyResult) { 
    17.         NSLog(@"%@",err); 
    18.     } else { 
    19.         NSLog(@"copy success."); 
    20.     } 
    21.  
    22.     return copyResult; 
     
    5.2 使用framework:
    1. - (BOOL)loadFrameworkInAppGroup 
    2.     NSError *err = nil; 
    3.     NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; 
    4.     NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path]; 
    5.     NSBundle *bundle = [NSBundle bundleWithPath:desPath]; 
    6.     BOOL result = [bundle loadAndReturnError:&err]; 
    7.     if (result) { 
    8.         Class root = NSClassFromString(@"Person"); 
    9.         if (root) { 
    10.             Person *person = [[root alloc] init]; 
    11.             if (person) { 
    12.                 [person run]; 
    13.             } 
    14.         } 
    15.     } else { 
    16.         NSLog(@"%@",err); 
    17.     } 
    18.  
    19.     return result; 
     
    经过测试,竟然能够加载成功。
     
    需要说明的是,这里只是说那么用是可以成功加载framework,但还面临不少问题,比如如果用户在启动app之前去使用extension,这时framework还没有copy过去,怎么处理;另外iOS的机制或者苹果的审核是否允许这样使用等。
     
    在一切确定下来之前还是乖乖按文档中的方式使用吧。
     
    六、生命周期
     
    extension和普通app的最大区别之一是生命周期。
     
    开始
    在用户通过host app点击extension时,系统就会实例化extension应用,这是生命周期的开始。
     
    执行任务
    在extension启动以后,开始执行它的使命。
     
    终止
    在用户取消任务,或者任务执行结束,或者开启了一个长时后台任务时,系统会将其杀掉。
     
    由此可见,extension就是为了任务而生!
     
    下图来自官方文档,它将生命周期划分的更详细:
     
     
    通过打印日志发现,Today中的widget在将Today切换到全部或者未读通知时都会被杀掉。
     
    七、 调试
     
    extension和普通app的调试方式差不多,开始调试前先选中extension对应的target,点击run,就会弹出下图所示选择框:
     
     
    需要选择一个host app,这里选择Today。
     
    然后即可和普通app一样调试了,不过我在实际使用过程中,发现有各种奇怪的事情,比如NSLog无法在控制台输出,应该是bug吧。
     
    八、 iOS8应用文件系统
     
    发现iOS8的文件系统发生了变化,新的文件系统将可执行文件(即原来的.app文件)从沙盒中移到了另外一个地方,这样感觉更合理。
     
    测试代码
    下述代码用于打印App Groups路径、应用的可执行文件路径、对应的Documents路径:
    1. - (void)logAppPath 
    2.     //app group路径 
    3.     NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; 
    4.     NSLog(@"app group: %@",containerURL.path); 
    5.  
    6.     //打印可执行文件路径 
    7.     NSLog(@"bundle: %@",[[NSBundle mainBundle] bundlePath]); 
    8.  
    9.     //打印documents 
    10.     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    11.     NSString *path = [paths objectAtIndex:0]; 
    12.     NSLog(@"documents: %@",path); 
     
    containing app执行结果
    1. 2014-06-23 19:35:03.944 AppExtensionDemo[7471:365131] app group: 
    2. /private/var/mobile/Containers/Shared/AppGroup/89CCBFB1-CA5E-4C7F-80CB-A3EB9E841816 
    3. 2014-06-23 19:35:03.946 AppExtensionDemo[7471:365131] bundle: 
    4. /private/var/mobile/Containers/Bundle/Application/1AC73797-A3BB-4BDE-A647-3D083DA6871A/AppExtensionDemo.app 
    5. 2014-06-23 19:35:03.948 AppExtensionDemo[7471:365131] documents: 
    6. /var/mobile/Containers/Data/Application/E5E6E516-0163-4754-9D10-A5F6C33A6261/Documents 
     
    extension执行结果
    1. Jun 23 19:37:49 autonavis-iPad com.foogry.AppExtensionDemo.TodayExtension[7638] <Warning>: app group: 
    2.   /private/var/mobile/Containers/Shared/AppGroup/89CCBFB1-CA5E-4C7F-80CB-A3EB9E841816 
    3. Jun 23 19:37:49 autonavis-iPad com.foogry.AppExtensionDemo.TodayExtension[7638] <Warning>: bundle: 
    4.   /private/var/mobile/Containers/Bundle/Application/596717B7-7CB8-4F53-BCD4-380F34ABD30F/AppExtensionDemo.app/PlugIns/com.foogry.AppExtensionDemo.TodayExtension.appex 
    5. Jun 23 19:37:49 autonavis-iPad com.foogry.AppExtensionDemo.TodayExtension[7638] <Warning>: documents: 
    6.   /var/mobile/Containers/Data/PluginKitPlugin/57581433-3DBD-4930-971F-78D30C150E8A/Documents 
     
    由此可见,不管是extension还是containing app,他们的可执行文件和保存数据的目录都是分开存放的,即所有app的可执行文件都放在一个大目录下,保存数据的目录保存在另一个大目录下,同样,AppGroup放在另一个大目录下。
     
    说明
     
    本文用到的demo已经上传到github上。
     
    文中可能有理解有误的地方,还请指出。
     
    参考文档
     
     
     
     
     
     
     
     
  • 相关阅读:
    P1144 最短路计数 题解 最短路应用题
    C++高精度加减乘除模板
    HDU3746 Teacher YYF 题解 KMP算法
    POJ3080 Blue Jeans 题解 KMP算法
    POJ2185 Milking Grid 题解 KMP算法
    POJ2752 Seek the Name, Seek the Fame 题解 KMP算法
    POJ2406 Power Strings 题解 KMP算法
    HDU2087 剪花布条 题解 KMP算法
    eclipse创建maven项目(详细)
    maven的作用及优势
  • 原文地址:https://www.cnblogs.com/Camier-myNiuer/p/4415148.html
Copyright © 2011-2022 走看看