在 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)技术 - 掘金