WWDC2014之iOS使用动态库 framework【转】

from:http://www.cocoachina.com/industry/20140613/8810.html

JUN 12TH, 2014

苹果的开放态度

WWDC2014上发布的Xcode6 beta版有了不少更新,其中令我惊讶的一个是苹果在iOS上开放了动态库,在Xcode6 Beta版的更新文档中是这样描述的:

Frameworks for iOS. iOS developers can now create dynamic frameworks. Frameworks are a collection of code and resources to encapsulate functionality that is valuable across multiple projects. Frameworks work perfectly with extensions, sharing logic that can be used by both the main application, and the bundled extensions.

详情见官方文档New Features in Xcode 6 Beta

framework是Cocoa/Cocoa Touch程序中使用的一种资源打包方式,可以将将代码文件、头文件、资源文件、说明文档等集中在一起,方便开发者使用,作为一名Cocoa/Cocoa Touch程序员每天都要跟各种各样的Framework打交道。Cocoa/Cocoa Touch开发框架本身提供了大量的Framework,比如Foundation.framework/UIKit.framework/AppKit.framework等。需要注意的是,这些framework无一例外都是动态库。

但残忍的是,Cocoa Touch上并不允许我们使用自己创建的framework。不过由于framework是一种优秀的资源打包方式,拥有无穷智慧的程序员们便想出了以framework的形式打包静态库的招数,因此我们平时看到的第三方发布的framework无一例外都是静态库,真正的动态库是上不了AppStore的。

WWDC2014给我的一个很大感触是苹果对iOS的开放态度:允许使用动态库、允许第三方键盘、App Extension等等,这些在之前是想都不敢想的事。

iOS上动态库可以做什么

和静态库在编译时和app代码链接并打进同一个二进制包中不同,动态库可以在运行时手动加载,这样就可以做很多事情,比如:

  • 应用插件化

目前很多应用功能越做越多,软件显得越来越臃肿。因此插件化就成了很多软件发展的必经之路,比如支付宝这种平台级别的软件:

首页上密密麻麻的功能,而且还在增多,照这个趋势发展下去,软件包的大小将会不可想象。目前常用的解决方案是使用web页面,但用户体验和Native界面是没法比的。

设想,如果每一个功能点都是一个动态库,在用户想使用某个功能的时候让其从网络下载,然后手动加载动态库,实现功能的的插件化,就再也不用担心功能点的无限增多了,这该是件多么美好的事!

  • 软件版本实时模块升级

还在忍受苹果动辄一周,甚至更长的审核周期吗?有了动态库妈妈就再也不用担心你的软件升级了!

如果软件中的某个功能点出现了严重的bug,或者想在其中新增功能,你的这个功能点又是通过动态库实现的,这时候你只需要在适当的时候从服务器上将新版本的动态库文件下载到本地,然后在用户重启应用的时候即可实现新功能的展现。

  • 共享可执行文件

在其它大部分平台上,动态库都可以用于不同应用间共享,这就大大节省了内存。从目前来看,iOS仍然不允许进程间共享动态库,即iOS上的动态库只能是私有的,因为我们仍然不能将动态库文件放置在除了自身沙盒以外的其它任何地方。

不过iOS8上开放了App Extension功能,可以为一个应用创建插件,这样主app和插件之间共享动态库还是可行的。

PS: 上述关于动态库在iOS平台的使用,在技术上都是可行的,但本人并没有真正尝试过做出一个上线AppStore的应用,因此并不保证按照上述方式使用动态库一定能通过苹果审核!

2014-6-23修正:

@唐巧_boy提醒,sandbox会验证动态库的签名,所以如果是动态从服务器更新的动态库,应该是签名不了的,因此应用插件化、软件版本实时模块升级估计很难实现,一切都只能等到iOS8正式版发布以后再验证了。感谢@唐巧_boy

创建动态库

1、创建动态库

  • 创建工程文件

在下图所示界面能够找到Cocoa Touch动态库的创建入口:

跟随指引一步步操作即可创建一个新的动态库工程,我的工程名字叫Dylib,Xcode会同时创建一个和工程target同名的.h文件,比如我的就是Dylib.h。

  • 向工程中添加文件

接下来就可以在工程中随意添加文件了。我在其中新建了一个名为Person的测试类,提供的接口如下:

1
2
3
4
5
@interface Person : NSObject

- (void)run;

@end

对应的实现部分:

1
2
3
4
5
6
7
8
9
10
11
@implementation Person

- (void)run
{
    NSLog(@"let‘s run.");

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"The Second Alert" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"done", nil];
    [alert show];
}

@end
  • 设置开放的头文件

一个库里面可以后很多的代码,但是我们需要设置能够提供给外界使用的接口,可以通过Target—>Build Phases—>Headers来设置,如下图所示:

我们只需将希望开放的头文件放到Public列表中即可,比如我开放了Dylib.hPerson.h两个头文件,在生成的framework的Header目录下就可以看到这两个头文件,如下图所示:

一切完成,Run以后就能成功生成framework文件了。

2、通用动态库

经过第一步我们只是创建了一个动态库文件,但是和静态库类似,该动态库并同时不支持真机和模拟器,可以通过以下步骤创建通用动态库:

  • 创建Aggregate Target

按下图所示,在动态库工程中添加一个类型为Aggregate的target:

按提示一步步操作即可,我给Aggregate的Target的命名是CommonDylib

  • 设置Target Dependencies

按以下路径设置CommonDylib对应的Target Dependencies:

1
TARGETS-->CommonDylib-->Build Phases-->Target Dependencies

将真正的动态库Dylib Target添加到其中。

  • 添加Run Script

按以下路径为CommonDylib添加Run Script:

1
TARGETS-->CommonDylib-->Build Phases-->Run Script

添加的脚本为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Sets the target folders and the final framework product.
FMK_NAME=${PROJECT_NAME}

# Install dir will be the final output to the framework.
# The following line create it in the root folder of the current project.
INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework

# Working dir will be deleted after the framework creation.
WRK_DIR=build
DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework

# -configuration ${CONFIGURATION}
# Clean and Building both architectures.
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos clean build
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator clean build

# Cleaning the oldest.
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi

mkdir -p "${INSTALL_DIR}"

cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"

# Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.
lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"

rm -r "${WRK_DIR}"

添加以后的效果如图所示:

该脚本是我根据一篇文章中介绍的脚本改写的,感谢原文作者

脚本的主要功能是:

1.分别编译生成真机和模拟器使用的framework; 2.使用lipo命令将其合并成一个通用framework; 3.最后将生成的通用framework放置在工程根目录下新建的Products目录下。

如果一切顺利,对CommonDylib target执行run操作以后就能生成一个如图所示的通用framework文件了:

使用动态库

添加动态库到工程文件

经过以上步骤的努力,生成了最终需要的framework文件,为了演示动态库的使用,新建了一个名为FrameworkDemo的工程。通过以下方式将刚生成的framework添加到工程中:

1
Targets-->Build Phases-->Link Binary With Libraries

同时设置将framework作为资源文件拷贝到Bundle中:

1
Targets-->Build Phases-->Copy Bundle Resources

如图所示:

仅仅这样做是不够的,还需要为动态库添加链接依赖。

自动链接动态库

添加完动态库后,如果希望动态库在软件启动时自动链接,可以通过以下方式设置动态库依赖路径:

1
Targets-->Build Setting-->Linking-->Runpath Search Paths

由于向工程中添加动态库时,将动态库设置了Copy Bundle Resources,因此就可以将Runpath Search Paths路径依赖设置为main bundle,即沙盒中的FrameworkDemo.app目录,向Runpath Search Paths中添加下述内容:

1
@executable_path/

如图所示:

其中的@executable_path/表示可执行文件所在路径,即沙盒中的.app目录,注意不要漏掉最后的/

如果你将动态库放到了沙盒中的其他目录,只需要添加对应路径的依赖就可以了。

需要的时候链接动态库

动态库的另一个重要特性就是即插即用性,我们可以选择在需要的时候再加载动态库。

  • 更改设置

如果不希望在软件一启动就加载动态库,需要将

1
Targets-->Build Phases-->Link Binary With Libraries

Dylib.framework对应的Status由默认的Required改成Optional;或者更干脆的,将Dylib.frameworkLink Binary With Libraries列表中删除即可。

  • 使用dlopen加载动态库

Dylib.framework为例,动态库中真正的可执行代码为Dylib.framework/Dylib文件,因此使用dlopen时如果仅仅指定加载动态库的路径为Dylib.framework是没法成功加载的。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (IBAction)onDlopenLoadAtPathAction1:(id)sender
{
    NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/Dylib.framework/Dylib",NSHomeDirectory()];
    [self dlopenLoadDylibWithPath:documentsPath];
}

- (void)dlopenLoadDylibWithPath:(NSString *)path
{
    libHandle = NULL;
    libHandle = dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);
    if (libHandle == NULL) {
        char *error = dlerror();
        NSLog(@"dlopen error: %s", error);
    } else {
        NSLog(@"dlopen load framework success.");
    }
}

以dlopen方式使用动态库不知道是否能通过苹果审核。

  • 使用NSBundle加载动态库

也可以使用NSBundle来加载动态库,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (IBAction)onBundleLoadAtPathAction1:(id)sender
{
    NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/Dylib.framework",NSHomeDirectory()];
    [self bundleLoadDylibWithPath:documentsPath];
}

- (void)bundleLoadDylibWithPath:(NSString *)path
{
    _libPath = path;
    NSError *err = nil;
    NSBundle *bundle = [NSBundle bundleWithPath:path];
    if ([bundle loadAndReturnError:&err]) {
        NSLog(@"bundle load framework success.");
    } else {
        NSLog(@"bundle load framework err:%@",err);
    }
}

使用动态库中代码

通过上述任一一种方式加载的动态库后,就可以使用动态库中的代码文件了,以Dylib.framework中的Person类的使用为例:

1
2
3
4
5
6
7
8
- (IBAction)onTriggerButtonAction:(id)sender
{
    Class rootClass = NSClassFromString(@"Person");
    if (rootClass) {
        id object = [[rootClass alloc] init];
        [(Person *)object run];
    }
}

注意,如果直接通过下属方式初始化Person类是不成功的:

1
2
3
4
5
6
7
- (IBAction)onTriggerButtonAction:(id)sender
{
    Person *object = [[Person alloc] init];
    if (object) {
       [object run];
    }
}

监测动态库的加载和移除

我们可以通过下述方式,为动态库的加载和移除添加监听回调:

1
2
3
4
5
+ (void)load
{
  _dyld_register_func_for_add_image(&image_added);
  _dyld_register_func_for_remove_image(&image_removed);
}

github上有一个完整的示例代码

从这里看出,原来就算空白工程软件启动的时候也会加载多达一百二十多个动态库,如果这些都是静态库,那该有多可怕!!

Demo

本文使用的例子已经上传到github上,需要的朋友请自取。

另外,本文对某些东西可能有理解错误的地方,还请指出。

参考文档:

时间: 2024-10-12 19:39:47

WWDC2014之iOS使用动态库 framework【转】的相关文章

WWDC2014之iOS使用动态库

苹果的开放态度 WWDC2014上发布的Xcode6 beta版有了不少更新,其中令我惊讶的一个是苹果在iOS上开放了动态库,在Xcode6 Beta版的更新文档中是这样描述的: Frameworks for iOS. iOS developers can now create dynamic frameworks. Frameworks are a collection of code and resources to encapsulate functionality that is val

IOS 使用动态库(dylib)和动态加载framework

在iphone上使用动态库的多为dylib文件,这些文件使用标准的dlopen方式来使用是可以的.那相同的在使用framework文件也可以当做动态库的方式来动态加载,这样就可以比较自由的使用apple私有的framework了. dlopen是打开库文件 dlsym是获取函数地址 dlclose是关闭. 当然,要使用这种方式也是有明显缺陷的,那就是你要知道函数名和参数,否则无法继续. 私有库的头文件可以使用class dump的方式导出来,这个详细的就需要google了. 下面是两个使用的例子

(转)iOS静态库与动态库的区别

一.什么是库? 库是共享程序代码的方式,一般分为静态库和动态库. 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝. 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存. 三.iOS里静态库形式? .a和.framework 四.iOS里动态库形式? .dylib和.framework 五.framework为什么既是静态库又是动态库? 系统的.framework是动态库,我们自己建立的.framework是静态库. 六.a

转载:iOS静态库与动态库

一.什么是库? 库是共享程序代码的方式,一般分为静态库和动态库. 二.静态库与动态库的区别? 库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种. 静态库:在程序编译时会被完整地链接到目标代码中,程序运行时将不再需要改静态库.利用静态函数库编译成的文件比较大,因为整个 函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了.当然这也会成为他的缺点,因为如果静态函数库改变了,

iOS 使用 Xcode8 制作动态库及静态库

在使用第三方 SDK 时,经常遇到他们提供的仅仅只有一个动态或静态库,并不能获取源码.使用动态库 FrameWork 或 静态库 Lib,可以满足不想把核心代码的具体实现向使用者展示,又能避免其他人错改动代码. 一.动态库 FrameWork 的制作: 打开 Xcode,选中 Cocoa Touch FrameWork. 新建一个 PublicModel 文件,简单输出Log 若这个 PublicManager 文件想要对外公开头文件.需要 然后编译,得到动态库 FrameWork 到目前为止,

静态库和动态库详解

1.什么是库,为什么使用库? 库是共享程序代码的方式,一般分为静态库和动态库:库实现了iOS程序的模块化,将某些特定的功能模块化为库的格式方便分享和使用! 2.静态库和动态库有什么特点? 异同点: 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝. 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序可以共用,节省内存. 共同点: 静态库和动态库都是闭源库,只能拿来满足某个功能的使用,不会暴露内部具体的代码信息,而从github上下载的第三

静态库与动态库的区别?

一.什么是库? 库是共享程序代码的方式,一般分为静态库和动态库. 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝. 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存. 三.iOS里静态库形式? .a和.framework 四.iOS里动态库形式? .dylib和.framework 五.framework为什么既是静态库又是动态库? 系统的.framework是动态库,我们自己建立的.framework是静态库. 六.a

组件化-动态库实战

http://www.cocoachina.com/articles/19136 组件化-动态库实战 sasukeo·2017-04-27阅读数 22643 本文来自南栀倾寒,作者 sasukeo 起因 理论功底 动态库和静态库 介绍 静态库和动态库的区别 举个例子, iOS 项目中使用 Embeded Framework 静态库和动态库如何构建和加载 静态库和动态库依赖关系 Xcode 项目结构 iOS 依赖管理事实上的标准 解决问题 制作动态库 剖析下动态库 Framework 吧 回过头来

iOS 静态库,动态库与 Framework

iOS 静态库,动态库与 Framework 静态库与动态库的区别 首先来看什么是库,库(Library)说白了就是一段编译好的二进制代码,加上头文件就可以供别人使用. 什么时候我们会用到库呢?一种情况是某些代码需要给别人使用,但是我们不希望别人看到源码,就需要以库的形式进行封装,只暴露出头文件.另外一种情况是,对于某些不会进行大的改动的代码,我们想减少编译的时间,就可以把它打包成库,因为库是已经编译好的二进制了,编译的时候只需要 Link 一下,不会浪费编译时间. 上面提到库在使用的时候需要