iOS研發適配:32bit/64bit & iOS10 - iOS13

在 iOS 開發的過程當中,版本和機型適配是你們都會遇到的「坑」。若是不注意不一樣版本API和編譯器的差別,那麼只會Bug頻出,屢屢返工啦。css

參考資料總結了面對不一樣位數的機器、不一樣 iOS 版本時可能遇到的「坑」,在學習整理、避免本身踩坑的同時也但願可以及時輸出分享,爲你們創造價值。web

歡迎你們留言討論,一塊兒進步(〃’▽’〃)!數組


目錄

1. 32 位 / 64 位設備適配

1.1  32 位、64 位的概念
1.2  哪些 iOS 設備是 32 位 & 怎樣判斷
1.3  32 位和 64 位的不一樣 & 如何解決
	1.3.1 數據基本類型長度和對齊方式
	1.3.2 BOOL 值的編譯差別
	1.3.3 指令集的編譯差別
	1.3.4 64bit 編譯時的容錯優化
複製代碼

2. iOS10 -> iOS13 適配一覽

2.1 iOS13
	2.1.1 DarkMode
	2.1.2 模態彈出默認交互改變
	2.1.3 私有 KVC 使用會 crash
	2.1.4 App 啓動過程當中,部分 View 會沒法實時獲取到 frame
	2.1.5 DeviceToken 格式變化
	2.1.6 UIWebView 的廢棄
2.2 iOS11
	2.2.1 UIScrollView: contentInsetAdjustmentBehavior
	2.2.2 UITableView: Self-Sizing
	2.2.3 受權相關
2.3 iOS10
	2.3.1 跳轉到app內的隱私數據設置頁面
	2.3.2 UIFeedbackGenerator 觸覺反饋
複製代碼

1、32 位 / 64 位設備適配

1.1 32位、64位的概念

「位」:CPU 中通用寄存器的數據寬度安全

相對於 32 位而言,64 位的 CPU 位寬增長一倍,使其可以處理更多更精確的數據,所以有必定加快數據處理的做用。bash

1.2 哪些 iOS 設備是32位 & 怎樣判斷

32 bit:iphone5 及以前架構

64 bit:iPhone5s 及以後(iPad Air以後)app

判斷是多少位設備:兩種方法iphone

///* 方法一 *///
    if (sizeof(void *) == 4) {
        NSLog(@"32-bit App");
    } else if (sizeof(void *) == 8) {
        NSLog(@"64-bit App");
    }
    ///* 方法二 *///
#if __LP64__
#define kNum 64
#else
#define kNum 32
#endif
    NSLog(@"kNum: %d",kNum);
複製代碼

__LP64__ 是由預處理器定義的宏,表明當前操做系統是 64 位ide

1.3 32位和64位的不一樣 & 如何解決

1.3.1 數據基本類型長度和對齊方式

如表所示爲 32bit、64bit 的數據類型長度和對齊方式,主要不一樣在於

  • long 長整數類型
  • pointer 指針類型
  • NSInteger:32bit 時與 int 長度相同,64 bit 時與 long 長度相同

開發Tipspost

(1) 常規不用 int,用 NSInteger;大數(>=10位) 用 long long

NSInteger 根據位數自動返回不一樣類型,適配不一樣位數的存儲方式,使更高位的 CPU 能發揮出性能。 在 iOS 12.4 > usr/include > objc > NSObjCRuntime.h 中對 NSInteger 定義爲:

#if __LP64__ || 0 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
複製代碼

32bit 時,NSInteger 能表示 int 的 4 字節:-21 4748 3648 ~ 21 4748 3647 (2的31次方 - 1)

64bit 時,NSInteger 能表示 long 的 8 字節:-922 3372 0368 5477 5808 ~ 922 3372 0368 5477 5807(2的63次方 - 1)

雖然 64bit 時範圍大,但涉及到設備適配,當要存儲的數 >= 10 位時,建議使用 long long 類型,來保證在 32 位機器上不會崩潰。

Eg. 涉及到時間戳爲毫秒的狀況下,定義相應字段是 long long類型,經過 longLongValue 獲取到值,就不會存在溢出的狀況

(2) 注意不一樣數據類型間的轉換,包括隱式轉換

  • 不要把長整型數據 (long) 賦值給整型 (int)

在 32 位系統沒有問題(long 和 int 都是 4 字節); 在 64 位系統,long 8 字節,int 4 字節,將 long 值賦予 int 將致使數據丟失

  • 不要將指針類型 (pointer) 賦值給整型 (int) 在 32 位系統沒有問題(Pointer 和 int 都是4字節); 在 64 位系統 Pointer 8字 節,int 4 字節,將 Pointer 值賦予 int 將致使地址數據丟失
  • 注意隱式轉換:
NSArray *items = @[@1, @2, @3];
for (int i = -1; i < items.count; i++) {
    NSLog(@"%d", i);
}
複製代碼

數組的count是NSUInteger類型的,-1與其比較時隱式轉換成NSUInteger,變成了一個很大的數字。 所以,老式for循環建議寫成:

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

}
複製代碼

(3) 注意和數位相關的數值計算

好比掩碼技術,若是使用一個 long 類型的掩碼,轉到 64 位系統後高位都是 0,計算出來的結果可能不符合預期。還有無符號整數和有符號整數的混用等也存在問題。

(4) 注意對齊方式帶來的變化,多使用 sizeof 幫助計算

若是在 32 位系統上定義一個結構包含兩個 long 類型,第二個 long 數值的偏移地址是 4,能夠經過結構地址 +4 的方式獲取,可是在 64 位系統第二個 long 數值的偏移地址是 8。

(5) NSLog、[NSString stringWithFormat:] 的格式

NSInteger aInt = 1804809223;
///> 下面的代碼在64Bit會有編譯器警告提示需強轉long,但在32Bit無警告
NSLog(@"%i", aInt);
    
///> 最好的解決方法:儘可能使用NSNumber,輕鬆適配32&64
NSLog(@"Number is %@", @(aInt));
///> 另一種解決方法:根據警告強轉
NSLog(@"Number is %ld", (long)aInt);
複製代碼

1.3.2 BOOL 值的編譯差別

在 iOS 12.4 > usr/include > objc > objc.h 中:

# if TARGET_OS_OSX || 0 || (TARGET_OS_IOS && !__LP64__ && !__ARM_ARCH_7K)
# define OBJC_BOOL_IS_BOOL 0
# else
# define OBJC_BOOL_IS_BOOL 1
# endif

#if OBJC_BOOL_IS_BOOL
    typedef bool BOOL;
#else
# define OBJC_BOOL_IS_CHAR 1
    typedef signed char BOOL; 
#endif
複製代碼

由代碼可知: 在 64 位 iOS、iWatch上,BOOL 是 bool 類型(非 0 即真) 在MacOS、32 位 iOS 上,BOOL 是 signed char 類型(1 個字節, -128 ~ 127)

開發Tips

  • NSNumber/NSString等類型,對象不能強轉BOOL,要經過boolValue(32bit和64bit都會出問題)
NSMutableDictionary *testDict = [NSMutableDictionary dictionary];
[testDict setObject:[NSNumber numberWithBool:NO] forKey:@"key"];
BOOL testValue = [testDict objectForKey:@"key"];
if (testValue) {
    NSLog(@"[NSNumber numberWithBool:NO]判斷爲真"); ///< 32bit和64bit都會走到這行代碼中,得出錯誤結果
} else {
    NSLog(@"[NSNumber numberWithBool:NO]判斷爲假");
}

testValue = [[testDict objectForKey:@"key"] boolValue];
if (testValue) {
    NSLog(@"[NSNumber numberWithBool:NO]判斷爲真");
} else {
    NSLog(@"[NSNumber numberWithBool:NO]判斷爲假"); ///< 使用正確的方式後,才能走到這行代碼,獲得正確結果
}
複製代碼

如代碼示例,第一個testValue沒使用boolValue,判斷爲真;第二個testValue是正確用法,使用了booValue,判斷爲假。

緣由:NSNumber強轉BOOL,會取指針的低8位(1個字節),非全0即判真,所以得出的結果是不正確的。

同理,也不能用NSNumber來設置BOOL屬性:

[view performSelector:@selector(setHidden:) withObject:@(NO)];
複製代碼

這樣的代碼是不生效的,由於NSNumber並無轉換成正確的BOOL值,建議直接使用setHidden

  • 數字不能直接用來作BOOL判斷(32bit會出問題)
BOOL a = 200 + 56;
if (a) {
    NSLog(@"BOOL a = 256判斷爲真"); ///< 64bit會走到這行代碼
} else {
    NSLog(@"BOOL a = 256判斷爲假"); ///< 32bit會走到這行代碼
}
複製代碼

32bit下編譯器才提示會被轉爲0,而且判斷爲假,緣由是signed char取低8位(一字節),全爲0則判假;

64bit下bool非0即1,指針地址非0,所以判真

1.3.3 指令集的編譯差別

指令集

  • ARM 處理器的指令集(手機)
    • Armv6
    • Armv7 / Armv7s(真機 32 位處理器)
    • Arm64:iphone5s 以上(真機 64 位處理器)
  • Mac 處理器的指令集(電腦)
    • i386:針對 Intel 通用微處理器 32 位處理器(模擬器 32 位處理器)
    • x86_64:針對 x86架構的 64 位處理器(模擬器 64 位處理器)

Xcode Build Setting 中指令集相關選項釋義
(1) Architectures:工程編譯後支持的指令集類型。
支持的指令集越多,指令集代碼的數據包越多,對應生成二進制包就越大,也就是 ipa 包會變大。
(2) Valid Architectures:限制可能被支持的指令集的範圍
編譯出哪一種指令集的包,將由 Architectures 與 Valid Architectures 的交集來肯定
(3) Build Active Architecture Only:指定是否只對當前鏈接設備所支持的指令集編譯
當設爲 YES,只對支持的指令集版本編譯,編譯速度更快;當設爲 NO,會編譯全部的版本
通常 debug 時候可選爲 YES,release 時爲 NO 以適應不一樣設備。

開發Tips

  • 若是在代碼中嵌入了彙編代碼,須要參考 64 位系統的指令集(Arm64)重寫彙編代碼。

1.3.4 64位編譯時的容錯優化

解決:避免依賴於 64bit 的編譯優化 在 32bit 真機上 NSLog 中傳入 NULL 會 crash

2、iOS10 -> iOS13 適配一覽

判斷版本: if (@available(iOS 11.0, *))

2.1 iOS 13

參考:WWDC 2019 - Videos - Apple Developer

2.1.1 DarkMode

Apps on iOS 13 are expected to support dark mode Use system colors and materials Create your own dynamic colors and images Leverage flexible infrastructure
審覈會關注是否適配黑夜模式

  • 適配黑夜模式:
if (@available(iOS 13.0, *)) {
	if (darkMode) {
        [UIApplication sharedApplication].keyWindow.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
	} else {
        [UIApplication sharedApplication].keyWindow.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
    }
}
複製代碼
  • 在VC關閉/打開DarkMode:
if (@available(iOS 13.0, *)) {
	self.view.overrideUserInterfaceStyle = UIUserInterfaceStyleLight; // Disable dark mode
	self.view.overrideUserInterfaceStyle = UIUserInterfaceStyleDark; // Enable dark mode
}
複製代碼
  • 全局關閉: 在 Info.plist 設置 UIUserInterfaceStyle 屬性爲 Light;
  • 內嵌的 WebView 須要手動修改 css 樣式才能適配

2.1.2 模態彈出默認交互改變

iOS 13的 presentViewController 默認有視差效果,模態出來的界面默認都下滑返回。 新效果動圖

緣由是 iOS13 中 modalPresentationStyle 的默認值改成 UIModalPresentationAutomatic,iOS13 以前默認是 UIModalPresentationFullScreen

若是想改回原來的動效須要手動加上這行代碼:

self.modalPresentationStyle = UIModalPresentationFullScreen;
複製代碼

2.1.3 私有KVC使用會crash

在 iOS13 中運行如下代碼會crash:

[_textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
[_textField setValue:[UIFont systemFontOfSize:14] forKeyPath:@"_placeholderLabel.font"];	
複製代碼

緣由:私有API內部屬性有檢索校驗,一旦命中馬上crash

解決方式:

_textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"姓名" attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14],NSForegroundColorAttributeName:[UIColor redColor]}];
複製代碼

2.1.4 App啓動過程當中,部分View會沒法實時獲取到frame

多是爲了優化啓動速度,App 啓動過程當中,部分View可能沒法實時獲取到正確的frame

只有等執行完 UIViewController 的 viewDidAppear 方法之後,才能獲取到正確的值,在 viewDidLoad 等地方 frame Size 爲 0

2.1.5 DeviceToken格式變化

#include <arpa/inet.h>
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    if (![deviceToken isKindOfClass:[NSData class]]) return;
    const unsigned *tokenBytes = (const unsigned *)[deviceToken bytes];
    NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
                          ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
                          ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
                          ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
    NSLog(@"deviceToken:%@",hexToken);
}
複製代碼

2.1.6 UIWebView的廢棄

UIWebView - UIKit | Apple Developer Documentation

iOS13 UIWebView Support

2.2 iOS 11

2.2.1 UIScrollView: automaticallyAdjustsScrollViewInsets -> contentInsetAdjustmentBehavior

iOS 11 廢棄了 UIViewController 的 automaticallyAdjustsScrollViewInsets 屬性,新增了 contentInsetAdjustmentBehavior 屬性,當超出安全區域時系統會自動調整 SafeAreaInsets,進而影響 adjustedContentInset 在iOS11中 adjustedContentInset 決定 tableView 內容與邊緣距離,因此須要設置 UIScrollView 的 contentInsetAdjustmentBehavior 屬性爲 Never,避免沒必要要的自動調整。

if (@available(iOS 11.0, *)) {
    // 做用於指定的UIScrollView
    self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    // 做用於全部的UIScrollView
    UIScrollView.appearance.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
else {
    self.automaticallyAdjustsScrollViewInsets = NO;
}
複製代碼

2.2.2 UITableView: Self-Sizing

iOS11 開始 UITableView 開啓了自動估算行高(Self-Sizing),Headers、footers、cells 的 estimated 高度默認值(estimatedRowHeightestimatedSectionHeaderHeightestimatedSectionFooterHeight)都從 iOS11 以前的 0 改變爲 UITableViewAutomaticDimension 若是不實現-tableView: viewForFooterInSection:-tableView: viewForHeaderInSection:,那麼三個高度估算屬性的改變會致使高度計算不對,產生空白。 解決方法是實現對應方法或把這三個屬性設爲 0:

self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;
複製代碼

TableView 和 SafeArea(安全區) 有如下幾點須要注意:

  • SeparatorInset 自動關聯 SafeAreaInsets,所以默認狀況下,表視圖的內容會避免其根 VC 在安全區域的插入。
  • UITableviewCell 和 UITableViewHeaderFooterView的 contentview 在安全區域內,所以應該始終在 contentview 中使用add-subviews操做。
  • 全部的 headers 和 footers 都應該使用 UITableViewHeaderFooterView,包括 table headers 和 footers、section headers 和 footers。

2.2.3 受權相關

在 iOS 11 中必須支持 When In Use 受權模式(NSLocationWhenInUseUsageDescription)

爲了不開發者只提供請求 Always 受權模式這種狀況,加入此限制,若是不提供 When In Use 受權模式,那麼 Always 相關受權模式也沒法正常使用。

若是要支持老版本,即 iOS 11 如下系統版本,那麼建議在 info.plist 中配置全部的 Key(即便 NSLocationAlwaysUsageDescription在 iOS 11及以上版本再也不使用):

NSLocationWhenInUseUsageDescription
NSLocationAlwaysAndWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
NSLocationAlwaysAndWhenInUseUsageDescription  // 爲iOS 11中新引入的一個 Key。
複製代碼

2.3 iOS10

2.3.1 跳轉到app內的隱私數據設置頁面

NSURL *urlLocation = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([[UIApplication sharedApplication] canOpenURL:urlLocation]){
    if (@available(iOS 10.0, *)) {
        [[UIApplication sharedApplication] openURL:urlLocation options:@{} completionHandler:nil];
    } else {
        [[UIApplication sharedApplication] openURL:urlLocation];
    }
}
複製代碼

2.3.2 UIFeedbackGenerator 觸覺反饋

相似於付款成功的震動反饋,只在 iOS 10 以上可用,所以須要判斷if (@available(iOS 10.0, *))

譯 如何使用 UIFeedbackGenerator 讓應用支持 iOS 10 的觸覺反饋 - iOS - 掘金


擴展閱讀:

iOS開發同窗的arm64彙編入門 - 劉坤的技術博客 64位和32位的寄存器和彙編的比較 - jmp esp - CSDN博客 iOS標記指針(Tagged Pointer)技術 - 掘金

相關文章
相關標籤/搜索