动态库和静态库的区别:Pass。直接进入动态库制作主题
零、实验环境
0.1.接下去内容在Xcode Version 8.3.3 (8E3004b)开发工具中完成。
一、基础准备
1.1.创建一个动态库MangoSDK,如下图
1.2.创建一个测试工具类,如下图
1.3.在MangoSDK.h中#import "MGUtils.h"
,如下图
1.4.点击工程 -> 在targets中选中MangoSDK -> Build Phases -> Headers,如下图所示,可以看到在动态库中创建的文件会自动添加到Build Phases中的project列表中,MangoSDK.h文件是处于Public列表中,所以外部只能看到MangoSDK.h这个头文件,由于我们动态库外部使用者需要调用MGUtilis.h
中的方法,所以也需要将MGUtils.h
拖拽到Public列表中.
二、编译动态库
2.1.选择动态库对应的Scheme,选择编译设备为对应的真机,如下图
如果没有连接真机,也可以,只要选择
Generic iOS Device
选项也是可以编译出对应真机的动态库,如下图
2.2.编译动态库(command + shift + B)后,在Xcode工程中的Products(这个目录不是工程源文件目录,而是编译后生成对应的沙盒目录)找到MangoSDK.framework文件,右键show in finder。如下图
2.3.利用lipo -info 查看动态库所支持的CPU指令集,步奏如下
- 打开终端
- cd 进入
MangoSDK.framework
,这里需要注意进入的是MangoSDK.framework
,而不是MangoSDK.framework
所在目录 - 在终端输入
$lipo -info MangoSDK
通过以上三个步奏后,在终端会显示出MangoSDK.framework
所支持的CPU指令集,如下图所示,可以看出默认新建工程后所编译出来的动态库所支持的CPU指令集是arm64(因为我所使用的真机是6sp,其CPU指令集是arm64,后面会介绍CPU指令集是什么)
上面2.3.2需要额外注意一下,cd进入的是
MangoSDK.framework
,下面演示一下进入MangoSDK.framework
所在目录,进行lipo -info
的错误结果:下面演示正确的lipo -info
姿势:
三、指令集的介绍
3.1. 指令集种类
- armv7|armv7s|arm64都是ARM处理器的指令集
- i386|x86_64 是iOS模拟器的指令集
3.2.指令集对应的机型
arm64:iPhone6s | iphone6s plus|iPhone6| iPhone6 plus|iPhone5S | iPad Air| iPad mini2(iPad mini with Retina Display)
armv7s:iPhone5|iPhone5C|iPad4(iPad with Retina Display)
armv7:iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4
i386: iPhone5 | iPhone 4s | iPhone 4及前代产品的模拟器
x86_64: iPhone5s | iPhone 6 | ... | iPhone8的模拟器
3.3.指令集兼容性说明
理论上指令集是向下兼容的,比如连接设备为arm64,那么是有可能编译出的动态库所支持的指令集为armv7s或者是armv7,这个具体看后面的介绍。但是向下兼容并不是说一个armv7s的动态库可以用在arm64架构的设备上,如果连接的设备是arm64的,而导入的动态库是没有支持arm64,那么在编译阶段即会报错
四、Xcode指令集的编译选项说明
4.1. 打开 Target -> Build Setting -> Architectures, 可以看到下图
4.2. 介绍一下三个编译选项
-
Architectures
:指明选定Target要求被编译生成的二进制包所支持的指令集 -
Build Active Architecture Only
: 指明是否只编译当前连接设备所支持的指令集,如果是,那么只编译出连接设备所对应的指令集,如果否,则编译出所有其它有效的指令集(由Architectures和Valid Architectures决定
) -
Valid Architectures
:指明可能支持的指令集并非Architectures列表中指明的指令集都会被支持
编译产生的动态库所支持的指令集将由上面三个编译选项所影响,首先一个动态库要成功编译,则需要这三个编译选项的交集不为空,举几个例子:
示例1:
Architectures 为armv7、arm64
Valid Architectures 为armv7、armv7s、arm64
Build Active Architecture Only 为 debug:YES release:NO
链接设备:iPhone 6s (arm64架构的设备)
编译(command + shift + B,保证Build Active Architecture Only 为 debug:YES 生效)
结果:编译成功,生成的动态库支持的指令集为arm64
示例2:
Architectures 为armv7、arm64
Valid Architectures 为 armv7s
Build Active Architecture Only 为 debug:YES release:NO
链接设备:iPhone 6s (arm64架构的设备)
编译(command + shift + B,保证Build Active Architecture Only 为 debug:YES 生效)
结果:编译失败,因为当前是debug模式,在该模式下Build Active Architecture Only 为YES,表示只编译支持该指令集的动态库,
但是由于Architectures和Build Active Architecture Only的交集中并不存在arm64,故三者的交集为空,故编译失败,无法生成动态库。
示例3:
Architectures 为armv7、arm64
Valid Architectures 为armv7、armv7s、arm64
Build Active Architecture Only 为 debug:NO release:NO
链接设备:iPhone 6s (arm64架构的设备)
编译(command + shift + B,保证Build Active Architecture Only 为 debug:YES 生效)
结果:编译成功,因为当前是debug模式,在该模式下Build Active Architecture Only 为NO,
表示可以编译的结果可能为当前连接的设备所支持的指令集以及其向下兼容的指令集(armv64、armv7s、armv7),其和另外两个编译选项的交集为armv7,故所生成的动态库支持的指令集为armv7
所以,我们在步奏二、编译动态库中之所以会生成支持arm64的动态库是因为:
1.Build Active Architecture Only 为 debug:YES release:NO
2.我们使用的是编译,所以生效的选项为debug:YES,表示只生成当前机型对应的指令集(iPhone 6sp为 arm64)
3. Architectures和Valid Architectures的交集为armv7、arm64,故三个选项的最终交集只有arm64,所以生成的动态库只支持arm64指令集
五、制作支持iPhone 4及以后机型的动态库
5.1. 支持iPhone 4 及以后机型的动态库的意思是:生成的动态库支持的指令集为armv7、armv7s、arm64
,所以Architectures的三个指令可以设置为:
Build Active Architecture Only 统一为NO(如果不修改,则不能使用编译去生成动态库,而是需要去修改scheme的run模式为release,并且command + R运行动态库,为了简便,这里统一设置为NO)
-
Architectures和Valid Architectures都设置为armv7、armv7s、arm64
设置后的Architecture 为:
-
按照步奏 二、编译动态库 进行操作,最后通过lipo -info可以看到我们的动态库已经支持了
armv7、armv7s、arm64
,如下图所示:
六、制作支持iPhone 4及以后模拟器的动态库
6.1. 支持iPhone 4 及以后模拟器的动态库的意思是:生成的动态库支持的指令集为i386、x86_64
,所以Architectures的三个指令可以设置为:
- Build Active Architecture Only 统一为NO
- Architectures和Valid Architectures最少都要包含armv7s、arm64(少了armv7s则不会支持i386,少了arm64则不会支持x86_64,挺神奇的,还没弄懂),设置后的Architecture 和步奏5.1第一张配图一样
- 参照步奏 二、编译动态库 进行操作,不过需要将步奏2.1中编译设备选择为模拟器(任意iPhone模拟器都可以),最后通过lipo -info可以看到我们的动态库已经支持了
i386、x86_64
,如下图所示:
七、合并模拟器和真机动态库
7.0.合并动态库的前提是已经按照第五、第六步奏生成了两个对应的动态库,否则直接进行合并操作的话,会提示找不到文件,这一点可以翻下去看粘贴的脚本命令,仔细读一读就知道原理了。
7.1.通过步奏五和步奏六生成的动态库名字是一样的,但是不通用于模拟器和真机,如果不合并动态库的话,那么在切换模拟器和真机的时候需要同时切换动态库,这是我们不希望看到的,所以我们需要将模拟器和真机动态库合并为一个通用的动态库。
7.2. 由于在终端中使用lipo -create -output
命令合并动态库有点复杂(搞了半个小时合并出来的动态库不能使用),故还是使用脚本生成动态库好了,步奏如下:
- 新建一个target脚本,如下图
- 粘贴以下脚本内容到指定位置,如下图
if [ "${ACTION}" = "build" ]
then
INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework
DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework
SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"
# 使用lipo命令将其合并成一个通用framework
# 最后将生成的通用framework放置在工程根目录下新建的Products目录下
lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"
#open "${DEVICE_DIR}"
#open "${SRCROOT}/Products"
fi
- 编译新target,如图所示
- 编译完成后生成的framework位于工程源代码根目录下的Products文件夹下面,通过
lipo -info
可以看到动态库已经支持i386、x86_64、armv7、armv7s、arm64
,如下图所示
八、使用动态库
8.1.将步奏七生成的动态库拖入新工程中,在新工程的AppDelegate.m
中键入如下代码
#import <MangoSDK/MangoSDK.h>
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ViewController *vc = [[ViewController alloc] init];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.window makeKeyAndVisible];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = vc;
[MGUtils mg_logMessage:@"framework test"];
return YES;
}
8.2.在新工程的target中的General -> Embedded Binaries中添加MangoSDK.framework
,如下图
8.3.分别使用真机和模拟器运行新工程,执行成功,控制台输出如下:
九、使用别人提供的动态库遇到的坑
9.1.第一类坑为别人提供的第三方库所支持的CPU指令集不全,出现的错误信息类似如下图:
上面的截图中,我们连接的设备是iPhone 7 模拟器,其CPU架构为x86_64,但是我导入的framework是真机编译出来的动态库(支持的指令集为
armv7、armv7s、arm64
,并没有x86_64
),所以就报了这样的类似的错误,进一步可以使用步奏 二、编译动态库 中的2.3小点查看别人提供的动态库所支持的指令集,这个坑属于那个提供动态库的同事造成的,让他去填就可以。
9.2.运行过程中出现 image not found
异常或者控制台没有异常输出,如下图
出现这种问题的原因是我们没有往
Embedded Binaries
中添加MangoSDK.framework
,所以进行如下操作即可解决这个异常。话说像讯飞之类的framework为什么不需要往Embedded Binaries添加对应SDK,即可以成功运行?这一点我还没有去研究,知道的小伙伴还请不吝赐言哟
以上便是我近来和动态库打交道过程中遇到的坑,如果你完整了看完了我这篇文章,还是建议你可以去实践一下制作一个动态库。说多了都是泪,由于我之前没有制作过动态库,同事提供了我一个SDK后,我集成出现了第九点中的两个坑,寻同事,同事曰:别人可以,你为什么不可以?iOS你比我熟!如果当初我对动态库的整个流程熟悉的话,那么我就可以对其说:首先你提供的动态库只支持armv7、armv7s
,我的手机是arm64
架构指令集的,其次,因为别人使用的设备恰好是iPhone 5
,所以没毛病。但是你必须给我重新编译一个支持i386、x86_64、armv7、armv7s、arm64
的动态库。所以熟悉动态库的制作-嵌入整个流程还是必要的。
如果以上内容有错,还请不吝指出,谢谢大家。
上面我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交审核
在SDK当前路径下执行以下命令删除i386 x86_64两个平台
bak文件是备份目录,上传appstore之后需要替换回bak目录下的SDK
mkdir ./bak
cp -r MangoSDK.framework ./bak
lipo MangoSDK.framework/MangoSDK -thin armv7 -output MangoSDK_armv7
lipo MangoSDK.framework/MangoSDK -thin arm64 -output MangoSDK_arm64
lipo -create MangoSDK_armv7 MangoSDK_arm64 -output MangoSDK
mv MangoSDK MangoSDK.framework/
十、动态库包含第三方库怎么做?
按照惯例,给MangoSDK这个目录增加cocoapod支持,文件内容如下:
# Uncomment this line to define a global platform for your project
platform :ios, '11.0'
target 'MangoSDK1' do
pod 'AFNetworking', '~> 3.0'
end
pod install 之后 打开cocoapods工程,为MGUtils.m
增加如下代码
#import "MGUtils.h"
#import "AFNetworking.h"
@implementation MGUtils
+(void)mg_logMessage:(NSString *)msg {
NSLog(@"%s- %@", __func__, msg);
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
[session GET:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"成功");
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"失败%@", error.description);
}];
}
@end
然后按照上文的打包方式打包,将生成的MangoSDK.framework拖入目标工程
打开target-> General,修改内容如下图所示:
注意,如果你的主工程的cocoaPods是使用动态库的集成方式,即profile文件中有use_frameworks!,那么制作MangoSDK的时候,也需要在profile中声明集成方式为use_frameworks!。建议制作两个SDK,兼容集成Swift的项目。
通过pod下载的第三方库默认是满足上文中的architecture中设置的第一个指令集,比如arm64,如果我们的MangoSDK要打出64e、64、7s、7的SDK,那么也需要第三方库都包含这几个指令,那么接下去,就看看怎么让第三方库SDK包含我们想要的指令,如下图: