2、通用动态库
经过第一步我们只是创建了一个动态库文件,但是和静态库类似,该动态库并同时不支持真机和模拟器,可以通过以下步骤创建通用动态库:
按下图所示,在动态库工程中添加一个类型为Aggregate的target:
按提示一步步操作即可,我给Aggregate
的Target的命名是CommonDylib
。
按以下路径设置CommonDylib
对应的Target Dependencies
:
1
|
TARGETS-->CommonDylib-->Build Phases-->Target Dependencies |
将真正的动态库Dylib Target添加到其中。
按以下路径为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}" open "${INSTALL_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
中添加下述内容:
如图所示:
其中的@executable_path/
表示可执行文件所在路径,即沙盒中的.app目录,注意不要漏掉最后的/
。
如果你将动态库放到了沙盒中的其他目录,只需要添加对应路径的依赖就可以了。
需要的时候链接动态库
动态库的另一个重要特性就是即插即用
性,我们可以选择在需要的时候再加载动态库。
如果不希望在软件一启动就加载动态库,需要将
1
|
Targets-->Build Phases-->Link Binary With Libraries |
中Dylib.framework
对应的Status由默认的Required
改成Optional
;或者更干脆的,将Dylib.framework
从Link Binary With Libraries
列表中删除即可。
以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来加载动态库,实现代码如下:
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上,需要的朋友请自取。
另外,本文对某些东西可能有理解错误的地方,还请指出。
参考文档:
-
Framework Programming Guide
-
OS X Man Pages
-
New Features in Xcode 6 Beta
-
ImageLogger
-
Dynamic Linking
-
Dynamic loading
-
Integrating Reveal with your iOS app
-
IOS Framework制作全攻略
-
Build Settings中的变量@rpath,@loader_path,@executable_path
-
深入浅出Cocoa之Framework
-
linux中静态库和动态库的区别和汇总