歡迎加入QQ討論群258996829
Swift 頭像
蘋果5袋
5
Swift

iOS工程適配64-bit經(jīng)驗分享

發(fā)布時間:2015-01-06 20:14  回復(fù):0  查看:4142   最后回復(fù):2015-01-06 20:14  

終究還是來了。Apple下發(fā)了支持64位的最后通牒:

As we announced in October, beginning February 1, 2015 new iOS apps submitted to the App Store must include 64-bit support and be built with the iOS 8 SDK. Beginning June 1, 2015 app updates will also need to follow the same requirements.

早應(yīng)該做的適配終于要開始動工了,苦了64位的CPU運行了這么久32位的程序。前段時間公司項目完成了64-bit包的適配,本沒那么復(fù)雜的事被無數(shù)不標準的老代碼攪和的不輕,總結(jié)幾個Tip共勉。

Tips

拒絕基本數(shù)據(jù)類型和隱式轉(zhuǎn)換

首當(dāng)其沖的就是基本類型,比如下面4個類型在32-bit和64-bit下分別是多長呢?

1
2
3
4
size_t s1 = sizeof(int);
size_t s2 = sizeof(long);
size_t s3 = sizeof(float);
size_t s4 = sizeof(double);

32-bit下:4, 4, 4, 8;64-bit下:4, 8, 4, 8

(PS: 這個結(jié)果隨編譯器,換其他平臺可不一定)

它們的長度變化可能并非我們對64-bit長度加倍的預(yù)期,所以說,程序中出現(xiàn)sizeof的代碼多看兩眼。而且,除非你明確知道自己在做什么,應(yīng)該使用下面的類型代替基本類型:

  • int -> NSInteger

  • unsigned -> NSUInteger

  • float -> CGFloat

  • 動畫時間 -> NSTimeInterval

這些都是SDK中定義的類型,而我們大部分時間都在跟SDK的API們打交道,使用它們能將類型轉(zhuǎn)換的影響降低很多。

再比如說下面的代碼:

1
2
3
4
NSArray *items = @[@1, @2, @3];
for (int i = -1; i < items.count; i++) {
    NSLog(@"%d", i);
}

結(jié)果是,for循環(huán)一次都沒有進。

數(shù)組的count是NSUInteger類型的,-1與其比較時隱式轉(zhuǎn)換成NSUInteger,變成了一個很大的數(shù)字:

1
2
3
4
(lldb) p i
(int) $0 = -1
(lldb) p (NSUInteger)i
(NSUInteger) $1 = 18446744073709551615

這和64-bit到?jīng)]啥關(guān)系,想要說明的是,這種隱式轉(zhuǎn)換也需要小心,一定要注意和這個變量相關(guān)的所有操作(賦值、比較、轉(zhuǎn)換)

老式for循環(huán)可以考慮寫成:

1
for (NSUInteger index = 0; index < items.count; index++) {}

當(dāng)然,數(shù)組遍歷還是更推薦用for-in或block版本的,它們之間的比較可以回顧下這篇文章。

使用新版枚舉

和上面的原因差不多,枚舉應(yīng)該使用新版的寫法:

1
2
3
4
5
6
typedef NS_ENUM(NSInteger, UIViewAnimationCurve) {
    UIViewAnimationCurveEaseInOut,
    UIViewAnimationCurveEaseIn,
    UIViewAnimationCurveEaseOut,
    UIViewAnimationCurveLinear
};

不僅能為枚舉值指定類型,而且當(dāng)賦值賦錯類型時,編譯器還會給出警告,沒理由不用這種寫法。

替代Format字符串

適配64-bit時,你是否遇到了下面的惡心寫法:

1
2
NSArray *items = @[@1, @2, @3];
NSLog(@"數(shù)組元素個數(shù):%lu", (unsigned long)items.count);

一般情況下,利用NSNumber的@語法糖就可以解決:

1
2
NSArray *items = @[@1, @2, @3];
NSLog(@"數(shù)組元素個數(shù):%@", @(items.count));

同理,int轉(zhuǎn)string也可以:

1
2
NSInteger i = 10086;
NSString *string = @(i).stringValue;

當(dāng)然,如需要%.2f這種Format就不適用了。

64-bit下的BOOL

32-bit下,BOOL被定義為signed char,@encode(BOOL)的結(jié)果是'c'

64-bit下,BOOL被定義為bool,@encode(BOOL)結(jié)果是'B'

更直觀的解釋是:

1
2
3
4
(lldb) p/t (signed char)7
(BOOL) $0 = 0b00000111 (YES)
(lldb) p/t (bool)7
(bool) $1 = 0b00000001 (YES)

32-bit版本的BOOL包括了256個值的可能性,還會引起一些坑,像這篇文章所說的。而64-bit下只有0(NO),1(YES)兩種可能,終于給BOOL正了名。

不直接取isa指針

編譯器已經(jīng)默認禁用了這種使用,isa指針在32位下是Class的地址,但在64位下利用bits mask才能取出來真正的地址,若真需要,使用runtime的object_getClass 和object_setClass方法。關(guān)于64位下isa的講解可以看這篇文章

解決第三方lib依賴和lipo命令

以源碼形式出現(xiàn)在工程中的第三方lib,只要把target加上arm64編譯就好了。

惡心的就是直接拖進工程的那些靜態(tài)庫(.a)或者framework,就需要重新找支持64-bit的包了。這時候就能看出哪些是已無人維護的lib了,是時候找個替代品了(比如我全網(wǎng)找不到工程中用到的一個音頻庫的64位包,終于在一個哥們的github上找到,哭著給了個star- -)

打印Mach-O文件支持的架構(gòu)

如何看一個可執(zhí)行文件是不是支持64-bit呢?

使用lipo -info命令,比如看看UIKit支持的架構(gòu):

1
2
3
// 當(dāng)前在Xcode Frameworks目錄
sunnyxx$ lipo -info UIKit.framework/UIKit
Architectures in the fat file: UIKit.framework/UIKit are: arm64 armv7s

想看的更詳細的信息可以使用lipo -detailed_info:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sunnyxx$ lipo -detailed_info UIKit.framework/UIKit 
Fat header in: UIKit.framework/UIKit
fat_magic 0xcafebabe
nfat_arch 2
architecture arm64
    cputype CPU_TYPE_ARM64
    cpusubtype CPU_SUBTYPE_ARM64_ALL
    offset 4096
    size 16822272
    align 2^12 (4096)
architecture armv7s
    cputype CPU_TYPE_ARM
    cpusubtype CPU_SUBTYPE_ARM_V7S
    offset 16826368
    size 14499840
    align 2^12 (4096)

當(dāng)然,還可以使用file命令:

1
2
3
4
sunnyxx$ file UIKit.framework/UIKit 
UIKit.framework/UIKit: Mach-O universal binary with 2 architectures
UIKit.framework/UIKit (for architecture arm64):Mach-O 64-bit dynamically linked shared library
UIKit.framework/UIKit (for architecture armv7s):Mach-O dynamically linked shared library arm

上述命令對Mach-O文件適用,靜態(tài)庫.a文件,framework中的.a文件,自己app的可執(zhí)行文件都可以打印下看看。

合并多個架構(gòu)的包

如果,我們有MyLib-32.a和MyLib-64.a,可以使用lipo -create命令合并:

1
sunnyxx$ lipo -create MyLib-32.a MyLib-64.a -output MyLib.a

支持64-bit后程序包會變大么?

會,支持64-bit后,多了一個arm64架構(gòu),理論上每個架構(gòu)一套指令,但相比原來會大多少還不好說,我們這里增加了大概50%,還有聽說會增加一倍的。

一個lib包含了很多的架構(gòu),會打到最后的包里么?

不會,如果lib中有armv7, armv7s, arm64, i386架構(gòu),而target architecture選擇了armv7s, arm64,那么只會從lib中l(wèi)ink指定的這兩個架構(gòu)的二進制代碼,其他架構(gòu)下的代碼不會link到最終可執(zhí)行文件中;反過來,一個lib需要在模擬器環(huán)境中正常link,也得包含i386架構(gòu)的指令。

Checklist

最后列一下官方文檔中的注意點:

  • 不要將指針強轉(zhuǎn)成整數(shù)

  • 程序各處使用統(tǒng)一的數(shù)據(jù)類型

  • 對不同類型的整數(shù)做運算時一定要注意

  • 需要定長變量時,使用如int32_t, int64_t這種定長類型

  • 使用malloc時,不要寫死size

  • 使用能同時適配兩個架構(gòu)的格式化字符串

  • 注意函數(shù)和函數(shù)指針(類型轉(zhuǎn)換和可變參數(shù))

  • 不要直接訪問Objective-C的指針(isa)

  • 使用內(nèi)建的同步原語(Primitives)

  • 不要硬編碼虛存頁大小

  • Go Position Independent

References

https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40013501-CH1-SW1

http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html

http://www.bignerdranch.com/blog/64-bit-smorgasbord/

http://www.bignerdranch.com/blog/bools-sharp-corners/


您還未登錄,請先登錄

熱門帖子

最新帖子

?