iOS的静态库有两种,.a和.framework, 动态库.framework居多(还有. dylib). 静态库建议也生成.framework模式的,好处是.framework包含了相关的.h文件等..a文件还要自己添加相关的.h文
动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存.系统提供的库基本上都属于此类,例如UIKit等,A应用和B应用中都用到了UIKit,系统只加载了一份在内存中.IOS中对于第三方的动态库,很遗憾,目前还不支持的.
iOS上动态库可以做什么?
和静态库在编译时和app代码链接并打进同一个二进制包中不同,动态库可以在运行时手动加载,这样就可以做很多事情,比如:
共享可执行文件:在其它大部分平台上,动态库都可以用于不同应用间共享,这就大大节省了内存。从目前来看,iOS仍然不允许进程间共享动态库,即iOS上的动态库只能是私有的,因为我们仍然不能将动态库文件放置在除了自身沙盒以外的其它任何地方。 不过iOS8上开放了App Extension功能,可以为一个应用创建插件,这样主app和插件之间共享动态库还是可行的。
注意:sandbox会验证动态库的签名,所以如果是动态从服务器更新的动态库,是签名不了的,因此应用插件化、软件版本实时模块升级等功能在iOS上无法实现。
项目framework管理工具:
CocoaPods 0.36开始支持动态库,Carthage也只支持动态库 ,所以都仅支持>=iOS8版本。
framework配置注意点:
***编译成功,但发现很多关于符号表的警告,这时需要将Generate Debug Symbols设置为NO即可关闭符号表警告。
***支持bitcode,在TAGETS的Build setting中搜索Other C Flags,添加命令“-fembed-bitcode”。
***在TARGETS->Build Phases->Headers里面,有三种类别。Public(公共的),这里存放供其他人查看的header。Private(私有的)这里存放私有的Header,以上两个Headers存放位置都会暴露出来,所有人可以查看。有些Header是不想给别人看到的。这种header放在第三个类Project中。
生成framework脚本:
set -e
# Sets the target folders and the final framework product.
FMK_TARGET_NAME=${PROJECT_NAME}
FMK_NAME=${PROJECT_NAME}
WRK_DIR=build
DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework
UNIVERSAL_OUTPUTFOLDER=${SRCROOT}/Products
# Cleaning the oldest.
if [ -d "${UNIVERSAL_OUTPUTFOLDER}" ]
then
rm -rf "${UNIVERSAL_OUTPUTFOLDER}"
fi
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
# Build Device and Simulator versions
xcodebuild -target "${FMK_TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration "Release" -sdk iphoneos clean build
xcodebuild -target "${FMK_TARGET_NAME}" -configuration "Release" -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO clean build
# Copy the framework structure (from iphoneos build) to the universal folder
cp -R "${DEVICE_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/"
# 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 "${UNIVERSAL_OUTPUTFOLDER}/${FMK_NAME}.framework/${FMK_NAME}"
# Convenience step to open the project's directory in Finder
rm -r "${WRK_DIR}"
open "${UNIVERSAL_OUTPUTFOLDER}"
使用动态库:
需要把动态库加入:Embedded Binaries 和 Linked Frameworks and Libraries。
加入Embedded Binaries,是因为Targets-->Build Setting-->Linking-->Runpath Search Paths中默认设置是@executable_path/Frameworks ,@executable_path/表示可执行文件所在路径,即沙盒中的.app目录,而Embedded Binaries就是这个Frameworks目录下。如果你将动态库放到了沙盒中的其他目录,只需要添加对应路径的依赖就可以了。
Targets-->Build Phases-->Link Frameworks With Libraries 就是对动态库的链接。动态库的另一个重要特性就是即插即用性,我们可以选择在需要的时候再加载动态库,framework对应的Status由默认的Required改成Optional;或者更干脆的,将framework从Link Binary With Libraries列表中删除改为手动加载。
手动加载及调用:
-(void)testFramework
{
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentDirectory = nil;
if ([paths count] != 0)
documentDirectory = [paths objectAtIndex:0];
//拼接我们放到document中的framework路径
NSString *libName = @"DframeworkDocuments.framework";
NSString *destLibPath = [documentDirectory stringByAppendingPathComponent:libName];
//判断一下有没有这个文件的存在 如果没有直接跳出
NSFileManager *manager = [NSFileManager defaultManager];
if (![manager fileExistsAtPath:destLibPath]) {
NSLog(@"There isn't have the file");
return;
}
//复制到程序中
NSError *error = nil;
//加载方式一:使用dlopen加载动态库的形式 使用此种方法的时候注意头文件的引入
// void* lib_handle = dlopen([destLibPath cStringUsingEncoding:NSUTF8StringEncoding], RTLD_LOCAL);
// if (!lib_handle) {
// NSLog(@"Unable to open library: %s\n", dlerror());
// return;
// }
//加载方式一 关闭的方法
// Close the library.
// if (dlclose(lib_handle) != 0) {
// NSLog(@"Unable to close library: %s\n",dlerror());
// }
//加载方式二:使用NSBundle加载动态库
NSError *err = nil;
NSBundle *frameworkBundle = [NSBundle bundleWithPath:destLibPath];
if (frameworkBundle && [frameworkBundle loadAndReturnError:&err]) {
NSLog(@"bundle load framework success.");
}else {
NSLog(@"bundle load framework err:%@",error);
return;
}
/*
*通过NSClassFromString方式读取类
*PacteraFramework 为动态库中入口类
*/
Class pacteraClass = NSClassFromString(@"PacteraFramework");
if (!pacteraClass) {
NSLog(@"Unable to get TestDylib class");
return;
}
/*
*初始化方式采用下面的形式
alloc init的形式是行不通的
同样,直接使用PacteraFramework类初始化也是不正确的
*通过- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
方法调用入口方法(showView:withBundle:),并传递参数(withObject:self withObject:frameworkBundle)
*/
NSObject *pacteraObject = [pacteraClass new];
[pacteraObject performSelector:@selector(showView:withBundle:) withObject:self withObject:frameworkBundle];
}