iOS 插件化开发
2017-08-10 本文已影响667人
飘金
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代码链接并打进同一个二进制包中不同,动态库可以在运行时手动加载,这样就可以做很多事情,比如:
-
应用插件化
目前很多应用功能越做越多,软件显得越来越臃肿。因此插件化就成了很多软件发展的必经之路,比如支付宝这种平台级别的软件:
image.png image.png这边是PiaoJinDylib
创建你测试类PiaoJin
头文件部分
#import <Foundation/Foundation.h>
@interface PiaoJin : NSObject
- (void)love;
@end
实现部分
#import "PiaoJin.h"
#import <UIKit/UIKit.h>
@implementation PiaoJin
- (void)love{
NSLog(@"love you more than I can say!");
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"love you more than I can say by 飘金!" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"确定", nil];
[alertView show];
}
@end
在PiaoJinDylib中引入
#import <UIKit/UIKit.h>
//! Project version number for PiaoJinDylib.
FOUNDATION_EXPORT double PiaoJinDylibVersionNumber;
//! Project version string for PiaoJinDylib.
FOUNDATION_EXPORT const unsigned char PiaoJinDylibVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <PiaoJinDylib/PublicHeader.h>
#import "PiaoJin.h"
设置开放的头文件
一个库里面可以后很多的代码,但是我们需要设置能够提供给外界使用的接口,可以通过Target—>Build Phases—>Headers来设置,如下图所示:
image.png我们只需将希望开放的头文件放到Public列表中即可,比如我开放了PiaoJinDylib.h和PiaoJin.h两个头文件,在生成的framework的Header目录下就可以看到这两个头文件.一切完成,Run以后就能成功生成framework文件了。
前面只是我们只是创建了一个动态库文件,但是和静态库类似,该动态库并同时不支持真机和模拟器,可以通过以下步骤创建通用动态库:
image.png image.png创建Aggregate Target(PiaoJinDylib工程下)
我给Aggregate的Target的命名是CommonDylib。
设置Target Dependencies
按以下路径设置CommonDylib对应的Target Dependencies:
TARGETS-->CommonDylib-->Build Phases-->Target Dependencies
将真正的动态库PiaoJinDylib Target添加到其中。
添加Run Script
按以下路径为CommonDylib添加Run Script:
TARGETS-->CommonDylib-->Build Phases-->Run Script
添加的脚本为:
# 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}"
添加以后的效果:
image.png脚本的主要功能是:
1.分别编译生成真机和模拟器使用的framework;
2.使用lipo命令将其合并成一个通用framework;
3.最后将生成的通用framework放置在工程根目录下新建的Products目录下。
如果一切顺利,对CommonDylib target执行run操作以后就能生成一个如图所示的通用framework文件了:
image.png image.png使用动态库
实际过程中动态库是需要从服务器下载并且保存到app的沙盒中的,这边直接模拟已经下载好了动态库并且保存到沙盒中:
image.png使用动态库
使用NSBundle加载动态库
- (IBAction)loadFrameWorkByBundle:(id)sender {
//从服务器去下载并且存入Documents下(只要知道存哪里即可),事先要知道framework名字,然后去加载
NSString *frameworkPath = [NSString stringWithFormat:@"%@/Documents/PiaoJinDylib.framework",NSHomeDirectory()];
NSError *err = nil;
NSBundle *bundle = [NSBundle bundleWithPath:frameworkPath];
NSString *str = @"加载动态库失败!";
if ([bundle loadAndReturnError:&err]) {
NSLog(@"bundle load framework success.");
str = @"加载动态库成功!";
} else {
NSLog(@"bundle load framework err:%@",err);
}
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:str message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"确定", nil];
[alertView show];
}
使用dlopen加载动态库
以PiaoJinDylib.framework为例,动态库中真正的可执行代码为PiaoJinDylib.framework/PiaoJinDylib文件,因此使用dlopen时指定加载动态库的路径为PiaoJinDylib.framework/PiaoJinDylib。
NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/PiaoJinDylib.framework/PiaoJinDylib",NSHomeDirectory()];
[self dlopenLoadDylibWithPath:documentsPath];
if (dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW) == NULL) {
char *error = dlerror();
NSLog(@"dlopen error: %s", error);
} else {
NSLog(@"dlopen load framework success.");
}
调用动态库中的方法
//调用FrameWork的方法,利用runTime运行时
- (IBAction)callMethodOfFrameWork:(id)sender {
Class piaoJinClass = NSClassFromString(@"PiaoJin");
if(piaoJinClass){
//事先要知道有什么方法在这个FrameWork中
id object = [[piaoJinClass alloc] init];
//由于没有引入相关头文件故通过performSelector调用
[object performSelector:@selector(love)];
}else {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"调用方法失败!" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"确定", nil];
[alertView show];
}
}
监测动态库的加载和移除
我们可以通过下述方式,为动态库的加载和移除添加监听回调:
+ (void)load
{
_dyld_register_func_for_add_image(&image_added);
_dyld_register_func_for_remove_image(&image_removed);
}
github上有一个完整的示例代码。
后记