iOS 利用 framework 进行动态更新

前言

目前 iOS 上的动态更新方案主要有以下 4 种:

  • HTML 5
  • lua(wax)hotpatch
  • react native
  • framework

前面三种都是通过在应用内搭建一个运行环境来实现动态更新(HTML 5 是原生支持),在用户体验、与系统交互上有一定的限制,对开发者的要求也更高(至少得熟悉 lua 或者 js)。

使用 framework 的方式来更新可以不依赖第三方库,使用原生的 OC/Swift 来开发,体验更好,开发成本也更低。

由于 Apple 不希望开发者绕过 App Store 来更新 app,因此只有对于不需要上架的应用,才能以 framework 的方式实现 app 的更新。

主要思路

将 app 中的某个模块(比如一个 tab)的内容独立成一个 framework 的形式动态加载,在 app 的 main bundle 中,当 app 启动时从服务器上下载新版本的 framework 并加载即可达到动态更新的目的。代码放在了这里

实战

创建一个普通工程 DynamicUpdateDemo,其包含一个 framework 子工程 Module。也可以将 Module 创建为独立的工程,创建工程的过程不再赘述。

依赖

在主工程的 Build Phases > Target Dependencies 中添加 Module,并且添加一个 New Copy Files Phase。

这样,打包时会将生成的 Module.framework 添加到 main bundle 的根目录下。

加载

主要的代码如下:

- (UIViewController )loadFrameworkNamed:(NSString )bundleName {
    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = nil;
    if ([paths count] != 0) {
        documentDirectory = [paths objectAtIndex:0];
    }

    NSFileManager *manager = [NSFileManager defaultManager];
    NSString *bundlePath = [documentDirectory stringByAppendingPathComponent:[bundleName stringByAppendingString:@".framework"]];

    // Check if new bundle exists
    if (![manager fileExistsAtPath:bundlePath]) {
        NSLog(@"No framework update");
        bundlePath = [[NSBundle mainBundle]
                      pathForResource:bundleName ofType:@"framework"];

        // Check if default bundle exists
        if (![manager fileExistsAtPath:bundlePath]) {
            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Oooops" message:@"Framework not found" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
            [alertView show];
            return nil;
        }
    }

    // Load bundle
    NSError *error = nil;
    NSBundle *frameworkBundle = [NSBundle bundleWithPath:bundlePath];
    if (frameworkBundle && [frameworkBundle loadAndReturnError:&error]) {
        NSLog(@"Load framework successfully");
    }else {
        NSLog(@"Failed to load framework with err: %@",error);
        return nil;
    }

    // Load class
    Class PublicAPIClass = NSClassFromString(@"PublicAPI");
    if (!PublicAPIClass) {
        NSLog(@"Unable to load class");
        return nil;
    }

    NSObject *publicAPIObject = [PublicAPIClass new];
    return [publicAPIObject performSelector:@selector(mainViewController)];
}

代码先尝试在 Document 目录下寻找更新后的 framework,如果没有找到,再在 main bundle 中寻找默认的 framework。
其中的关键是利用 OC 的动态特性 NSClassFromStringperformSelector 加载 framework 的类并且执行其方法。

framework 和 host 工程资源共用

第三方库

Class XXX is implemented in both XXX and XXX. One of the two will be used. Which one is undefined.

这是当 framework 工程和 host 工程链接了相同的第三方库或者类造成的。

为了让打出的 framework 中不包含 host 工程中已包含的三方库(如 cocoapods 工程编译出的 .a 文件),可以这样:

  • 删除 Build Phases > Link Binary With Libraries 中的内容(如有)。此时编译会提示三方库中包含的符号找不到。
  • 在 framework 的 Build Settings > Other Linker Flags 添加 -undefined dynamic_lookup。**必须保证 host 工程编译出的二进制文件中包含这些符号。**

类文件

尝试过在 framework 中引用 host 工程中已有的文件,通过 Build Settings > Header Search Paths 中添加相应的目录,Xcode 在编译的时候可以成功(因为添加了 -undefined dynamic_lookup),并且 Debug 版本是可以正常运行的,但是 Release 版本动态加载时会提示找不到符号:

Error Domain=NSCocoaErrorDomain Code=3588 "The bundle “YourFramework” couldn’t be loaded." (dlopen(/var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework, 265): Symbol not found: _OBJC_CLASS_$_BorderedView
      Referenced from: /var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework
      Expected in: flat namespace
     in /var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework) UserInfo=0x174276900 {NSLocalizedFailureReason=The bundle couldn’t be loaded., NSLocalizedRecoverySuggestion=Try reinstalling the bundle., NSFilePath=/var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework, NSDebugDescription=dlopen(/var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework, 265): Symbol not found: _OBJC_CLASS_$_BorderedView
      Referenced from: /var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework
      Expected in: flat namespace
     in /var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework, NSBundlePath=/var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework, NSLocalizedDescription=The bundle “YourFramework” couldn’t be loaded.}

因为 Debug 版本暴露了所有自定义类的符号以便于调试,因此你的 framework 可以找到相应的符号,而 Release 版本则不会。

目前能想到的方法只有将相同的文件拷贝一份到 framework 工程里,并且更改类名。

访问 framework 中的图片

在 storyboard/xib 中可以直接访问图片,代码中访问的方法如下:

UIImage *image = [UIImage imageNamed:@"YourFramework.framework/imageName"]

注意:使用代码方式访问的图片不可以放在 xcassets 中,否则得到的将是 nil。并且文件名必须以 @2x/@3x 结尾,大小写敏感。因为 imageNamed: 默认在 main bundle 中查找图片。

常见错误

Architecture

dlopen(/path/to/framework, 9): no suitable image found.  Did find:
/path/to/framework: mach-o, but wrong architecture

这是说 framework 不支持当前机器的架构。
通过

lipo -info /path/to/MyFramework.framework/MyFramework

可以查看 framework 支持的 CPU 架构。

碰到这种错误,一般是因为编译 framework 的时候,scheme 选择的是模拟器,应该选择iOS Device

此外,如果没有选择iOS Device,编译完成后,Products 目录下的 .framework 文件名会一直是红色,只有在 Derived Data 目录下才能找到编译生成的 .framework 文件。

签名

系统在加载动态库时,会检查 framework 的签名,签名中必须包含 TeamIdentifier 并且 framework 和 host app 的 TeamIdentifier 必须一致

如果不一致,否则会报下面的错误:

Error loading /path/to/framework: dlopen(/path/to/framework, 265): no suitable image found. Did find:
/path/to/framework: mmap() error 1

此外,如果用来打包的证书是 iOS 8 发布之前生成的,则打出的包验证的时候会没有 TeamIdentifier 这一项。这时在加载 framework 的时候会报下面的错误:

[deny-mmap] mapped file has no team identifier and is not a platform binary:
/private/var/mobile/Containers/Bundle/Application/5D8FB2F7-1083-4564-94B2-0CB7DC75C9D1/YourAppNameHere.app/Frameworks/YourFramework.framework/YourFramework

可以通过 codesign 命令来验证。

codesign -dv /path/to/YourApp.app

如果证书太旧,输出的结果如下:

Executable=/path/to/YourApp.app/YourApp
Identifier=com.company.yourapp
Format=bundle with Mach-O thin (armv7)
CodeDirectory v=20100 size=221748 flags=0x0(none) hashes=11079+5 location=embedded
Signature size=4321
Signed Time=2015年10月21日 上午10:18:37
Info.plist entries=42
TeamIdentifier=not set
Sealed Resources version=2 rules=12 files=2451
Internal requirements count=1 size=188

注意其中的 TeamIdentifier=not set

采用 swift 加载 libswiftCore.dylib 这个动态库的时候也会遇到这个问题,对此Apple 官方的解释是:

To correct this problem, you will need to sign your app using code signing certificates with the Subject Organizational Unit (OU) set to your Team ID. All Enterprise and standard iOS developer certificates that are created after iOS 8 was released have the new Team ID field in the proper place to allow Swift language apps to run.

If you are an in-house Enterprise developer you will need to be careful that you do not revoke a distribution certificate that was used to sign an app any one of your Enterprise employees is still using as any apps that were signed with that enterprise distribution certificate will stop working immediately.

只能通过重新生成证书来解决这个问题。但是 revoke 旧的证书会使所有用户已经安装的,用该证书打包的 app 无法运行。

等等,我们就跪在这里了吗?!

现在企业证书的有效期是三年,当证书过期时,其打包的应用就不能运行,那企业应用怎么来更替证书呢?

Apple 为每个账号提供了两个证书,这两个证书可以同时生效,这样在正在使用的证书过期之前,可以使用另外一个证书打包发布,让用户升级到新版本。

也就是说,可以使用另外一个证书来打包应用,并且可以覆盖安装使用旧证书打包的应用。详情可以看 Apple 文档

You are responsible for managing your team’s certificates and provisioning profiles. Apple Developer Enterprise Program certificates expire after three years and provisioning profiles expire after one year.

Before a distribution certificate expires, create an additional distribution certificate, described in Creating Additional Enterprise Distribution Certificates. You cannot renew an expired certificate. Instead, replace the expired certificate with the new certificate, described in Replacing Expired Certificates.

If a distribution provisioning profile expires, verify that you have a valid distribution certificate and renew the provisioning profile, described in Renewing Expired Provisioning Profiles.

参考

http://devguo.com/blog/2015/06/16/iosdong-tai-geng-xin-frameworkshi-xian/

http://stackoverflow.com/questions/25909870/xcode-6-and-embedded-frameworks-only-supported-in-ios8

http://blog.csdn.net/like7xiaoben/article/details/44081257

https://www.apperian.com/mam-blog/impact-ios-8-app-wrapping/

http://stackoverflow.com/questions/9216485/how-to-manage-enterprise-distribution-certificate-expiration

时间: 2024-10-27 23:56:38

iOS 利用 framework 进行动态更新的相关文章

iOS中动态更新补丁策略JSPatch运用基础二

iOS中动态更新补丁策略JSPatch运用基础二 一.引言     上篇博客中介绍了iOS开发中JSPatch引擎进行动态热修复的一些基础功能,其中包括向Objective-C类中添加类方法与成员方法.添加临时成员变量,使用JavaScript调用原生的Objective-C属性和方法等.本篇博客将基于上一篇继续介绍Objective-C中的一些特殊数据类型在JavaScript文件中的使用方法,博客中大部分内容扩展自JSPatch开源git的wiki:https://github.com/ba

iOS中动态更新补丁策略JSPatch运用基础一

iOS中动态更新补丁策略JSPatch运用基础         JSPatch是GitHub上一个开源的框架,其可以通过Objective-C的run-time机制动态的使用JavaScript调用与替换项目中的Objective-C属性与方法.其框架小巧,代码简洁,并且通过系统的JavaScriptCore框架与Objective-C进行交互,这使其在安全性和审核风险上都有很强的优势.Git源码地址:https://github.com/bang590/JSPatch. 一.从一个官方的小de

iOS使用lua语言的使用步骤与实现插件的动态更新

一:lua使用步骤:(可参考http://www.duote.com/tech/ios/19919_2.html#contentbody)用Xcode创建项目 我们先创建一个新项目 通过Finder浏览到你保存该项目的文件夹.创建三个新的文件夹:wax.scripts和Classes.你的文件夹看起来应该像这样: 通过Finder浏览到你保存该项目的文件夹 设置Wax(第一部分,处理文件) 首先,下载源代码的压缩包.Wax放在GitHub上(https://github.com/probably

iOS中 Framework静态库的创建和使用遇到的那些坑 韩俊强的博客

前言 网上关于Framework制作的教程数不胜数,然而都过于陈旧,最新的也是使用Xcode7的教程,而且有些设置也只给出步骤,并没有给出原因,而且按照有些教程制作出的framework还有些问题,所以我把自己制作framework的过程记录下来,并且使用的是最新的Xcode8环境.本次制作framework,包含AFN,FMDB第三方,.a文件,xib,Bundle文件,还有Category分类,几乎制作和使用framework遇到的所有坑都被我遇到了,所以,此篇博客在我这属于干货,特此分享给

通过XML数据交换实现XForm DataInstance的动态更新

XForms 是下一代 Web 表单的数据处理技术,它通过 Data Instance(数据实例),定义表单上所有和后端应用关联的数据信息,实现各种数据处理,实现了 MVC 中的数据 Modle 与 View 和 Controller 的清晰分离.本文首先 IBM Lotus Forms 进行简单介绍,然后结合作者的项目开发经验,提出了 XForms 在实际表单应用中的一个典型的动态数据交换的应用场景.针对该应用场景,将由浅入深的为大家介绍通过 XForm 的 XML 数据交换,实现 DataI

JVM TI学习(2) 如何动态更新JVM中的class文件

在一个运营系统中,如果出现业务方法变更,而我们使用的应用服务器不支持热部署的话,那么重启可能是更新的唯一选择.目前多数应用服务器不支持热部署,包括生产模式下的weblogic.之所以说是生产模式,weblogic在开发模式下是支持这种动态更新的,即我们只要替换部署目录下的类文件,重新访问时可以看到新业务方法生效,而且即使在生产模式下,weblogic也能"支持"动态更新,但做法上比较麻烦,需要使用version信息控制应用,这个功能weblogic9就开始提供,但好像很少有客户这么用过

iOS制作framework静态库图文教程_IOS

本文实例为大家分享了iOS制作framework静态库教程 ,供大家参考,具体内容如下 环境: 硬件:macbook air 系统:OSX EI Capitan 版本:10.11.3 xcode :Version 7.2.1 (7C1002) 最近在做ios的静态库(据说framework动态库不能上传到app store).a 和framework都做过了,这里就先说framework的制作流程: 1.首先在xcode下新建cocoa touch framework工程: 2.新建好工程后,往

ios开发 蓝牙通信-ios利用如何利用蓝牙通信

问题描述 ios利用如何利用蓝牙通信 ios利用如何利用蓝牙通信, 现在在研究蓝牙通信的问题,利用哪个framework:corebluetooth还是其他的?大家有推荐的文档.demo等? 急!!! 解决方案 可以看看这个demohttp://m.blog.csdn.net/article/details?plg_nld=1&id=51014318&plg_auth=1&plg_uin=1&plg_usr=1&plg_vkey=1&plg_nld=1&am

全自动:Windows 2000 中的 DNS 动态更新 (转)

全自动:Windows 2000 中的 DNS 动态更新 域名系统 (DNS) 为域名(比如 ftp.microsoft.com)与相应 IP 地址或多个地址的解析提供了一个方法.DNS 是一个可缩放的分布式资源记录数据库,可为最大的网络(如 Internet)提供名称解析.在 DNS 中,A(地址)资源记录提供名称对 IP 地址的解析,PRT(指针)资源记录提供 IP 地址对名称的解析.但是,最早设计 DNS 时所有运行 TCP/IP 的计算机都是手工配置的.用特定的 IP 地址手工配置一台计