2011年12月22日木曜日

プロジェクトのARC移行について


iOS Advent Calendar 2011 22日目
この記事は@glassonion1さん主催のiOS Advent Calendar2011に向けて書いています。

昨日の@yomoappさんのiOS向けのオープンソースをまとめたサイトを作りましたからのバトンタッチです。

最近4.2以前のXCodeで開発したアプリをARC対応させたので、その手順と注意点など書いていきたいと思います。
ARCって何ですか
ARCとはAutomatic Reference Countingの略称で、XCode4.2のApple LLVM 3.0 コンパイラから搭載された新機能です。

ご存知、iOSのObjective-CにはGCが無い為、オブジェクトのライフタイム管理はretain/releaseによるリファレンスカウンタの増減によって制御していました。

ARCはこれまで自分で記述しなければならなかった、リファレンスカウンタの制御をコンパイラが自動で組み込んでくれるという素晴らしい仕組みです。
ARCのメリット
ARCによって享受できるメリットは、何と言ってもretain/releaseからの解放ではないでしょうか。これまで私がretain/releaseが原因のクラッシュ、メモリリークに悩まされた時間と言ったら、そりゃアプリがもう2つ、3つは作れたくらいの時間を費やしたのではないかと思いますorz

実際のところ、ARC登場の頃にはretain/releaseにだいぶ慣れてしまった感もありましたが、ARCを使い出してからは、もう戻る気にはなれません。

あとretain/releaseが無くなる事によって、ソースコードが非常にシンプルで綺麗になります。これは個人的に、思いの外ありがたい変化でした。
XCode4.2からはARCがデフォルト
XCode4.2からは新規に作成したプロジェクトはデフォルトでARCが有効になっています。ARCを無効にしたい場合は、プロジェクト作成時のウィザードでUse Automatic Reference Countingのチェックを外します。

既存プロジェクトのARCへの移行手順
Edit > Refactor > Convert to Objective-C ARCを選択すると、XCodeプロジェクトのARCへの移行前のプリチェックが実行されます。



プリチェックを実行すると、そのXCodeプロジェクトがARCへの移行が可能であるかのチェックが行われ、変換が出来ない類のものがエラーとして列挙されます。移行を完了するには、全てのエラーを手動で取り除く必要があります。

プリチェックを行う前に、XCode > Preferences > General > Continue building after errorsにチェックを入れます。このオプションを付けておく事で全てのエラーを抽出する事が出来るので、まとめて修正することが出来ます。



なお、プリチェックを開始する際に、ターゲットがデバイスになっているとこんなエラーになります。↓ターゲットはSimuratorにします。



自分のアプリでプリチェックを実行した時は169個のエラーが出ました。ARCへの移行を完了するには、これらを全て対処する必要があります。




全てのエラーの対処が出来たら、↓こんな画面が表示されて変換前/後の確認が出来ます。問題なければSaveボタンを押して、変換を実行したらARCへの移行は完了です。



ARCへの移行の流れは以上ですが、以降はプリチェックでエラーが出た際のトラブルシューティングなどについて書きます。
ARC移行で行われている事
そもそもARCへの移行を行った時、何が行われているんでしょうか。恐らく以下の事が行われていると思われます。
  1. コンパイラをApple LLVM 3.0に変更
    Build Option > Compiler for C/C++/Objective-C が Apple LLVM compiler 3.0に変更されます。
  2. Automatic Reference Countingの有効化
    上のコンパイラに付随するオプションのObjective-C Automatic Reference Counting がYESになります。
  3. retain/release/autoreleaseの除去
    コード上からリファレンスカウントの処理が除去されます。
    例えばXCode 4.1のこんなコードが↓
    NSString *string1 = [[NSString stringWithString:@"ABCD"]retain];
    [string1 release];
        
    NSString *string2 = [[NSString stringWithString:@"ABCD"]autorelease];
    [string2 release];
     
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
    NSString *string3 = [NSString stringWithString:@"ABCD"];
    [pool release];
    
    ARCに移行すると、こうなります。↓
    NSString *string1 = [NSString stringWithString:@"ABCD"];
        
    NSString *string2 = [NSString stringWithString:@"ABCD"];
         
    @autoreleasepool {
        NSString *string3 = [NSString stringWithString:@"ABCD"];
    }
    
    見ての通り、コード上のretain/releaseが全て除去され、NSAutoreleasePoolは@autoreleasepoolブロックに置き換えられています。ARC環境でも記述が変わっただけでautoreleaseは使用する事が出来ます。
  4. プロパティのオプション変換
    クラスのプロパティにも変換が加えられます。
    例えばXCode 4.1のこんな宣言が↓
    @property (nonatomic, assign) id delegate;
    @property (nonatomic, retain) NSString* val1;
    @property (nonatomic, readonly) NSString* val2;
    @property (nonatomic, copy) NSString* val3;
    
    ARCに移行すると、こうなります。↓
    @property (nonatomic, unsafe_unretained) id delegate;
    @property (nonatomic, strong) NSString* val1;
    @property (unsafe_unretained, nonatomic, readonly) NSString* val2;
    @property (nonatomic, copy) NSString* val3;
    

ARC移行のプリチェックでは、上記の自動変換処理で対応できないコードが抽出されます。
ARC移行のプリチェックでエラーになる例
それでは私の環境で出た代表的なエラーと、その対処方法について書いていきます。もしかしたらここに出ていないエラーもあるかもしれませんが、そんな時はググって下さい。
  1. autorelease
    autoreleaseしかしていない行はエラーになります。これは該当の一行を除去するだけでOKです。
  2. switch文のスコープ
    これは原因がいまいち判りませんね・・
    caseの中をブラケット{}で囲うと直ります。どうもコンパイラのバグではないかという噂で、将来的には直ってるかもしれません。とりあえずブラケットしましょう。

  3. 構造体のオブジェクトポインタ
    ARCでは構造体のオブジェクトポインタが使用できなくなりました。今回ARC化するアプリ内で使用しているライブラリに、この様な構造体があったので、このソースをARCから除外して対処しました。ARCからの除外方法については後述します。

  4. Bridged Cast問題
    Objective-CにはToll-Free Bridgeという仕組みがあり、CocoaのオブジェクトとCore Foundationのオブジェクトがキャストするだけで相互に利用出来ます。

    例えばNSStringとCFString、NSDateとCFDate、NSArrayとCFArray等のペアですが、これらはキャストするだけで相互のメソッドを利用できます。

    しかしARC環境下ではCocoaオブジェクトだけが管理対象になるため、オブジェクトのオーナーシップを明示する必要があります。
ソースをARC対象外にする方法
ARCを有効にしたプロジェクトでは、ソース毎にARC対象外にする事が出来ます。対処外にしたクラスは従来のretain/release方式でコードを記述する事が出来ます。

Bridged Castの問題を自分で対処しきれない場合などは、ARC対象外に指定してしまいましょう。またサードパーティのライブラリ等も、自分でARC化出来ない場合や作成元が非ARCを推奨しているものは対象外にしてしまいましょう。
ARCを有効にしているプロジェクトで、特定のソースをARC対象外にするにはBuild PhasesCompile SourcesでARC対象外にするソースのCompiler flagsに「-fno-objc-arc」と記述します。



一方、既存のプロジェクトをこれからARC化する際に、任意のクラスを対象外にしたい場合は、プリチェックを実行する直前にクラスの一覧からチェックマークを外します。

まとめ
非常に便利なARCですが、移行するものによってはstrong、weak参照やBridged Castなどの仕組みをよく理解する必要があります。

今回ちょっと時間が足りず、Bridged Castまで踏み込めなかったのですが、また追々自分の知識の整理の為に、記事にしたいと思います。

少々難しい部分もあるものの、ポイントさえ押さえておけばretain/releaseの煩わしさから解放され、すっきりしたコードのARCライフが待っているはずです!


明日は@ninjinkunさんです!