iOS應(yīng)用如何實現(xiàn)64位的支持

蘋果在2014年10月20號發(fā)布了一條消息:從明年的二月一號開始,提交到App Store的應(yīng)用必須支持64-bit。詳細(xì)消息地址為:https://developer.apple.com/news/?id=10202014a

那們我們應(yīng)該如何開始著手讓自己的App支持64-Bit呢?

基本知識

從iPhone 5S的A7 CPU開始到剛剛發(fā)布的iPhone 6(A8 CPU)都已經(jīng)支持64-bit ARM 架構(gòu)。關(guān)于64-bit的介紹詳見維基百科。知乎上有很多關(guān)于蘋果使用A7,A8芯片的討論,可以參考iPhone 6 的 Apple A8 芯片對比 Apple A7 提升明顯嗎?,iPhone 5s 配備的 A7 處理器是 64 位,意味著什么?

Xcode 5.0.1開始支持編譯32-bit和64-bit的Binary

同時支持32-bit和64-bit,我們需要選擇的minimum deployment target為 iOS 5.1.1

64-bit的Binary必須運行在支持64-bit的CPU上,并且最小的OS版本要求是 7.0.3

關(guān)于Xcode “Build Setting”中的Architectures參數(shù)問題

Architectures:你想支持的指令集。(支持指令集是通過編譯生成對應(yīng)的二進(jìn)制數(shù)據(jù)包實現(xiàn)的,如果支持的指令集數(shù)目有多個,就會編譯出包含多個指令集代碼的數(shù)據(jù)包,造成最終編譯的包很大。)

Valid architectures:即將編譯的指令集。(Valid architectures 和 Architecture兩個集合的交集為最終編譯生成的版本)

Build Active Architecture Only:是否只編譯當(dāng)前設(shè)備適用的指令集(如果這個參數(shù)設(shè)為YES,使用iPhone 6調(diào)試,那么最終生成的一個支持ARM64指令集的Binary。一般在DEBUG模式下設(shè)為YES,RELEASE設(shè)為NO)

關(guān)于指令集如下參考:

ARMv8/ARM64: iPhone 6(Plus), iPhone 5s, iPad Air(2), Retina iPad Mini(2,3)

ARMv7s: iPhone 5, iPhone 5c, iPad 4

ARMv7: iPhone 3GS, iPhone 4, iPhone 4S, iPod 3G/4G/5G, iPad, iPad 2, iPad 3, iPad Mini

ARMv6: iPhone, iPhone 3G, iPod 1G/2G

對于支持64-bit,我們可以設(shè)置Architectures為Standard architectures,在最新的Xcode 6上,它包括 armv7和arm64。

讓App支持32-bit和64-bit基本步驟

確保Xcode版本號>=5.0.1

更新project settings, minimum deployment target >= 5.1.1

改變Architectures為Standard architectures(include 64-bit)

運行測試代碼,解決編譯warnings and errors,對照本文檔或者官方文檔64-Bit Transition Guide for Cocoa Touch對相應(yīng)地方做出修改。(編譯器不能告訴我們一切)

在真實的64-bit機器上測試

使用Instruments查看內(nèi)存使用問題

64-bit主要的變化

64-bit運行時環(huán)境和32-bit運行時環(huán)境主要有以下兩點的不同:

數(shù)據(jù)類型的改變

方法調(diào)用上的改變

數(shù)據(jù)類型的改變

整型數(shù)據(jù)類型的變化如下:

關(guān)于字節(jié)對齊的概念可以參考如下鏈接:http://blog.csdn.net/21aspnet/article/details/6729724#comments

浮點型類型的改變?nèi)缦?

數(shù)據(jù)類型的改變可能會為我們的程序帶來這些影響:

增加內(nèi)存壓力

64-bit到32-bit數(shù)據(jù)之間的相互轉(zhuǎn)化

計算可能產(chǎn)生不同的結(jié)果

當(dāng)把一個值從大的數(shù)據(jù)類型拷貝到小的數(shù)據(jù)類型,數(shù)據(jù)可能被截斷。(NSInteger -> int)

方法調(diào)用上的改變

基于32-bit的CPU和基于64-bit上的CPU有不同數(shù)量的寄存器,在方法調(diào)用上有不同的協(xié)議。因此32-bit和64-bit在匯編層級上是不同的。如果我們在程序中不使用匯編編程,調(diào)用協(xié)議很少會遇到。

如何編寫健壯的64-bit代碼

根據(jù)上述改變,官方文檔64-Bit Transition Guide for Cocoa Touch給出如下7步:

不要將長整型long賦值給整型int (64-bit上會導(dǎo)致數(shù)據(jù)丟失)

不要將指針類型pointer賦值給整型int (64-bit導(dǎo)致地址數(shù)據(jù)丟失)

留意數(shù)值計算(掩碼計算,無符號整數(shù)和有符號整數(shù)同時使用等)

留意對齊方法帶來的變化

32-bit到64-bit之間數(shù)據(jù)轉(zhuǎn)化(通過網(wǎng)絡(luò)傳遞的用戶數(shù)據(jù),可能同時存在于32-bit和64-bit的環(huán)境下)

重寫匯編代碼

不要在可變參數(shù)方法和不可變參數(shù)方法之前進(jìn)行強制轉(zhuǎn)化

在LLVM編譯器中,枚舉類型也可以定義枚舉的大小。我們在使用中,指派枚舉值到一個變量時,應(yīng)該使用適當(dāng)?shù)臄?shù)據(jù)類型。

不要將指針類型pointer賦值給整型int

int a = 5;

int *c = &a;

/* 32-bit下正常,64-bit下錯誤。最新的Xcode6.0編譯提示警告:'Cast to int* for smaller integer type int'*/

int *d = (int *)((int)c + 4);

/* 正確, 指針可以直接增加*/

int *d = c + 1;

如果我們一定要把指針轉(zhuǎn)化為整型,可以把上述代碼改為:

/* 32-bit和64-bit都正常。*/

int *d = (int *)((uintptr_t)c + 4);

查看uintptr_t定義為typedef unsigned long uintptr_t;

保持?jǐn)?shù)據(jù)類型一致

方法使用時,入?yún)ⅲ鰠⒑唾x值都需要注意保持?jǐn)?shù)據(jù)類型一致。在iOS App中尤其要注意以下幾個類型的正確使用:

long

NSInteger

CFIndex

size_t

在32-bit和64-bit下,fpos_t和off_t都是64 bits的數(shù)據(jù)大小,永遠(yuǎn)不要把它們指向int整型。

long PerformCalculation(void);

int c = PerformCalculation(); // 錯誤 64-bit上數(shù)據(jù)將被截取

long y = PerformCalculation(); // 正確

int PerformAnotherCalculation(int input);

long i = LONG_MAX;

int x = PerformCalculation(i); // 錯誤

int ReturnMax()

{

return LONG_MAX; // 錯誤

}

Cocoa中常見的數(shù)據(jù)類型轉(zhuǎn)化問題

NSInteger : 在32-bit和64-bit下有分別的定義:

#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64

typedef long NSInteger;

#else

typedef int NSInteger;

#endif

我們永遠(yuǎn)不應(yīng)該假設(shè)NSInteger和int是一樣大的,下面的例子在使用中就需要注意:

使用NSNumber對象轉(zhuǎn)化時

使用NSCoder編解碼的時候,如果在64-bit設(shè)備下對NSInteger編碼,在32-bit設(shè)備下對NSInteger解碼。解碼時如果值的大小超過了32-bit,這個時候就會出現(xiàn)異常

Famework中使用NSInteger定義的一些常量

CGFloat: 和NSInteger一樣有不同的定義

typedef CGFLOAT_TYPE CGFloat;

#if defined(__LP64__) && __LP64__

# define CGFLOAT_TYPE double

#else

# define CGFLOAT_TYPE float

#endif

下面給出錯誤示范:

CGFloat value = 200.0;

CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &value); //64-bit下出現(xiàn)錯誤

CGFloat value = 200.0;

CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &value); //正確

整型數(shù)值計算問題

關(guān)于C語言的符號位擴展可參考資料為:http://blog.163.com/shi_shun/blog/static/237078492010651063936/

我們直接來看例子:

int a = -2;

unsigned int b = 1;

long c = a + b;

long long d = c;

printf("%lld\n", d);

問題:這段代碼在32-bit下運行結(jié)果符合我們的預(yù)期,輸出為 -1(0xffffffff)。在64-bit下運行結(jié)果為:4294967295 (0x00000000ffffffff)。

原因:一個有符號的值和一個同樣精度的無符號的值相加結(jié)果是無符號的。這個無符號的結(jié)果被轉(zhuǎn)換到更高精度的數(shù)值上時采用零擴展。

解決方案:把變量b換成長整型long

創(chuàng)建數(shù)據(jù)結(jié)構(gòu)時使用合適的數(shù)據(jù)大小

C99提供了內(nèi)置的數(shù)據(jù)類型保證了一致的數(shù)據(jù)大小,即使底層的硬件結(jié)構(gòu)不同。在某些case下,我們知道數(shù)據(jù)是一個固定的大小或者一個特定的變量擁有一個有限的取值范圍。這個時候,我們應(yīng)該選擇特定的類型以避免浪費內(nèi)存。

類型如下:

永遠(yuǎn)不要使用malloc去為變量申請?zhí)囟▋?nèi)存的大小,改為使用sizeof來獲取變量或者結(jié)構(gòu)體的大小。

另外我們還需要注意修改格式化字符串來同時支持32-bit和64-bit。

小心處理方法和方法指針

int fixedFunction(int a, int b);

int variadicFunction(int a, ...);

int main

{

int value2 = fixedFunction(5,5);

int value1 = variadicFunction(5,5);

}

上述兩個方法中,在32-bit下使用相同的指令讀取參數(shù)的數(shù)據(jù),但是在64-bit上,是使用完全不同的協(xié)議來編譯的。

如果在代碼中傳遞方法指針,應(yīng)該保證方法調(diào)用的協(xié)議是一致的。永遠(yuǎn)不要將一個可變參數(shù)的方法轉(zhuǎn)化成固定參數(shù)的方法。

int MyFunction(int a, int b, ...);

int (*action)(int, int, int) = (int (*)(int, int, int)) MyFunction;

action(1,2,3); // 錯誤示范

上述錯誤的寫法,編譯器是不會提示警告或者錯誤的,并且在模擬器中也不會暴露出問題來。在發(fā)布自己的App前,一定記得要使用真機去測試。

總結(jié)

在支持64-bit過程中,應(yīng)該按照Apple文檔中提供的7個步驟完整檢查項目工程。如果工程中涉及到大量的C或者C++代碼,在支持64-bit中要更加謹(jǐn)慎。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容