极少数应用程序很“奢侈”的只支持最新版本的 iOS。 设置一个较低的部署目标以及基于特定 iOS 版本的代码分支通常是很有必要的。虽然苹果公司的信息有些矛盾,还是有各种办法来完成这个。最近在这条 tweet 上看到有人警告说,不要这样做:

[Swift] 纯文本查看 复制代码
#define IsIOS7 ([[[[UIDevice currentDevice] systemVersion] substringToIndex:1] intValue]>=7)


GitHub 搜索显示,有超过 8000 的结果调用了substringToIndex:1 。所有这些代码碰到 iOS 10 就“懵逼”了。因为 iOS 10 会被检测成 iOS 1 了,估计只有在越狱的应用中才会出现吧。

又是同样的老故事。Windows 9 变成 Windows 10 是因为有太多代码通过 if (name.startsWith("windows 9")) 来检查 Windows 95 和 98 了。

高效的iOS应用版本支持方法 Efficient iOS Version Checking 支持Swift

高效的iOS应用版本支持方法 Efficient iOS Version Checking 支持Swift - 敏捷大拇指 - 高效的iOS应用版本支持方法 Efficient iOS Version Checking 支持Swift





1、新 API

苹果公司令人惊讶的花了相当长的时间才意识到这个问题并提供了更好的 API。iOS 8 中,终于有了一些改进!现在 NSProcessInfo 有一个新的 operatingSystemVersion 方法,更重要的是还有
  1. - (BOOL)isOperatingSystemAtLeastVersion: (NSOperatingSystemVersion)version
复制代码
方法来检查。

[Objective-C] 纯文本查看 复制代码
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 9, .minorVersion = 1, .patchVersion = 0}]) {
    NSLog(@"Hello from > iOS 9.1");
}

// Using short-form for the struct, we can make things somewhat more compact:
if ([NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9,3,0}]) {
    NSLog(@"Hello from > iOS 9.3");
}





2、我们在 PSPDFKit 做了什么

PSPDFKit 是一个关于PDF的SDK,我们可以用它在PDF文档上实现查看、注释以及填写表单的功能。最开始写这个 SDK 的时候还是 iOS 4,随着一系列新 iOS 版本的发布,它也不断的在改进。那个时候还没有专门的 API 来检测版本,许多应用采用类似下面的代码:

[Objective-C] 纯文本查看 复制代码
if ([[[UIDevice currentDevice] systemVersion] isEqualToString:@"7.0"]) {
    //do stuff
}


这样用不好,所以我们从来没这样用过。比较字符串速度可能很快,但是在这种情况下是个错误的选择。正确的做法是像下面这样

[Objective-C] 纯文本查看 复制代码
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) \
  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)

if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"9.0")) {
    ...
}


这样又太笨重了,容易出错。有种更简单的方法。我们可以用 NSFoundationVersionNumber(或者 kCFCoreFoundationVersionNumber)来比较。系统检测开销降低到一个简单的 if 比较。不需要调用其它方法,所以它效率极高,即使在紧凑的循环中表现也不错。

[Swift] 纯文本查看 复制代码
if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_9_0) {
    // do stuff for iOS 9 and newer
}
else {
    // do stuff for older versions than iOS 9
}


事实上,这正是苹果公司在 2013 构建现代应用程序技术论坛中章节 2 所建议的。

告诫: 有时候会缺少一些常量。NSFoundationVersionNumber 是在 NSObjCRuntime.h 中定义的,作为 Xcode 7.3.1 的一部分,我们设定常数范围从 iPhone OS 2 到 #define NSFoundationVersionNumber_iOS_8_4 1144.17 - 而不是 9.0-9.3\。 对于 kCFCoreFoundationVersionNumber 也一样。注意,虽然这些数字很相似,但是它们的意义是不同的,所以使用其中一个或者另外一个。


如果你做 macOS 开发的话,也可以使用 NSAppKitVersionNumber ,它通常是更新到最新的

在 SDK 10(Xcode 8)苹果补充了缺少的数字,甚至还有未来的版本。

[Swift] 纯文本查看 复制代码
#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


会有 iOS 9.4 吗?考虑到 iOS 10 将在未来 3 个月内发布,而且 9.3.3 仍然是 beta 版,我估计是不会有了,但是最好还是占个坑吧。在 PSPDFKit 中,我们是使用下面的模式来定义缺少的版本号。如果代码以一个更高的最低部署目标构建,代码会自动编译,当我们遗漏了一些 iOS 版本时,这会很有帮助。

[Swift] 纯文本查看 复制代码
// iOS 9 compatibility
#ifndef kCFCoreFoundationVersionNumber_iOS_9_0
#define kCFCoreFoundationVersionNumber_iOS_9_0 1223.1
#endif

#define PSPDF_IS_IOS9_OR_GREATER (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0)

#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
#define PSPDF_IF_IOS9_OR_GREATER(...) \
if (PSPDF_IS_IOS9_OR_GREATER) { \
PSPDF_PARTIAL_AVAILABILITY_BEGIN \
__VA_ARGS__ \
PSPDF_PARTIAL_AVAILABILITY_END }
#else
#define PSPDF_IF_IOS9_OR_GREATER(...)
#endif

#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 90000
#define PSPDF_IF_PRE_IOS9(...)  \
if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_9_0) { \
PSPDF_PARTIAL_AVAILABILITY_BEGIN \
__VA_ARGS__ \
PSPDF_PARTIAL_AVAILABILITY_END }
#else
#define PSPDF_IF_PRE_IOS9(...)
#endif


请注意部分可用的宏。这是添加到 SDK 9 中出现的一个警告。当我们打包进版本代码块时,这些没什么用,所以禁用它。我们有一些单独的宏,在做一些其他类型的可用性检查时会有用。我们在某些情况下使用,例如在 UICollectionView 中实现一些交互动画,在 iOS 9 中拖拽 tab 或者 page,同时也要支持 iOS 8。

[Swift] 纯文本查看 复制代码
#define PSPDF_PARTIAL_AVAILABILITY_BEGIN \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wpartial-availability\"")

#define PSPDF_PARTIAL_AVAILABILITY_END \
_Pragma("clang diagnostic pop")




2.1、为什么用这些宏

自从前段时间我们放弃了 iOS 7,我们可以轻易的切换到新的 isOperatingSystemAtLeastVersion: 方法上。其内部实现是通过调用 operatingSystemVersion ,是相当高效的。但它会产生更多的代码,仍然比我们现在的实现要慢一点。我没看到过基础检测的正面比较,但是可以肯定的说用了这些宏会更好,如果没有用宏的话,赶紧试试吧。

如果我们直接看 operatingSystemVersion 的实现,确实有点丑。它被缓存了,但是它通过调用 _CFCopySystemVersionDictionary() 生成版本号,然后查找 kCFSystemVersionProductVersionKey (就是 ProductVersion),然后对该字符串执行 componentsSeparatedByString: 。不知道为啥,我更期望这是硬编码,但是从外部字典文件读取可能更加灵活。




3、Swift

由于 Swift 2.0 是支持内置版本检查的语言,以前是这么用的:

[Swift]