http://disanji.net/2011/03/20/tips-tricks-for-conditional-ios3-ios32/

http://www.yifeiyang.net/iphone-development-techniques-of-environmental-chapter-7-distinguish-between-different-versions-of-the-iphone/

 

关于选择执行iOS3,iOS3.2,iOS4代码的技巧

来自  友盟翻译组 stefaliu
1,623 次阅读 评论 (0)

这篇文章将要讲述如何判断你当前程序正在运行的iOS版本以及如何写一个宏来选择性地编译与运行为不同iOS所写的代码。

一个能支持多个版本iOS的工程

使得程序能够运行在多个版本iOS上相对来说是简单的:

  • 在工程中设置“Base SDK”为最新的iOS版本号,它包含你打算利用的新特性。这个越高。。能用的新特性就越多。。
  • 设置“iPhone OS Deployment Target”为你打算支持的最老的iOS版本。这个越低,支持老版本就越多。你要处理的新特性对于区别老版本的代码就越多。。。

然而,按上述进行正确设置是这个问题中最简单的一部分。困难部分是使用新iOS版本中的新特性而不会破坏老版本上的程序。

在3.1.3模拟器上运行

在进入代码部分之前,我们需要先讨论如何在老版本的模拟器上运行你的项目。在iOS开发中模拟器是十分重要的(因为它比在真实的设备上运行要更快而 且更简单)。但是苹果公司已经在当前的Xcode builds上移去了早于3.2版本的SDK,因而要在模拟器上检验你为老设备而写的程序是困难的,你不得不在真实设备上进行程序的安装。

能够支持3.1.3是相对重要的,因为这是最初的iPhone和iPod Touch所能支持的最近的一个版本,而iOS 4占据iPhone和iPod Touch市场80%以上的份额还需要几个月的时间。

为了能够在模拟3.1.3版本,你必须安装老版本的Xcode。你可以下载Xcode 3.1.4 for Leopard with iPhone SDK 3.1.3或者Xcode 3.1.4 for Snow Leopard with iPhone SDK 3.1.3.注意要把它安装到与你的Xcode3.2.3不同的路径下(选择不同的磁盘路径或者重命名之前的开发路径)。

一旦你获得了老版本的Xcode,你需要复制你的主target而且在这个副本中设置Base SDK为3.1.3。你应该为它设置一个second target因为我们不能为了在这个模拟器运行代码而冒着搞混main target的风险。

使用新的iOS版本特性同时支持老的iOS版本

例如,如果你打算在老版本的iOS上运行的程序包括iOS4背景的任务,那么你需要使用下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
    if ([[UIApplication sharedApplication]
        respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)])
    {
        UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
            beginBackgroundTaskWithExpirationHandler:^{}];
 
        // Perform work that should be allowed to continue in background
 
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    }
#endif

有三个重要的成分:

  1. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000这里来保证:如果我们选择使用低于4.0版本的Base SDK来建造工程,那么就不会引起编译问题。这对于在老版本模拟器上运行来说是必需的。
  2. 内部的执行检查UIApplication是否支持beginBackgroundTaskWithExpirationHandler方法,由于最后的build是基于SDK4.0,所以这个检查确保我们需要的方法是可以获得的。
  3. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 4000#endif之间的部分是iPhone OS4的代码。

使这个条件执行的部分不太复杂

前面代码的问题包括检查编译时间和检查某个方法是否存在是相当麻烦的,因为你必须记得这两项。

如果你打算集成这两项检查,一个更好的方法如下所示:

1
2
3
4
5
6
7
8
9
IF_IOS4_OR_GREATER
(
    UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
        beginBackgroundTaskWithExpirationHandler:^{}];
 
    // Perform work that should be allowed to continue in background
 
    [[UIApplication sharedApplication] endBackgroundTask:bgTask];
);

我们能够实现这个宏如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32
#endif
 
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
#define IF_IOS4_OR_GREATER(...) \
    if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \
    { \
        __VA_ARGS__ \
    }
#else
#define IF_IOS4_OR_GREATER(...)
#endif

如果我们打算加入一些高于某个特定版本的OS版本上的东西,我们不需要前面提到的条件编译(因为在后面版本的编译时我们需要这些代码)。在这个情况下,只有runtime检查是需要的。你既可以直接做该检查,也可以与其他宏保持整齐如下所示:

1
2
3
4
5
#define IF_PRE_IOS4(...) \
    if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \
    { \
        __VA_ARGS__ \
    }

这里有三点需要指出:

  1. 我使用kCFCoreFoundationVersionNumber在runtime时决定iPhone OS的版本。网上也有许多其他的例子比如使用[[UIDevice currentDevice] systemVersion],但是这个方法需要进行字符串的比较而且可能需要处理字符串中的最大和最小数字。相比而言,一个double型的比较更加直接。
  2. 我没有使用惯用的do{x}while(0)结构在这个宏中,因此如果你需要的话你可以添加一个else在该宏的末尾(而且它不需要条件编译)。
  3. 我为这个宏设计了可变的参量列表,因此你可以添加任意数量的参量而不会有问题。

最后一点,kCFCoreFoundationVersionNumber的定义可能不会在每一个SDK的版本中,因此你应当有条件的定义他们以防它们不存在。下面是一个方便的列表:

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
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_2_0 478.23
#endif
 
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_1
#define kCFCoreFoundationVersionNumber_iPhoneOS_2_1 478.26
#endif
 
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_2
#define kCFCoreFoundationVersionNumber_iPhoneOS_2_2 478.29
#endif
 
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_3_0 478.47
#endif
 
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_1
#define kCFCoreFoundationVersionNumber_iPhoneOS_3_1 478.52
#endif
 
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_2
#define kCFCoreFoundationVersionNumber_iPhoneOS_3_2 478.61
#endif
 
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32
#endif

不需要宏的解决方案也是不错的

比一个简单的宏好一些的做法是一个简单的函数。当你的代码内容不包含与OS版本特定关系的代码时,利用函数是一个有效的方法(只有条件本身是与OS 特性相关)。一个通用的例子是分开处理iPad和iPhone版本的布局。通常情况,如果你需要编译iPad3.2和iPhone3.1.3,你需要下面 的代码:

1
2
3
4
5
6
7
8
9
10
11
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 30200
    if ([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)] &&
        [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
    {
        // iPad specific layout changes
    }
    else
#endif
    {
        // iPhone layout
    }

你可以处理它利用像IF_IOS4_OR_GREATER那样的条件执行的宏,但是一个更好的方法如下:

1
2
3
4
5
6
7
8
if (isIPad())
{
    // iPad specific layout changes
}
else
{
    // iPhone layout
}

在这儿所有的条件执行的部分都放在isIPad()函数里面:

1
2
3
4
5
6
7
8
9
10
11
12
BOOL isIPad()
{
    IF_3_2_OR_GREATER
    (
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
        {
            return YES;
        }
    );
 
    return NO;
}

结论

苹果不会使SDK能够容易地支持老版本的iPhone。我确定他们想要每一个用户都能用到新版本或者购买新设备如果他们当前的设备已经不能再更新。

这对于App Store的开发者来说不是一个现实观点。你不能期望所有的用户都尽可能地更新版本。

为多版本SDK开发的关键一点是保证条件执行语句的数量尽可能的少。你肯定不希望为了使你的代码支持不同的版本而在代码中加入太多的条件执行语句。每一个条件执行都是一个额外的测试工作,因为不同的行为必须完全在不同的平台上进行测试。

想我在这里讨论的条件执行和函数能够帮助你们。如果你发现你自己需要许多条件执行语句,那么可能你需要考虑更改设计比如为不同的OS版本实例化不同的子类。

作者:Matt Gallagher
原文链接:http://cocoawithlove.com/2010/07/tips-tricks-for-conditional-ios3-ios32.html
原文:
Tips & Tricks for conditional iOS3, iOS3.2 and iOS4 code
In this post, I’ll show you ways to determine which version of iOS you are running on and show you how to write a macro that can both conditionally compile and runtime switch between the code for different versions of iOS.
A project or target that supports multiple versions of iOS

To make an application target that runs on multiple versions of iOS is relatively simple:

  • Set the “Base SDK” in your projects settings to the newest version number of iOS whose features you may want.
  • * Set the “iPhone OS Deployment Target” to the oldest version number of iOS that you will support

However, getting the target settings correct is the easy part of the problem. The hard part is using new features on newer iOS versions without breaking the app on older versions.

Running in the 3.1.3 simulator

Before getting to the actual code, it might be worthwhile to discuss how to run your projects in older versions of the simulator.

The simulator is an important part of iOS development (since it is much faster and simpler than running your code on the device). But Apple have removed SDK versions earlier than 3.2 from the current Xcode builds. This makes it hard to verify your apps on earlier devices unless you install on a physical device.

Support for 3.1.3 is relatively important since it is the last version of iOS supported by the original iPhone and iPod Touch and it will be a few months before iOS 4 exceeds 80% of the remaining iPhone and iPod Touch market.

To allow simulation in 3.1.3, you must install an old version of Xcode. If you are a registered iPhone developer, you can download Xcode 3.1.4 for Leopard with iPhone SDK 3.1.3 or Xcode 3.1.4 for Snow Leopard with iPhone SDK 3.1.3. Be careful to install these in a different location to your Xcode 3.2.3 with iOS3.2/iOS4 (either select a different hard disk or rename your existing /Developer directory before you install).

Once you’ve got an old version of Xcode, you’ll want to duplicate your main target and set the Base SDK to 3.1.3 in this duplicate (because it won’t exist in this version of Xcode). You should use a second target for this because you shouldn’t risk messing with your main target just to run code in the simulator.
Using features from newer iOS versions while supporting older iOS versions

For example, if you want to start an iOS4 background task in an application that you want to run on earlier versions of iOS, then you’ll need to use code like this:

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
    if ([[UIApplication sharedApplication]
        respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)])
    {
        UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
            beginBackgroundTaskWithExpirationHandler:^{}];

        // Perform work that should be allowed to continue in background

        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    }
#endif

There are three important components:

  1. The #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 4000 compile-time conditional. This ensures that if we choose to build this project with a Base SDK lower than 4.0, then it won’t cause compile problems. This is essential for running in older versions of the simulator.
  2. The runtime check that UIApplication supports the beginBackgroundTaskWithExpirationHandler method. Since the final release build will be built against the 4.0 SDK (even if users install on SDK 3.0) this runtime check ensures that the method we need is available.
  3. Everything else between the #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 4000 and the #endif is the iPhone OS 4 code.
      1. I use the kCFCoreFoundationVersionNumber to determine the iPhone OS at runtime. There are many examples on the web using [[UIDevice currentDevice] systemVersion] but that method requires a string comparison and potentially handling of major and minor numbers within the string components. A single double comparison is far more straightforward.
      2. I have not used the typical do { x } while (0) wrapper around the macro, so you can simply tack an else onto the end if you choose (and it doesn’t need conditional compilation of its own).
      3. I use a variable argument list for the macro. This is so that any number of commas may appear in the contents without causing problems.
    1. Making the conditional work less ugly

      The problem with the previous code is the compile-time conditional and the runtime check for the presence of methods is cumbersome since you must remember to to both.

      If you want to integrate both a compile-time check and a runtime check, a better approach would look like this:

      IF_IOS4_OR_GREATER
      (
          UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
              beginBackgroundTaskWithExpirationHandler:^{}];
      
          // Perform work that should be allowed to continue in background
      
          [[UIApplication sharedApplication] endBackgroundTask:bgTask];
      );

      We can implement this macro as follows:

      #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0
      #define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32
      #endif
      
      #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
      #define IF_IOS4_OR_GREATER(...) \
          if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \
          { \
              __VA_ARGS__ \
          }
      #else
      #define IF_IOS4_OR_GREATER(...)
      #endif

      If we want to include something only in OS versions prior to a a specific version, then we don’t need the conditional compilation (since we still want the code to appear when compiled in a later version. In this case, only a runtime check is required. You can either do this directly, or for symmetry with other macros, you could use:

      #define IF_PRE_IOS4(...) \
          if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \
          { \
              __VA_ARGS__ \
          }

      Three interesting points to note about these macros:

      A final point… the kCFCoreFoundationVersionNumber definitions may not be in every version of the SDK (each SDK normally contains definitions for versions up to but not including itself), so you should conditionally define them yourself in case they’re missing. Here’s a handy list:

      #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_0
      #define kCFCoreFoundationVersionNumber_iPhoneOS_2_0 478.23
      #endif
      
      #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_1
      #define kCFCoreFoundationVersionNumber_iPhoneOS_2_1 478.26
      #endif
      
      #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_2
      #define kCFCoreFoundationVersionNumber_iPhoneOS_2_2 478.29
      #endif
      
      #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_0
      #define kCFCoreFoundationVersionNumber_iPhoneOS_3_0 478.47
      #endif
      
      #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_1
      #define kCFCoreFoundationVersionNumber_iPhoneOS_3_1 478.52
      #endif
      
      #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_2
      #define kCFCoreFoundationVersionNumber_iPhoneOS_3_2 478.61
      #endif
      
      #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0
      #define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32
      #endif

      Better still: solutions that don’t require macros

      Better than a simple macro is a simple function. This is a valid solution where the contents of your conditional code does not itself contain OS specific code (only the condition itself requires OS specific logic).

      A common example is handing separate layout for iPad and iPhone versions. Ordinarily, if you’re compiling for iPad 3.2 and iPhone 3.1.3, you need the following code:

      #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 30200
          if ([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)] &&
              [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
          {
              // iPad specific layout changes
          }
          else
      #endif
          {
              // iPhone layout
          }

      You can handle this with a conditional macro like the IF_IOS4_OR_GREATER but a far better solution is:

      if (isIPad())
      {
          // iPad specific layout changes
      }
      else
      {
          // iPhone layout
      }

      Where all the conditional pollution is tidily kept in your isIPad() function:

      BOOL isIPad()
      {
          IF_3_2_OR_GREATER
          (
              if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
              {
                  return YES;
              }
          );
      
          return NO;
      }

      Conclusion

      Apple doesn’t exactly make it easy to support old versions of the iPhone SDK. I’m sure they want everyone to keep up to date or buy new devices if their current device can’t be updated.

      That’s not always a realistic attitude for App Store developers. You can’t expect all your customers to upgrade as soon as possible.

      The important point when writing for multiple versions of the SDK is to keep as few conditionals as possible. You don’t want to have thousands of conditionals in your code for supporting different versions. Every conditional is extra testing work since different behaviors must be fully exercised on all different platforms.

      While the conditionals and functions I’ve talked about here will help, if you find yourself needing a lot of conditionals you may also want to consider design changes like instantiating different subclasses for different OS versions.