JOE:
最近在为一个项目适配64位架构,参考官方的文档和大家在网上的心得然后就整理出这么一个好东东供大家做适配参考。要回寝室了,不然就没门口进了。。。
说明:
以下是我在做64位升级的各个步骤,里面的例子仅仅是该步骤的一些例子而已,大家可以举一反三,而“提示”里是一些个人心得而已。
1.配置项目文件中相关的选项
(1)确保需要支持arm64的各个TARGETS里的General的Deployment Target是5.1.1或以上(因为iOS 5.1.1或以上才开始支持arm64架构)
(2)确保TARGETS里的Architectures和Valid Architectures 的有效值的合集包含armv7和arm64(这里为什么说有效值,是因为它的只会收到PROGECT里的值替代,如果是它空值的话)
(3)其他选项就没什么了,如果还有不对的话,xcode还是会给你相应的修改提示请求的
小提示:如果你的当前active scheme所指向的设备是32位或64位的话,在debug下会分别会以32位和64位编译,所以两个都得测试哦。还有记得在真机上调试,因为arm64和x86_64毕竟是两个不一样的架构
2.更新项目中的 *.framework 或者 *.a 等第三方库
(1)但对应的官方网下载同时兼容armv7和arm64的库(也可以获得源代码自己生成相应的库,你有源码的话)
(2)在本地使用MAC下的lipo命令检查一下是否同时兼容armv7和arm64
例子:lipo -info 库文件
(3)使用代码比对工具对新旧库的头文件进行一下比对,检查API是否发生了变化,并且做相应的接口变更工作
小提示:在更换库的时候,最好是把新下载的库更改一下名字跟旧的库一样并且直接替换原项目中的旧库(路径也不用变),这样的就不用做代码里面库引入等其他修改工作也不会修工程配置文件中你一把心机手动设置的各个Configurations Set库搜索路径。
3.重写项目中的.s后缀的汇编文件
(1) 参照iOS ABI Function Call Guide检查并且重写asm代码
小提示:iOS并不完全支持汇编的标准,所以必须参照Guide来写对应的asm。如果项目中没有使用汇编的话,就可以直接跳过这个步骤了
4.到了这个步骤后基本上就可以进行黑盒测试
(1)交给测试同学去做黑盒测试测试(这样虽然对测试的同学是一个很大的工作量,但是对于工程比较大的项目,从黑盒定位问题所在是代价成本是相对是比较低,除非有一套完善的自动化白盒或黑盒测试工具)
(2)当然了,接下来就是修复测试同学发现的兼容性和适配性bug
[1].代理的实现
例子1:
系统代理:- (NSInteger)numberOfRow;
实现代理:- (int)numberOfRow { return 0; } -> - (NSInteger)numberOfRow { return 0; }
例子2:
自定义代理:- (CGFloat)percentOfRate;
实现代理 :- (float)numberOfRow { return 0.f; } -> - (CGFloat)numberOfRow { return 0.f; }
小提示:其实这是很不好的写法,可以说是错误的写法,但是为什么在32位的情况下是可用的呢,其实仅仅是碰巧,32位下NSInteger是int,CGFloat是float,函数的“样子”才碰巧是一致的可以生效,到64位下代理就无法正常回调,问题就会显示出来了。
[2].修改了系统行为
例子:
系统实现:- (void)setAlpha:(CGFloat)alpha {...}
覆盖系统:- (void)setAlpha:(CGFloat)alpha { ; } -> - (void)setAlpha:(BOOL)alpha { [super setAlpha:alpha]; ...} 用新类继承该系统类,然后在新类里覆盖该方法
小提示:其实这样的行为是不可取的,也不是标准的做法,这样会破坏了UIKit该控件的系统架构,如果你确实这么做了但是没有发现问题,那你就为自己的工程埋下了一个地雷了,肯定会引发“后患”的,所以,这种情况下你应该自定义一个继承这个系统类的自定义类,然后再去覆盖它(记得是类,不是系统类的类别),这里为什么64位跟它有关,是因为每个架构都会使用宏来控制一定的系统行为。
(3)最后就修复测试同学发现的逻辑性错误的bug
[1].位运算
例子1:
long a = 1 << (32-1); -> long a = 1 << (sizeof(long)-1); 只把long类型的a最低位的值保留到最高位,其他位清掉,很明显在64位环境下逻辑是错误的
例子2:
NSUInteger max = ~0; -> NSUInteger max = ~((NSUInteger)0); 获取无符号整形类型的最大值
小提示:其实这些问题在编码的时候多考虑一下就好了,可以使用sizeof()运算符先探测一下,并且增加位长度发生变化的代码逻辑。
[2].数据类型转换
例子1:
b是B类的一个实例,而c是B里面的一个NSInteger类型的成员变量
int a = b.c; -> NSInteger a = age;
小提示:如果c是保存外部NSInteger的数据,而在64位下的c所存储的信息就比a多了,就有可能会丢失了c的数据
例子2:
unsigned int a = 0;
int b = -1;
unsigned long c = a+b;
或 ->
unsigned int a = 0;
int b = -1;
unsigned long c = (long)(int)(a+b);
或 ->
unsigned int a = 0;
int b = -1;
unsigned long c = (unsigned long)(int)(a+b);
小提示:这里的问题跟例子3是相似的
例子3:
unsigned int a = 0;
int b = -1;
long c = a+b;
或 ->
unsigned int a = 0;
int b = -1;
int c = a+b;
或 ->
unsigned long a = 0;
long b = -1;
long c = a+b;
或 ->
unsigned int a = 0;
int b = -1;
long c = (int)(a+b);
小提示:这里可以给出在64位上"c"和"d"的结果发生错误的根本原因:有符号数跟无符号数进行运算后得到的是无符号数,而无符号数直接向上扩展成“更大的”有符号数或无符号数就会出问题
例子4:
NSUInteger nua = 1;
NSInteger na = -1;
unsigned int ua = 1;
int ia = -1;
CGFloat cf = .1f;
float f = .1f;
NSLog(@"%u %d %u %d %f %f", nua, na, ua, ia, cf, f);
->
NSLog(@"%lu %ld %u %d %lf %f", (unsigned long)nua, (long)na, ua, ia, (double)cf, f);
小提示:这里可以很明显的发现,为什么NSUInteger,NSInteger,CGFloat需要强制转换,而其他不用呢,因为格式化字符串只认识“d”是“int”而不是“NSInteger”而且把会变长的数据类型都使用“更大的数据类型”不但可以避免发生数据丢失,同时也能保证兼容32位和64位平台。而且,苹果官方也不支持把“NS”,“CG”等前缀的变量直接进行格式化解析
[3].指针出错:
例子:
void func(){...}
int *ptr = func;
ptr();
->
void func(){...}
long *ptr = func;
if(sizeof(func)==sizeof(long)) ((long)ptr)();
else if(sizeof(func)==sizeof(long)) ((int)ptr)();
else return;
小提示:在64位下的所有指针都是64位的,为了兼容64和32,可以使用long来保存指针类型,而不需要进行类型转换,因为long在32位下是4个字节,在64位下是8个字节。当然这只是个偷懒的方法,还是使用我建议的写法吧
[4].变长函数转定长函数
例子:
void(myLog*)(NSString *, int, int) = (void(*)(NSString *, int, int))NSLog;
myLog(@"%d",1);
->
#define myLog(format, INT1, INT2) NSLog((NSString *)(format), (int)(INT1), (int)(INT2));
myLog(@"%d %d",1,1);
小提示:在模拟器上是不是没发现然后可以看到“1,1”?那就对了,这个问题在模拟器上是不会暴露出来的,同时编译器也不会报错,但就是在64位上就会有不可预知的错误发生。因为在64位上的runtime中,变参的函数和固定参数的函数的调用方式已经不一样了
[5].如果还有其他未解之谜的话可以看一下是不是其他数据类型变化,可以参考“底部”的参考来源
最近在为一个项目适配64位架构,参考官方的文档和大家在网上的心得然后就整理出这么一个好东东供大家做适配参考。要回寝室了,不然就没门口进了。。。
说明:
以下是我在做64位升级的各个步骤,里面的例子仅仅是该步骤的一些例子而已,大家可以举一反三,而“提示”里是一些个人心得而已。
1.配置项目文件中相关的选项
(1)确保需要支持arm64的各个TARGETS里的General的Deployment Target是5.1.1或以上(因为iOS 5.1.1或以上才开始支持arm64架构)
(2)确保TARGETS里的Architectures和Valid Architectures 的有效值的合集包含armv7和arm64(这里为什么说有效值,是因为它的只会收到PROGECT里的值替代,如果是它空值的话)
(3)其他选项就没什么了,如果还有不对的话,xcode还是会给你相应的修改提示请求的
小提示:如果你的当前active scheme所指向的设备是32位或64位的话,在debug下会分别会以32位和64位编译,所以两个都得测试哦。还有记得在真机上调试,因为arm64和x86_64毕竟是两个不一样的架构
2.更新项目中的 *.framework 或者 *.a 等第三方库
(1)但对应的官方网下载同时兼容armv7和arm64的库(也可以获得源代码自己生成相应的库,你有源码的话)
(2)在本地使用MAC下的lipo命令检查一下是否同时兼容armv7和arm64
例子:lipo -info 库文件
(3)使用代码比对工具对新旧库的头文件进行一下比对,检查API是否发生了变化,并且做相应的接口变更工作
小提示:在更换库的时候,最好是把新下载的库更改一下名字跟旧的库一样并且直接替换原项目中的旧库(路径也不用变),这样的就不用做代码里面库引入等其他修改工作也不会修工程配置文件中你一把心机手动设置的各个Configurations Set库搜索路径。
3.重写项目中的.s后缀的汇编文件
(1) 参照iOS ABI Function Call Guide检查并且重写asm代码
小提示:iOS并不完全支持汇编的标准,所以必须参照Guide来写对应的asm。如果项目中没有使用汇编的话,就可以直接跳过这个步骤了
4.到了这个步骤后基本上就可以进行黑盒测试
(1)交给测试同学去做黑盒测试测试(这样虽然对测试的同学是一个很大的工作量,但是对于工程比较大的项目,从黑盒定位问题所在是代价成本是相对是比较低,除非有一套完善的自动化白盒或黑盒测试工具)
(2)当然了,接下来就是修复测试同学发现的兼容性和适配性bug
[1].代理的实现
例子1:
系统代理:- (NSInteger)numberOfRow;
实现代理:- (int)numberOfRow { return 0; } -> - (NSInteger)numberOfRow { return 0; }
例子2:
自定义代理:- (CGFloat)percentOfRate;
实现代理 :- (float)numberOfRow { return 0.f; } -> - (CGFloat)numberOfRow { return 0.f; }
小提示:其实这是很不好的写法,可以说是错误的写法,但是为什么在32位的情况下是可用的呢,其实仅仅是碰巧,32位下NSInteger是int,CGFloat是float,函数的“样子”才碰巧是一致的可以生效,到64位下代理就无法正常回调,问题就会显示出来了。
[2].修改了系统行为
例子:
系统实现:- (void)setAlpha:(CGFloat)alpha {...}
覆盖系统:- (void)setAlpha:(CGFloat)alpha { ; } -> - (void)setAlpha:(BOOL)alpha { [super setAlpha:alpha]; ...} 用新类继承该系统类,然后在新类里覆盖该方法
小提示:其实这样的行为是不可取的,也不是标准的做法,这样会破坏了UIKit该控件的系统架构,如果你确实这么做了但是没有发现问题,那你就为自己的工程埋下了一个地雷了,肯定会引发“后患”的,所以,这种情况下你应该自定义一个继承这个系统类的自定义类,然后再去覆盖它(记得是类,不是系统类的类别),这里为什么64位跟它有关,是因为每个架构都会使用宏来控制一定的系统行为。
(3)最后就修复测试同学发现的逻辑性错误的bug
[1].位运算
例子1:
long a = 1 << (32-1); -> long a = 1 << (sizeof(long)-1); 只把long类型的a最低位的值保留到最高位,其他位清掉,很明显在64位环境下逻辑是错误的
例子2:
NSUInteger max = ~0; -> NSUInteger max = ~((NSUInteger)0); 获取无符号整形类型的最大值
小提示:其实这些问题在编码的时候多考虑一下就好了,可以使用sizeof()运算符先探测一下,并且增加位长度发生变化的代码逻辑。
[2].数据类型转换
例子1:
b是B类的一个实例,而c是B里面的一个NSInteger类型的成员变量
int a = b.c; -> NSInteger a = age;
小提示:如果c是保存外部NSInteger的数据,而在64位下的c所存储的信息就比a多了,就有可能会丢失了c的数据
例子2:
unsigned int a = 0;
int b = -1;
unsigned long c = a+b;
或 ->
unsigned int a = 0;
int b = -1;
unsigned long c = (long)(int)(a+b);
或 ->
unsigned int a = 0;
int b = -1;
unsigned long c = (unsigned long)(int)(a+b);
小提示:这里的问题跟例子3是相似的
例子3:
unsigned int a = 0;
int b = -1;
long c = a+b;
或 ->
unsigned int a = 0;
int b = -1;
int c = a+b;
或 ->
unsigned long a = 0;
long b = -1;
long c = a+b;
或 ->
unsigned int a = 0;
int b = -1;
long c = (int)(a+b);
小提示:这里可以给出在64位上"c"和"d"的结果发生错误的根本原因:有符号数跟无符号数进行运算后得到的是无符号数,而无符号数直接向上扩展成“更大的”有符号数或无符号数就会出问题
例子4:
NSUInteger nua = 1;
NSInteger na = -1;
unsigned int ua = 1;
int ia = -1;
CGFloat cf = .1f;
float f = .1f;
NSLog(@"%u %d %u %d %f %f", nua, na, ua, ia, cf, f);
->
NSLog(@"%lu %ld %u %d %lf %f", (unsigned long)nua, (long)na, ua, ia, (double)cf, f);
小提示:这里可以很明显的发现,为什么NSUInteger,NSInteger,CGFloat需要强制转换,而其他不用呢,因为格式化字符串只认识“d”是“int”而不是“NSInteger”而且把会变长的数据类型都使用“更大的数据类型”不但可以避免发生数据丢失,同时也能保证兼容32位和64位平台。而且,苹果官方也不支持把“NS”,“CG”等前缀的变量直接进行格式化解析
[3].指针出错:
例子:
void func(){...}
int *ptr = func;
ptr();
->
void func(){...}
long *ptr = func;
if(sizeof(func)==sizeof(long)) ((long)ptr)();
else if(sizeof(func)==sizeof(long)) ((int)ptr)();
else return;
小提示:在64位下的所有指针都是64位的,为了兼容64和32,可以使用long来保存指针类型,而不需要进行类型转换,因为long在32位下是4个字节,在64位下是8个字节。当然这只是个偷懒的方法,还是使用我建议的写法吧
[4].变长函数转定长函数
例子:
void(myLog*)(NSString *, int, int) = (void(*)(NSString *, int, int))NSLog;
myLog(@"%d",1);
->
#define myLog(format, INT1, INT2) NSLog((NSString *)(format), (int)(INT1), (int)(INT2));
myLog(@"%d %d",1,1);
小提示:在模拟器上是不是没发现然后可以看到“1,1”?那就对了,这个问题在模拟器上是不会暴露出来的,同时编译器也不会报错,但就是在64位上就会有不可预知的错误发生。因为在64位上的runtime中,变参的函数和固定参数的函数的调用方式已经不一样了
[5].如果还有其他未解之谜的话可以看一下是不是其他数据类型变化,可以参考“底部”的参考来源
参考来源:64-Bit Transition Guide for Cocoa Touch
本人刚刚参与实习,望大家不舍自己的个人建议,本人很愿意跟大家交流 ^-^