甘いものが好きです

iOS App開発時に感じた疑問や課題、その他の雑感などを書いていきます。

MRCとARCの両方に対応したコードをシンプルに書く方法

MRC(Manual Reference Counting)とARC(Automatic Reference Counting)の両方に対応したコードを書くためには

#if __has_feature(objc_arc)
#if __has_feature(objc_arc_weak)

という分岐により条件つきコンパイルをしなければならない。
今となっては少々古い話題ではあるが*1、気になったのでなるべく簡単に対応する方法を調べてみた。異なるビルド環境にソースコード側で対応する場合このような方法があること自体、覚えておいて損はないだろうと思う。

MRCとARCの両方に対応する上で気をつけなければならない点は主に次の2点だ。

  1. 使用可能なライフタイム修飾子の違い
  2. 明示的なretain/releaseのメッセージ送信の必要有無

環境に応じてライフタイム修飾子を適切に使い分ける

ブロックオブジェクトの中で変数にアクセスする場合、解放済オブジェクトにアクセスしたり循環参照が生じてしまうおそれがある。そこで、各変数に適切なライフタイム修飾子を設定しておく必要がある。ここで問題となるのは、ビルド時に使用するSDKやDeployment Targetとして指定するiOSのバージョンによって、使用可能なライフタイム修飾子が異なるという点である。

次のページでは、MRCとARCの双方の環境においてブロックオブジェクトを安全かつ容易に扱うための方法が説明されている。

このページの中で、ライフタイム修飾子についてのマクロを定義する方法が示されている。iOS4.3とiOS5以降の違いは今もなお意識すべきポイントであるため、大変参考になる。該当箇所のみを以下に抜粋する。

// ARC & memory management
// Use these prefixes to be compatible with ARC on iOS 5/ ARC on iOS 4.X / non-ARC
// 
#if __has_feature(objc_arc_weak) // iOS 5 or above
#define __my_block_weak        __weak
#define __my_block_weak_unsafe __weak
#elif __has_feature(objc_arc)    // iOS 4.X
#define __my_block_weak        __strong
#define __my_block_weak_unsafe __unsafe_unretained
#else                            // iOS 3.X or non-ARC projects
#define __my_block_weak        __strong
#define __my_block_weak_unsafe __block
#endif

retain/releaseメッセージもマクロ化

retain、release、autoreleaseなどの保持・解放に関するメッセージを送信する部分のコードも、できることなら毎回__has_feature(objc_arc)で分岐させずにシンプルに書きたい。この点については次のページでマクロの定義の例が掲載されている。

マクロ部分のみを抜粋すると以下のとおり。

#ifndef AH_RETAIN
#if __has_feature(objc_arc)
#define AH_RETAIN(x) x
#define AH_RELEASE(x)
#define AH_AUTORELEASE(x) x
#define AH_SUPER_DEALLOC
#else
#define __AH_WEAK
#define AH_WEAK assign
#define AH_RETAIN(x) [x retain]
#define AH_RELEASE(x) [x release]
#define AH_AUTORELEASE(x) [x autorelease]
#define AH_SUPER_DEALLOC [super dealloc]
#endif
#endif

*1:Xcode 4.5で指定できるDeployment TargetはiOS4.3かそれ以降に制限されている。