iOS 版本兼容

Tips

编译设置

  • Base SDK设置

    指的是当前编译所用的SDK 版本。

    可以将值改为“Latest iOS”或者是Xcode支持列表上的任意版本的SDK。Base SDK设置会引导编译器使用该版本的SDK编译和构建应用,也就是说,它会直接控制应用使用哪些API。

  • Deployment Target设置

    它控制着运行应用需要的最低操作系统版本。

    如果你将它设成了特定版本,比如7.0,App Store会自动阻止运行早期操作系统的用户下载或安装这个应用。目前支持iOS 7.0系统的主要是那些大厂APP。一般APP仅仅支持8.0系统即可。

若你想要体验最新的iOS 10的功能,又要兼容iOS 8.0版本,Base SDK设置为最新的iOS 10(默认即可)。Deployment Target设置为iOS 8.0。当然,你用到的iOS 10 的API,要确保它们不会在iOS 8.0上不会crash。

iOS版本宏

iOS 系统

从usr/include/Availablity.h头文件中可以查找到这部分:

/*__IPHONE_NA is not defined to a value but is uses as a token by macros to indicate that the API is unavailable*/

#define __IPHONE_2_0      20000
#define __IPHONE_2_1      20100
#define __IPHONE_2_2      20200
#define __IPHONE_3_0      30000
#define __IPHONE_3_1      30100
#define __IPHONE_3_2      30200
#define __IPHONE_4_0      40000
#define __IPHONE_4_1      40100
#define __IPHONE_4_2      40200
#define __IPHONE_4_3      40300
#define __IPHONE_5_0      50000
#define __IPHONE_5_1      50100
#define __IPHONE_6_0      60000
#define __IPHONE_6_1      60100
#define __IPHONE_7_0      70000
#define __IPHONE_7_1      70100
#define __IPHONE_8_0      80000
#define __IPHONE_8_1      80100
#define __IPHONE_8_2      80200
#define __IPHONE_8_3      80300
#define __IPHONE_8_4      80400
#define __IPHONE_9_0      90000
#define __IPHONE_9_1      90100
#define __IPHONE_9_2      90200
#define __IPHONE_9_3      90300
#define __IPHONE_10_0    100000
#define __IPHONE_10_1    100100

静态检查

静态检查,即在编译时段就检查当前SDK编译与构建应用是否能使用某个API或已经不支持某个API。

编译常量

__IPHONE_OS_VERSION_MIN_REQUIRED

用来判断是否当前SDK版本“仍然”支持或具有某些功能。

#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
    //minimum deployment target is 8.0, so it’s safe to use iOS 8-only code
    当前SDK最小支持的设备系统,即8.0,所以在iOS 8.0设备上是安全的
    
#else
    //you can use iOS8 APIs, but the code will need to be backwards
    //compatible or it will crash when run on an iOS 7 device
    你仍然可以使用iOS 8的API,但是在iOS 7的设备上可能会crash.
#endif

__IPHONE_OS_VERSION_MAX_ALLOWED

用来判断是否当前版本“开始”支持或具有某些功能;

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
    //you can use iOS 10 APIs here because the SDK supports them
    //but the code may still crash if run on an iOS 8 device
    可以使用最新的iOS 10的API,开始支持的新功能。但是仍然可能会在iOS 8的设备上crash。
#else
    //this code can’t use iOS 10 APIs as the SDK version doesn’t support them
    不能使用iOS 10的API,只能使用iOS 10之前的。
#endif

针对Xcode的旧版本,或者设备上的就版本,不存在相应的宏,采用如下定义:

1
2
3
4
5
6
7
8
9
#ifndef __IPHONE_10_0
#define __IPHONE_10_0 100000
#endif

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0

#else

#endif

动态检查

CoreFoudation/NSFoundation版本宏

类似于iOS 系统版本宏,系统还给出了CoreFoudation和NSFoudation宏,并且给出了kCFCoreFoundationVersionNumber和NSFoundationVersionNumber两个值。这两个值均是double值。

在iOS10.2.1系统上,以上两个值,分别是:1348.220000、1349.130000。
在Debug下,两个值的存储po出来分别是:1348.22、1349.1300000000001。

kCFCoreFoundationVersionNumber

1
在CoreFoundatin/CFBase.h中定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#if TARGET_OS_IPHONE
#define kCFCoreFoundationVersionNumber_iPhoneOS_2_0 478.23
#define kCFCoreFoundationVersionNumber_iPhoneOS_2_1 478.26
#define kCFCoreFoundationVersionNumber_iPhoneOS_2_2 478.29
#define kCFCoreFoundationVersionNumber_iPhoneOS_3_0 478.47
#define kCFCoreFoundationVersionNumber_iPhoneOS_3_1 478.52
#define kCFCoreFoundationVersionNumber_iPhoneOS_3_2 478.61
#define kCFCoreFoundationVersionNumber_iOS_4_0 550.32
#define kCFCoreFoundationVersionNumber_iOS_4_1 550.38
#define kCFCoreFoundationVersionNumber_iOS_4_2 550.52
#define kCFCoreFoundationVersionNumber_iOS_4_3 550.52 //同上
#define kCFCoreFoundationVersionNumber_iOS_5_0 675.00
#define kCFCoreFoundationVersionNumber_iOS_5_1 690.10
#define kCFCoreFoundationVersionNumber_iOS_6_0 793.00
#define kCFCoreFoundationVersionNumber_iOS_6_1 793.00 //同上
#define kCFCoreFoundationVersionNumber_iOS_7_0 847.20
#define kCFCoreFoundationVersionNumber_iOS_7_1 847.24
#define kCFCoreFoundationVersionNumber_iOS_8_0 1140.1
#define kCFCoreFoundationVersionNumber_iOS_8_1 1141.14
#define kCFCoreFoundationVersionNumber_iOS_8_2 1142.16
#define kCFCoreFoundationVersionNumber_iOS_8_3 1144.17
#define kCFCoreFoundationVersionNumber_iOS_8_4 1145.15
#define kCFCoreFoundationVersionNumber_iOS_8_x_Max 1199
#define kCFCoreFoundationVersionNumber_iOS_9_0 1240.1
#define kCFCoreFoundationVersionNumber_iOS_9_1 1241.11
#define kCFCoreFoundationVersionNumber_iOS_9_2 1242.13
#define kCFCoreFoundationVersionNumber_iOS_9_3 1242.13 //同上
#define kCFCoreFoundationVersionNumber_iOS_9_4 1280.38
#define kCFCoreFoundationVersionNumber_iOS_9_x_Max 1299
#endif

NSFoundationVersionNumber

1
在Foundation/NSObjRuntime.h中定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#if TARGET_OS_IPHONE
#define NSFoundationVersionNumber_iPhoneOS_2_0 678.24
#define NSFoundationVersionNumber_iPhoneOS_2_1 678.26
#define NSFoundationVersionNumber_iPhoneOS_2_2 678.29
#define NSFoundationVersionNumber_iPhoneOS_3_0 678.47
#define NSFoundationVersionNumber_iPhoneOS_3_1 678.51
#define NSFoundationVersionNumber_iPhoneOS_3_2 678.60
#define NSFoundationVersionNumber_iOS_4_0 751.32
#define NSFoundationVersionNumber_iOS_4_1 751.37
#define NSFoundationVersionNumber_iOS_4_2 751.49
#define NSFoundationVersionNumber_iOS_4_3 751.49 //同上
#define NSFoundationVersionNumber_iOS_5_0 881.00
#define NSFoundationVersionNumber_iOS_5_1 890.10
#define NSFoundationVersionNumber_iOS_6_0 992.00
#define NSFoundationVersionNumber_iOS_6_1 993.00
#define NSFoundationVersionNumber_iOS_7_0 1047.20
#define NSFoundationVersionNumber_iOS_7_1 1047.25
#define NSFoundationVersionNumber_iOS_8_0 1140.11
#define NSFoundationVersionNumber_iOS_8_1 1141.1
#define NSFoundationVersionNumber_iOS_8_2 1142.14
#define NSFoundationVersionNumber_iOS_8_3 1144.17
#define NSFoundationVersionNumber_iOS_8_4 1144.17 //同上
#define NSFoundationVersionNumber_iOS_8_x_Max 1199
#define NSFoundationVersionNumber_iOS_9_0 1240.1
#define NSFoundationVersionNumber_iOS_9_1 1241.14
#define NSFoundationVersionNumber_iOS_9_2 1242.12
#define NSFoundationVersionNumber_iOS_9_3 1242.12 //同上
#define NSFoundationVersionNumber_iOS_9_4 1280.25
#define NSFoundationVersionNumber_iOS_9_x_Max 1299
#endif

针对上面的宏,写起来复杂,所以可以定义一个宏,来简化书写:

#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_10_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_10_0 1299
#endif

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
#define IF_IOS10_OR_GREATER(...) \
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_10_0) \
{ \
__VA_ARGS__ \
}
#else
#define IF_IOS10_OR_GREATER(...)
#endif

#define IF_PRE_IOS10(...) \
if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_10_0) \
{ \
__VA_ARGS__ \
}

IF_IOS10_OR_GREATERIF_PRE_IOS10就可以在使用中快捷调用。

IF_IOS10_OR_GREATER
(
    //iOS 10 code here
);

然而,即使上面这种方法可以简单的检查系统版本,你仍然无法保证绝对正确,因为你会发现以上宏中不同的系统版本中存在相同的值。

缺陷:当你要判断那些刚好存在相同值的版本的时候,也是一个危险的判断。

检查运行的iOS 系统版本

if ([[ [UIDevice currentDevice] systemVersion] floatValue] >= 10.0) {
}

⚠️注意:上面的判断方法,在判断整数版本的时候,使用起来挺方便,也不会出什么大问题。但是仍然是极其危险的。因为float在计算机中的存储,是近似而非精确。

比如,最新的iOS 10.2.1系统,有以下一段代码:

1
2
3
4
NSString *version = [[UIDevice currentDevice] systemVersion];
int int_ver = [version intValue];
float float_ver = [version floatValue];
NSLog(@"%@-%d-%f",version,int_ver,float_ver);

打印出来如下的结果:

1
10.2.1-10-10.200000

可以看出,10.2.1打印出来的floatValue值,是【10.20000】,这还算小事。更为致命的是,在控制台调试时发现:

所以,假如比较10.2.1和10.2.2比较,就很难得出结果。

鉴于此,以上方法不推荐使用!最好不用!

那么,什么是好的运行时判断系统的方法呢?

定义判断版本的宏:

1
2
3
4
5
#define SYSTEM_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)

但是,由于NSString的compare方法本身的缺陷:

1
NSInteger compareResult = [@"10.0" compare:@"10" options:NSNumericSearch];

上面返回的结果:NSOrderedDescending,即10.0相比于10是低版本。

遗憾的是,上面的系统版本判断也是有漏洞的。但是相对于之前的漏洞,这个漏洞似乎可以接受。

iOS 8之后,NSOperatingSystemVersion

NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];

NSProcessInfo还提供了:

1
2
3
4
NSOperatingSystemVersion v = (NSOperatingSystemVersion){8,1,3};
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:v]) {

}

以上这个方法,只有在iOS 8之后,但是对于一些超级APP,仍然在支持iOS 7,所以也就无能为力了。

又是一个有遗憾的判断。

检查某个API是否可用

  1. respondsToSelector可以检查某个方法是否可用;

  2. 检查某个类是否包含某个@property。通过

    • NSString *capitalizedProperty = [NSString stringWithFormat:@"%@%@", [[propertyName substringToIndex:1] uppercaseString], [propertyName substringFromIndex:1]];

      再根据respondsToSelector来检查。注意setter、getter的名字;

  1. class_getProperty
1
2
3
if (class_getProperty([object class], "propertyName")) {
// it has that property!
}

最新Xcode 9的方法

在这里:

1
2
3
if (@available 11.0) {

}

总结

参考

What are the common use cases for __IPHONE_OS_VERSION_MAX_ALLOWED?

SDK Compatibility Guide

Tips & Tricks for conditional iOS3, iOS3.2 and iOS4 code

[译] 高效的 iOS 应用版本支持方法

How to check iOS version?

nv-ios-version

NSString-compareToVersion