支持特定架构的框架实现
前言
有时候,我们在给第三方移动端提供工具库的时候,会出现不兼容x86的情况。
- 比如:我们引用的一个框架不包含x86_64架构,导致我们的库无法编译出x86架构版本
- 比如:框架本身就是为相机或AR等特定功能而生,甚至剔除x86_64架构代码可以节省库的大小
这些情况下,我们能只编译arm64,放弃其它架构吗?答案当然是NO。
直接放弃32位arm和x86架构的支持,非常直接的,会带来两个负面效果。
- 如果应用有上千万的用户,其中很小的比例就可能几十万用户,放弃支持等于放弃这部分用户。(iPhone 5S 年代CPU的用户)
- 开发者原本使用模拟器进行设备兼容性调试和开发,接入仅64位的库后,工程无法在模拟器继续编译,给调试和屏幕大小适配带来困扰
无论是1还是2,都是无法接受的结果。我们需要找到一种方案,在支持支持arm64的情况下缩减体积。
思路
我们要解决的核心问题有两个
- 解决第三方代码不支持指定架构导致无法编译对应架构的问题
- 在支持非目标时候,我们编译了很多无用的代码。
解决思路也很简单
- 使用 TargetConditionals.h 的架构定义来区分架构,在不支持的架构中省去这部分功能和代码
- 非目标架构情况,使用 TargetConditionals.h 屏蔽所有代码
- 掩盖掉所有内部修饰痕迹
栗子
比如我们有一个 OnlyARM.framework 我们要做到
- arm64:功能正常
- armv7 / x86_64:功能无法使用,但是不会导致工程编译问题
我们核心 OnlyARMCore ,可能是这样子的:
OnlyARMCore.h
1 | |
OnlyARMCore.m
1 | |
其中,OnlyEvil64 是邪恶的第三方框架,仅仅支持64位架构,这时候,如果我们想要编译支持所有架构的自己框架,就会得到如下结果
1 | |
于是要在非arm64架构中干掉 OnlyEvil64
1 | |
但是,回到主题,如果我们的功能,原本就仅仅支持64位设计呢?我们其实可以直接区分两个OnlyARMCore;一个OnlyARMCore for arm64,一个for others。那我们可以这么写:
1 | |
到这里,我们又发现了新的问题,如果说我们的这个类,有200个方法,我们会需要重新实现200次像是 - (void)sayHi 这样的空函数,这既重复又容易出错。
有没有一劳永逸解决的方法呢?我们看了看 @interface OnlyARMCore : NSObject 中的NSObject,想起来了古老的消息转发。
很简单了,只要在 #if (TARGET_CPU_ARM64) #else 和 #endif之间处理掉所有非arm64架构的方法就可以。
1 | |
然后,为了最小化痕迹,我们可以把这部分代码抽象到catagory中。
OnlyARMCore+OnlyARMCore_DynamicMethod.h
1 | |
OnlyARMCore+OnlyARMCore_DynamicMethod.m 中
1 | |
但是这时候,我们又有疑问了
1 | |
因为编译器在对外的.h 文件,我们是没有区分架构的
1 | |
在外面看来(或从编译器角度看),这时候是同时编译了64位和其它架构的。但是实际上在.m中,非64位架构,实际上想 saySomething 或者 sayHi 时候,实例方法和类方法分别是转发给了转发给了
1 | |
和
1 | |
在以上方法中,我们最终都将消息指向了 OnlyARMCore_dynamicMethodIMP ,来告诉外部开发者,我们的功能只在特定架构生效。
这时候,我们需要告诉编译器,这部分检查可以略过
1 | |
我们忽略掉了所有push 和 pop 之间的warning
1 | |
思考
- Objective-C的动态机制,使得很多操作变成可能;但是要记住,更多的可能性意味着可能更难debug;好的代码不应该丢掉可维护性和可读性。
- Swift 笔者还未尝试,理论上也是可以通过@objc使用Objective-C的动态特性,但是Swift毕竟本身是静态类型的语言,Swift应该会有更适合的方式吧。
- 目前认知:可能还是支持x86架构会更好,在代码中通过宏定义来屏蔽x86的实现;以避免对第三方工程的入侵。
- 至于32位,最近的移动设备是 iPhone 5;已经随着时间的长河成为了历史,已不太有支持的必要。
Demo
GitHub - DikeyKing/OnlyARMDemo: A Example of use arm64 only framework