在 iOS 開發的過程當中,版本和機型適配是你們都會遇到的「坑」。若是不注意不一樣版本API和編譯器的差別,那麼只會Bug頻出,屢屢返工啦。css
參考資料總結了面對不一樣位數的機器、不一樣 iOS 版本時可能遇到的「坑」,在學習整理、避免本身踩坑的同時也但願可以及時輸出分享,爲你們創造價值。web
歡迎你們留言討論,一塊兒進步(〃’▽’〃)!數組
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.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 觸覺反饋
複製代碼
「位」:CPU 中通用寄存器的數據寬度安全
相對於 32 位而言,64 位的 CPU 位寬增長一倍,使其可以處理更多更精確的數據,所以有必定加快數據處理的做用。bash
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
開發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) 注意不一樣數據類型間的轉換,包括隱式轉換
在 32 位系統沒有問題(long 和 int 都是 4 字節); 在 64 位系統,long 8 字節,int 4 字節,將 long 值賦予 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);
複製代碼
在 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
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 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,所以判真
指令集
Xcode Build Setting 中指令集相關選項釋義
(1) Architectures:工程編譯後支持的指令集類型。
支持的指令集越多,指令集代碼的數據包越多,對應生成二進制包就越大,也就是 ipa 包會變大。
(2) Valid Architectures:限制可能被支持的指令集的範圍
編譯出哪一種指令集的包,將由 Architectures 與 Valid Architectures 的交集來肯定
(3) Build Active Architecture Only:指定是否只對當前鏈接設備所支持的指令集編譯
當設爲 YES,只對支持的指令集版本編譯,編譯速度更快;當設爲 NO,會編譯全部的版本
通常 debug 時候可選爲 YES,release 時爲 NO 以適應不一樣設備。
開發Tips
解決:避免依賴於 64bit 的編譯優化 在 32bit 真機上 NSLog 中傳入 NULL 會 crash
判斷版本: if (@available(iOS 11.0, *))
參考:WWDC 2019 - Videos - Apple Developer
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;
}
}
複製代碼
if (@available(iOS 13.0, *)) {
self.view.overrideUserInterfaceStyle = UIUserInterfaceStyleLight; // Disable dark mode
self.view.overrideUserInterfaceStyle = UIUserInterfaceStyleDark; // Enable dark mode
}
複製代碼
iOS 13的 presentViewController 默認有視差效果,模態出來的界面默認都下滑返回。 新效果動圖
緣由是 iOS13 中 modalPresentationStyle
的默認值改成 UIModalPresentationAutomatic
,iOS13 以前默認是 UIModalPresentationFullScreen
若是想改回原來的動效須要手動加上這行代碼:
self.modalPresentationStyle = UIModalPresentationFullScreen;
複製代碼
在 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]}];
複製代碼
多是爲了優化啓動速度,App 啓動過程當中,部分View可能沒法實時獲取到正確的frame
只有等執行完 UIViewController 的 viewDidAppear
方法之後,才能獲取到正確的值,在 viewDidLoad
等地方 frame Size 爲 0
#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);
}
複製代碼
UIWebView - UIKit | Apple Developer Documentation
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;
}
複製代碼
iOS11 開始 UITableView 開啓了自動估算行高(Self-Sizing),Headers、footers、cells 的 estimated 高度默認值(estimatedRowHeight
、estimatedSectionHeaderHeight
、estimatedSectionFooterHeight
)都從 iOS11 以前的 0 改變爲 UITableViewAutomaticDimension
若是不實現-tableView: viewForFooterInSection:
和-tableView: viewForHeaderInSection:
,那麼三個高度估算屬性的改變會致使高度計算不對,產生空白。 解決方法是實現對應方法或把這三個屬性設爲 0:
self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;
複製代碼
TableView 和 SafeArea(安全區) 有如下幾點須要注意:
在 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。
複製代碼
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];
}
}
複製代碼
相似於付款成功的震動反饋,只在 iOS 10 以上可用,所以須要判斷if (@available(iOS 10.0, *))
譯 如何使用 UIFeedbackGenerator 讓應用支持 iOS 10 的觸覺反饋 - iOS - 掘金
擴展閱讀:
iOS開發同窗的arm64彙編入門 - 劉坤的技術博客 64位和32位的寄存器和彙編的比較 - jmp esp - CSDN博客 iOS標記指針(Tagged Pointer)技術 - 掘金