IOS系统从07年出来,到现在也有6年了,每年发布一次到两次新的设备,从iPhone1,iPhone2 ... iPhone4s再到最新的iPhone5。硬件在升级的过程中CPU的架构也可能发生变化,如最早的armv6,armv7再带最新的iPhone5所以用的armv7s。同时伴随每一次硬件升级,系统都会做一次大版本的升级,目前最新的版本已经到了6.x了。
每次系统升级的时候,总有一些用户会因为各种原因不愿或者不能升级到最新的系统。这就意味着,如果我们的程序要让尽可能多的人使用,就得支持尽可能多的硬件架构及系统版本。如果我们写的程序和硬件打交道比较少的话,要做的事也就是配置一下工程选项中的Architectures,在其中添加上我们要支持的硬件架构就好了。然而每次系统的升级都会伴随这很多新的API,Framework的引入,以及部分老的API的废弃。也就说我们要支持的系统版本越多,工作量也就越大,所以我们通常会根据工作量和系统版本的分布情况做出取舍。现在AppStore上基本上的程序一般最多也就支持IOS 4.3的系统了吧,我做的上个项目PowerCam记得也是从4.0版本开始支持。
下面我们分别从两个方面讨论一下:
一、支持多设备类型
不同的设备CPU指令集可能是不同的,这也就导致了其支持的框架可能不同。例如iPhone3G及之前版本的设备的CPU只支持armv6的指令集,导致了这些设备只支持OpenGL ES 1.1版本,所有iPad及iPhone3GS之后的所有设备都采用armv7及armv7s(iPhone5,iPad4采用)指令集,因此既可以支持OpenGL ES 1.1和2.0。
二、支持多系统版本
Xcode在工程的编译选项中,我们可以看到两个选项:Base SDK和Deployment Target,第一个选项是用来设置我们项目是基于哪个版本的SDK开发,第二选项是用来设置我们的项目最低可以部署到哪一个系统运行。
Xcode新建的工程中通常都会采用最用的SDK以及支持到最新的版本。因为我们要既支持新的版本,同事兼顾的老的版本,为了能够利用上新版本中新的特性,所以最好选择最新的SDK,这一项通常不用改,维持原样就好。如果要支持叫早的系统版本,则需要修改Deployment Target选项。如下图所示,我们设置项目需要最老的版本为IOS 4.3。
到这儿就设置完了,Xcode会根据我们的设置进行编译打包。在这里我们讨论一下Xcode是如何使用了最新版本的SDK,却可以支持到较老的系统版本呢?查阅文档SDK Compatibility Guide发现,在程序打包的时候,Xcode只是将一些导出符号打到我们的程序包中,具体到运行时会根据实际的设备的系统版本进行匹配。文档中有一个图可以解释Base SDK和Deployment Target之间是如何运作的,如下图:
该图是以Mac OS工程为例的,不过原理是一样的。意思是对于我们支持部署的版本之前的所有API,我们都是可以无条件使用的(当前在新的版本中最好使用新的API),在我们支持部署的版本和我们开发所基于的SDK版本之间的API,能否使用取决于我们的APP具体运行的系统版本。
在系统版本升级的过程中,通常会添加一些新的API和Framework,也会废弃掉一些旧的效率不高的API。这些废弃的API虽然不能立马变得不可用,但是不保证在后面的版本中会继续支持,所以为了我们的程序能够在新的系统中更可靠,高效的运行,我们应该尽量使用新的API。
无论是在新的系统版本中调用已经不支持了的API,还是在旧的系统版本中调用新的系统版本中才引入的API都会导致我们的程序Crush。因此我们在调用那些我们部署的版本以后添加的API或者Framework时需要进行判断当前程序所运行的环境是否支持。
下面我们从几个方面讨论:
1) 判断一个类是否可用
在IOS 4.2以后我们可以通过class这个类方法来判断一个类在当前运行时是否可用,代码如下:
if ([UICollectionView class]) { // 6.0以后可以使用 } else { // 之前的版本,需要使用可替代的技术实现 }
在Mac OS则需要使用NSClassFromString来判断一个类是否可用。
2) 判断一个方法是否可用
NSObject类有一个方法instancesRespondToSelector可以用来判断一个类的实例是否响应指定的方法,如果要判断一个类是否响应一个类方法则可以使用respondToSeletor方法。代码如下:
if ([UIViewController instancesRespondToSelector:@selector(presentViewController:animated:completion:)]) { // 5.0以后支持 } else { // 不支持该方法 }
3) 判断一个函数是否可用
我们知道C语言中每个函数名都代表着这个函数的地址,因此我们可以通过判断该函数名字是否NULL来判断支持该函数。
if (CGColorCreateCopyWithAlpha != NULL) { // 支持该函数 } else { // 不支持该函数 }
4) 判断一个extern 变量或者Notification名是否可用
extern变量和Notification名其实都是一个变量,我们只需要判断它的地址是否NULL即可,代码如下:
if (&MPMoviePlayerReadyForDisplayDidChangeNotification != NULL) { // 6.0以后存在该通知 } else { // 不存在该通知 }
该文档中还讲到如果你想针对不同的SDK版本进行条件编译,可以采用宏来实现,感兴趣的同学可以自己看看。