iOS 使用动态库

苹果的开放态度

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

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.linux

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

framework是Cocoa/Cocoa Touch程序中使用的一种资源打包方式,能够将将代码文件、头文件、资源文件、说明文档等集中在一块儿,方便开发者使用,做为一名Cocoa/Cocoa Touch程序员天天都要跟各类各样的Framework打交道。Cocoa/Cocoa Touch开发框架自己提供了大量的Framework,好比Foundation.framework/UIKit.framework /AppKit.framework等。须要注意的是,这些framework无一例外都是动态库。git

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

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

iOS上动态库能够作什么

和静态库在编译时和app代码连接并打进同一个二进制包中不一样,动态库能够在运行时手动加载,这样就能够作不少事情,好比:objective-c

  • 共享可执行文件

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

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

2014-6-23修正:app

@唐巧_boy提醒,sandbox会验证动态库的签名,因此若是是动态从服务器更新的动态库,是签名不了的,所以应用插件化、软件版本实时模块升级等功能在iOS上没法实现。

建立动态库

一、建立动态库

  • 建立工程文件

在下图所示界面可以找到Cocoa Touch动态库的建立入口:

framework

跟随指引一步步操做便可建立一个新的动态库工程,个人工程名字叫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来设置,以下图所示:

header

咱们只需将但愿开放的头文件放到Public列表中便可,好比我开放了Dylib.hPerson.h两个头文件,在生成的framework的Header目录下就能够看到这两个头文件,以下图所示:

public_header

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

二、通用动态库

通过第一步咱们只是建立了一个动态库文件,可是和静态库相似,该动态库并同时不支持真机和模拟器,能够经过如下步骤建立通用动态库:

  • 建立Aggregate Target

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

aggregate

按提示一步步操做便可,我给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}" 

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

commonlib_setting

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

脚本的主要功能是:

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

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

products

使用动态库

添加动态库到工程文件

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

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

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

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

如图所示:

framework_demo_setting

仅仅这样作是不够的,还须要为动态库添加连接依赖。

自动连接动态库

添加完动态库后,若是但愿动态库在软件启动时自动连接,能够经过如下方式设置动态库依赖路径:

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

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

1
@executable_path/ 

如图所示:

run_search_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上,须要的朋友请自取。

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

参考文档:

相关文章
相关标签/搜索