Natsu

iOSアプリ開発者。代表作はローン計算アプリiLoan Calc。 開発時のモットーは的確な設計をすること。今の状態に満足することなく、どこまでも成長していきたい、そんなことを考えつつ勉強に励む毎日です。

 

昨年から関わってきたiPad用教育ゲーム、「とけいのよみかたゲーム Clock Tuto」を無事にリリースすることができました。

企画は主に@mat_jpさんが、開発は私がメインで行っています。

とにかく、勉強しているということを意識せず、遊んでいるうちに色々なことが身に付くようなゲームを作りたくて、あれこれ試行錯誤しながら、今の形にたどり着きました。まだまだ至らないところはありますが、これからも少しずつ改善していく予定です。

Clock Tutoは、落ちものゲームの一種です。上から落ちてくる時計を適切な箱に入れることで、時計の読み方を習得していきます。

最初は、子供たちにとって身近な、食事時間と起床、就寝時間から始めます。その後、ちょうどの時間、30分、15分&45分と少しずつ難しくなっていき、最後は、任意の時間が読めるようになるというものです。

段階を経て進めていくうちに、自然と時計の読み方が身に付くような仕組みを心がけました。

実際、何人かの子供たちにお願いしたユーザーテストを見ていても、徐々に時計の読み方に慣れていく様子が確認できました。

今後も、子供たちに喜んでもらえるようなアプリを出していきたいと思っています。

Clock Tutoはただいまリリース記念セール中(85円)。この機会にぜひ!

とけいのよみかたゲーム Clock Tuto

Clock Tuto サポートサイト: http://tutoapps.com/
Clock Tuto ツイッターアカウント: http://twitter.com/tutoapps_jp/
App Store Badge EN white small

 

先日App StoreからリリースされたXcode 4.3ですが、個人的には結構驚きな変更がありました。ARCを利用している場合に、プロパティのデフォルト属性(オブジェクトの所有に関する属性)が変更になっているではないですか。

これまでのデフォルト属性はassign

オブジェクトの所有に関するデフォルト属性は、これまでassignでした。したがって、オブジェクトのプロパティで属性指定を行わないと警告が出ていたと思います。

また、Xcode 4.2 + ARC環境では属性指定は必須でした。これは、readonlyプロパティのときも同様です。属性を指定しないとエラーとなります(参考:[iOS5] ARC : プロパティ属性と使い方)。エラーになるのは、インスタンス変数生成時にどの所有修飾子をつけていいか分からないためです。

Xcode 4.3 + ARCでのデフォルト属性はstrong

Xcode 4.3でも、ARCを利用しない場合はこれまでどおりのようですが、ARCを使っているとデフォルト属性がstrongになっています。つまり、属性指定なしでの宣言が可能となったということです。

// interface
@property (nonatomic) NSString *string;

// impliment
@synthesize string;

この場合、生成されるstring変数はstrongとなります。もちろんエラーも警告も出ません。

環境ごとのデフォルト属性

実際に、Xcode 4.2, Xcode 4.3で属性指定なしのプロパティがどのように扱われているか、property_getAttributesを使って調べてみました。確認用に用意したコードは以下。showPropertyAttribute内でログを出力し、結果を確認します。

@interface AttributeTest : NSObject

@property (nonatomic) NSString *noattributeStr;
@property (nonatomic, strong) NSString *strongStr;
@property (nonatomic, weak) NSString *weakStr;
@property (nonatomic, unsafe_unretained) NSString *unsafeunretainedStr;
@property (nonatomic, retain) NSString *retainStr;
@property (nonatomic, assign) NSString *assignStr;

+ (void)showPropertyAttribute;
@end

@implementation AttributeTest

@synthesize noattributeStr, strongStr, weakStr, unsafeunretainedStr, retainStr, assignStr;
+ (void)showPropertyAttribute {
    unsigned int outCount;
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);

    for (int i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        fprintf(stdout, "%s\n", property_getAttributes(property));
    }
}
@end

結果のフォーマットは、”Tエンコードタイプ, … ,Vプロパティ名”となり、カンマ区切りの部分にプロパティの属性が表示されます。例えば、readonlyは”R”, retainは”&”, weakは”W”といった具合です(詳細は、Declared Properties : Objective-C Runtime programming Guideを参照のこと)。

Xcode 4.3 + ARC

まず、Xcode 4.3でARCを利用した場合の結果です。

結果:

T@"NSString",&,N,VnoattributeStr
T@"NSString",&,N,VstrongStr
T@"NSString",W,N,VweakStr
T@"NSString",N,VunsafeunretainedStr
T@"NSString",&,N,VretainStr
T@"NSString",N,VassignStr

この結果より、noattributeStrは”&”が表示されているためretainされていることがわかります。つまり、strongプロパティとして扱われていることになります。すぐ下にあるstrongStrと属性が全く同じになっていることが確認できます。

Xcode 4.3 + 非ARC


次に、Xcode 4.3で非ARCの場合です(以下、noattributeStrの結果のみ表示します)。

結果:

T@"NSString",N,VnoattributeStr

“&”が入っていませんので、これはassignプロパティであることを意味しています。従来どおりの仕様です。なお、非ARC環境で属性指定なしのプロパティを宣言すると、これまでどおりの警告が出ます。

No ‘assign’, ‘retain’, or ‘copy’ attribute is specified – ‘assign’ is assumed

Xcode 4.2 + ARC

続いて、Xcode 4.2でARCを用いた場合です。この場合、何もしないとエラーになってしまい確認ができませんので、まずはインスタンス変数を定義します。

@interface AttributeTest : NSObject {
    NSString *noattributeStr;
}
@property (nonatomic) NSString *noattributeStr;
@end

お察しのとおり、これでもまだエラーとなります。エラーの内容を見ると、

Existing ivar ‘noattributeStr’ for unsafe_unretaind property ‘noattributeStr’ must be __unsafe_unretained

となっています。これですでに、noattributeStrはunsafe_unretainedプロパティだと分かりました。変数宣言を__unsafe_unretainedに修正し、property_getAttributesを実行します。

@interface AttributeTest : NSObject {
    __unsafe_unretained NSString *noattributeStr;
}
@property (nonatomic) NSString *noattributeStr;
...
@end

結果:

T@"NSString",N,VnoattributeStr

インスタンス変数を__unsafe_unretainedとしているので当たり前の結果ですが、やはりretainされていません。なお、ここでも、Xcode 4.3+非ARCのときと同様の警告が出ます。

Xcode 4.2 + 非ARC

最後に、Xcode 4.2 + 非ARCの場合です。これは、Xcode 4.3 + 非ARCと全く同じですのでここでは省略します。

XcodeのバージョンとARCの有無による違いを見てきましたが、まとめると、“Xcode 4.3 + ARC”のときのみデフォルト属性がstrongとなり、その他はすべてこれまでどおり、デフォルト属性はassign(またはunsafe_unretained)です。Xcode 4.3でARCを利用する場合、この変更は頭に入れておいた方がよいでしょう。

ARCへの自動変換時はご注意を

Xcodeの各バージョンとARC対応の有無によるデフォルト属性の違いは上にまとめたとおりです。最後に、これに伴うXcodeの変更で最も大きい(かつ、場合によってはかなり危険)と思ったことです。

Xcode 4.2以降では、非ARCのコードをARCに対応させるための自動変換機能を利用できます。これは、Xcodeのメニューから、”Edit->Refactor->Convert to Objective-C ARC…”を選択することで実行可能です。

この自動変換によるプロパティ属性の置き換え方法が、大きく変更されています。Xcode 4.2では、retainプロパティはstrongプロパティに置き換わりました。つまり、

@property (nonatomic, retain) NSString *string;

は、

@property (nonatomic, strong) NSString *string;

に置き換わります。

これが、Xcode 4.3では、属性指定なしの宣言に置き換わるようになりました。つまり、

@property (nonatomic, retain) NSString *string;

は、

@property (nonatomic) NSString *string;

に置き換わります。

Xcode 4.3ではデフォルト属性がstrongですので、この変換は、Xcode4.2での変換と全く同じ意味を持ちます。したがって、変換自体は正しく行われていることになります。何が問題かと言うと、Xcode 4.3で自動変換したコードをXcode 4.2でビルドしようと思ってもエラーになってビルドできないということです。

みなさま、はまらないようにご注意ください。せめて自動変換のときくらいはstrongつけてほしい気もしますね。。

なお、ここで使っているXcode 4.3はMac App Storeよりダウンロードした公式バージョンです(Build 4D502)。iOSSDK 5.1がついているベータバージョンではありません。

質問、間違いの指摘などはツイッターでお願いします。@natsun_happy

1月 302012
 

早いもので、あっという間に2012年も1ヶ月が過ぎましたね。そろそろ今年もまた恒例の、一年の目標を掲げてみます。

とは言え、今年はプライベートに色々と変化がありそうなので、開発の方は少しスローペースになるかもしれません。そんな中でも、来年、再来年につなげていけるような一年にしていきたいと思っています。

教育アプリ(ゲーム)のリリース

昨年一年間、ずいぶんと力を入れてきた新アプリの開発。2011年の反省でも少し触れましたが、現在、@mat_jpさんと一緒に、楽しみながら学べる教育アプリを開発中です(iPad用)。

教育カテゴリもゲームもどちらも初めてで、分からないことだらけで始めた開発ですが、アプリ作りをとても楽しませてもらっています。

とにかく、まずはこれを形にします。

iLoan Calcアップデート

ご好評いただいているローン計算アプリiLoan Calc(サポートサイト)。すでに基本的な機能は十分そろっていますが、少しずつでも小さな改善をしていきたいと思っています。

具体的なアップデート内容は現在検討中ですが、4〜5ヶ月に一度のアップデートを目指していきます。

情報発信(ブログ更新)

ブログはこれまでどおり更新していきたいです。昨年、週1回更新という目標をたてましたが、なかなか難しいということがわかりました。

それにもめげずに、今年もできるだけ週1回ペースを守っていけるようにしたいと思います(と言いつつ、これが今年初の記事ですが・・・)。


技術力アップ

毎年のことですが、ベース力を維持、向上していくのは絶対不可欠。

今年は開発のペースこそ落ちるかもしれませんが、新しいOSやデバイスに取り残されないよう、常にアンテナを張って技術力を高めていきたいと思っています。

去年に引き続き、本や公式ドキュメント、英語ブログなどを読んで力を付けていきたいです。

iOS 5になって公式ドキュメントもかなり更新されています。まだまだ読み切れていないものが多いので、こちらもチェックしていく予定です。

新規アプリの開発

今、自分が欲しいアプリが二つあります。まずは、これらを自分のために形にしてみて、自ら使ってみることで機能のブラッシュアップなどができればと思っています。

どこまでできるか分かりませんが、来年、再来年につながる大事な一年として、無駄なく過ごしていければと思っています。

 

今年もまたこの季節がやってきました。すでにiTunes Connectも休暇に入ったことですし、本当に残すところわずかですね。今年1月に一年の目標を掲げました(2011年の目標)。全体の達成度はというと、65%くらいでしょうか。若干、方向性が変わってきていますが、一つずつ振り返ってみたいと思います。

iLoan Calcの展開:80%

このような目標をたてていました。

  1. 3ヶ月に1回のアップデート
  2. iLoan Calc for iPadリリース
  3. iLoan Calc for Androidの開発??

アップデートに関しては、今年は結構頑張りました。2月にバージョン3.1をリリースし、続けて3.1.1, 3.1.2, 3.1.3と少しずつ機能を強化。その後、7月に念願だったiPad対応(ユニバーサル化)を果たしました(バージョン4.0, 4.0.1)。最後は12月に4.1, 4.1.1をリリース。こちらは小さな機能追加です。

3ヶ月に1回という頻度こそなかなかキープすることはできませんでしたが、リリース回数、そして、iPad対応ができたことに関しては、満足しております。

最後のAndroid対応ですが、こちらは色々思うことがあり、今の段階では見送ることにしました。

その他、不動産業者さんや住宅販売業者さんから、何件かiLoan Calcを自社専用にカスタマイズして欲しいという依頼をいただいたのですが、残念ながらこちらはお断りすることになりました。実現できれば面白いことができそうだとは思ったのですが、開発リソースの問題やその他個人的な事情によりお断りせざるを得ませんでした。非常に残念です。

新規アプリケーション開発:50%

目標は、最低1本は新しいアプリをリリースする、でした。が、残念ながら今年は1本もリリースできませんでした。

とは言え、何もやっていなかったわけではなく、ただいま教育ゲームに奮闘中です。@mat_jpさんと一緒に企画中で、開発は主に私が担当しています。長い目で見てじっくりと検討しながら進めているため時間がかかっていますが、あと一息のところまできています。

来年前半にはリリースできるでしょう。

ということで、達成度は50%としました。簡単にサクッと1本作ってしまうのもいいですが、こうして色々悩みつつじっくり作っていくのもまた楽しいですね。

技術力アップ:80%

週4日以上、好きな本やドキュメントなどを読む、という目標をたてました。なかなか毎日継続するのは大変ですね。平均したら週3回くらいだったように思いますが、今年は、多くの新しいことを勉強しました。

特に、ゲーム開発を始めたこともあり、まったく未知だったゲームの世界について勉強したことが有意義でした。また、後半にきて一番大きかったのはiOS 5でしょうか。変更内容がかなり大きかったため、こちらは継続して勉強中です。

読んで面白かったと思う本をいくつかご紹介。リンクはブログ内の書評ページまたはAmazon.co.jp(書評がないもの)です。

ゲーム開発

iOS開発一般

また、購読している英語ブログが増えました。中でもお気に入りのものをいくつかご紹介します。

  • RAYWENDERLICH : チュートリアルが分かりやすくて素晴らしい。特にiOS 5のチュートリアルではかなりお世話になっています。
  • [iOS developer:tips]; : Tipsやオープンソースの紹介など。
  • mikeash.com: NSBlog : CocoaやObjective-Cの詳細がメイン。
  • ManiacDev.com : チュートリアルやオープンソースの紹介など。

情報発信:70%

今年は、自分一人で勉強するだけではなく、それらの内容を発信していける年にしたいと思っていました。そのひとつとして、1週間に1回のブログ更新、という目標がありました。

1週間に1回。私にとっては、できそうでできない数字でした。特に、技術的な内容を記事にしようとすると、詳細を調べたりサンプルプロジェクトを作って検証してみたりと、意外と時間がかかり大変です。

今年すべてのブログ更新回数は40回。そのうち開発関連の内容は29回でした。来年はもう少し頑張っていきたいな。

ライフプランニングの勉強:60%

金利、保険、ローンあたりの本を何冊か読みました。

iLoan Calcの機能として、ローンだけではなくクレジットの試算も可能にするかなど、いくつか検討しました。その際、金利に関する以下の2冊が役立ちました(リンクAmazon.co.jp)。

また、ローンについては、淡河氏のウサギとカメの本がお気に入りです。資金計画について非常にわかりやすく解説されています。

書評)資金計画なくしてローン選びはできない! [書評] ウサギのローン カメのローン〜最高のローンを選ぶ方法

他には、ちょっと気になった記事があったのでこんな分析もしてみました。
30代サラリーマンの住宅購入は本当に危険か? シミュレーション結果を分析

ライフプランニングの勉強をする、という漠然とした目標でしたが、自分なりにアンテナをはって意識していられたのではないかと思っています。ただ、具体的な成果がないので達成度は60%といったところでしょうか。

まとめ

なかなか具体的な成果を出すのが難しい一年でしたが、地盤固めはできたのではないかと思っています。年が明けたら新アプリリリースに向けて最終スパートの予定です。

ファイナンスに続き、興味のある教育分野でのアプリ開発に関わることができ、非常に充実した一年でした。リリースまではたどり着けませんでしたが、ここに至るまでの過程で得たものは大きいのではないかと思っています。

また、お受けすることはできませんでしたが、アプリ開発の依頼をしてきてくださった方々に、この場を借りて改めて感謝いたします。興味を持っていただいたこと、非常に嬉しく思います。

来年はどんな一年になるのでしょうか。今年の成果をうまく繋げていきたいものですね。

では皆様、良いお年を。

 

iOS 5でこっそりと変更になっていたことに気がついたことのひとつとして、UITableViewCellのデフォルト背景色があります。

背景色が変更された!

UITableViewCellをinitWithStyle:で作成すると、iOS 4までは背景が白(R:1.0, G:1.0, B:1.0)のセルが作成されていました。

ところが!

iOS 5では、なんと真っ白ではないのです。背景色は(R:0.97, G:0.97, B:0.97)となっていました。びっくり。

カスタムセルを作っている場合には注意が必要

背景色が変更になっているため、デフォルトのセル上に別のUILabelなどを乗せて独自のカスタムセルを作っている場合には注意が必要になります。

subViewの背景は透過させない方がパフォーマンスがよくなるというので、あえて、背景色を白にしたUILabelを乗せていたのですが、残念なことにOS 5になったらここだけ微妙に色が合っていませんでした。しかも、デバイスの輝度を下げているとなかなか気がつきにくいのでご注意を。

対処方法

subViewすべての背景を(R:0.97, G:0.97, B:0.97)にすればいいかというと、そうではありませんよね。もちろん、Deploypment TargetがOS5.0以上なら問題ありませんが、OS 4以下もサポートするアプリではそうはいきません。今度は、OS4以下でセルの背景色とsubViewの背景色が合わなくなってしまいます。

そんなわけで、カスタムセルのクラス(UITableViewCellのサブクラス)で真面目にsetBackgroundColor:メソッドをオーバーライドしましょう。

- (void)setBackgroundColor:(UIColor *)backgroundColor {
    [super setBackgroundColor:backgroundColor];

    self.mySubView.backgroundColor = backgroundColor;
}

これで、subViewの背景色はいつでもセルの背景色と一致します。

もちろん、OSバージョンを見て背景色を分けてもいいですが、できるだけハードコーディングは減らした方がいいと思うので、setBackgroundColor:メソッドのオーバーライドがお勧めです。

これなら、次のバージョンでさらに背景色が変更になってももう心配はいりません。

そもそも、最初から真面目に実装していればよかったものの、ちょっと(本当にちょっと・・・)横着をしていました。この横着が、今回のような事態を招いてしまったわけで、これからは気をつけていこうと思います。反省。。

 

これまでの記事はこちら:

ARCまとめの最終回はAutoreleaseとキャストについてです。また、最後で簡単にですが、Xcodeの環境設定についても触れます。

Autorelease

ARC環境下では、これまでのNSAutoreleasePoolは使えません。そうは言っても、別にAutorelease環境がなくなってしまったわけではなく、作法が少し変わったのですね。

まずは、参考までにmain.mを見てみましょう。

非ARC(マニュアルメモリ管理)

int main(int argc, char *argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

ARC

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

ARC環境では、NSAutoreleasePoolを作成する代わりに、@autoreleasepoolブロックが使用されています。

@autoreleasepoolブロックの始まりが、NSAutoreleasePoolオブジェクトの生成で、ブロックの終了が、NSAutoreleasePoolオブジェクトの解放だと思えばよいでしょう。

@autoreleaseを使うと、ブロック内で生成されたautoreleaseオブジェクトは、すべてブロックの終了とともに解放されます。

なお、@autoreleasepoolブロックは、ARC, 非ARCに関係なく使用できるようです。

Bridged Cast

これまで、(void *)と(id)間のキャスト(CとObjective-C間のキャスト)はそのまま記述できていましたが、ARC環境下では、明示的にオーナーシップの所在を明らかにする必要があります。これは、Objective-CのオブジェクトとCore Foundationのオブジェクト間のキャスト(Toll-Free bridgedが可能なもの)でも同様です。

Toll-Free Bridgedとは

Toll-Free Bridgedとは、NSArrayとCFArrayRef, NSStringとCFStringRefのように、オブジェクト構造が同じためにそのままキャストが可能なものを言います。

※ Toll-Free Bridgedのオブジェクト一覧は、
Core Foundation Design Concepts – Toll-Free Bridged Types
を参照のこと。

例えば、非ARC環境では、Toll-Free Bridgedのオブジェクトは、以下のように単純にキャストすることができました。

NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (CFStringRef)string;

Core Foundationの関数にObjective-Cのオブジェクトを引数として渡すときも同様です。

ARC環境ではコンパイルエラー

しかし、上記のようなコードは、ARC環境ではコンパイルエラーとなります。
“Cast of Objective-C pointer type ‘NSString *’ to C pointer type ‘CFStringRef’ (aka ‘const struct __CFString *’) requires a bridged cast”

エラー内容を見てみると、bridged castが必要だと言っています。さらに、コンパイラが親切に修正例も教えてくれています。候補は二つあって、
“Use __bridge to convert directly (no change in ownership)”
“Use __bridge_retained to make an ARC object available as a +1 ‘CFStringRef’ (aka ‘const struct __CFString *’)”
です。

オーナーシップ権を変更しないのなら、__bridgeを使い、CFStringRefの方でretainカウントを+1したければ、__bridge_retainedを使いなさいということですね。

Objective-CオブジェクトがARCの対象であるのに対し、Core FoundationオブジェクトはARCの対象ではありません。したがって、これらの間でキャストをする際には、コンパイラにオーナーシップ権をどうすべきか指示する必要があるというわけです。

3つのbridgeタイプ

Bridgeタイプには上述した__bridge, __bridge_retainedの他にもう一つ、__bridge_transferというものがあります。

__bridge

NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (__bridge CFStringRef)string;

単純なキャストを行うときに使用します。オーナーシップ権は移行しませんので、キャスト元の変数(上の例ではstring)が破棄された(スコープ外に出るなど)あとでの、キャスト先の変数(cfString)の使用は非常に危険です。

__bridge_retained

NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (__bridge_retained CFStringRef)string;
...
CFRelease(cfString); // Core FoundationはARC対象外なので自分でreleaseする。

キャストと同時に、キャスト先の変数(cfString)のretain countが+1されます。これにより、キャスト元の変数(string)が破棄されたあとでも、安全にキャスト先の変数を使用することができます。ただし、上記のようにObjective-CからCore Foundationにキャストした場合、Core FoundationがARC対象外なので、最終的に自分でcfStringをreleaseして上げる必要があります。


__bridge_retainedキャストを用いる代わりに、CFBridgingRetain()関数を利用することもできます。

NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = CFBridgingRetain(string);
...
CFRelease(cfString); // Core FoundationはARC対象外なので自分でreleaseする。

__bridge_transfer

オーナーシップ権が移行されます。キャストと同時に、キャスト元の変数はreleaseされ、キャスト先の変数がretainされると考えればよいでしょう。__bridge_transferは、Core FoundationオブジェクトをObjective-Cオブジェクトにキャストするときによく利用されると思います。

CFStringRef cfString = CFStringCreate...();
NSString *string = (__bridge_transfer NSString *)cfString;

// CFRelease(cfString); __bridge_transferを使ったのでreleaseは不要!!

__bridge_transferキャストを用いる代わりに、CFBridgingRelease()関数を利用することもできます。このコードだと、CFReleaseが不要な理由が明らかでしょう。

CFStringRef cfString = CFStringCreate...();
NSString *string = CFBridgingRelease(cfString);

キャストのタイプを決めるポイント

キャストのタイプを決めるときは、

  • キャスト元、先のどちらがARC対象でどちらがARC非対象か
  • ARC対象外のオブジェクトのreleaseは誰がすべきか
  • それぞれのオブジェクトの生存範囲はどこか(ARCの場合スコープ外に出れば破棄される)

あたりをポイントに考えると分かりやすいと思います。

Xcodeの環境設定

最後になりましたが、簡単にXcodeの環境設定についてです。

非ARCの旧プロジェクトをARCに対応させたい場合、Xcodeのメニューから”Edit”->”Refactor”->”Convert to Objective-C ARC…”を選択すればOKです。最初にコンパイラがプレチェックをして、問題なければ移行できます。

なお、このとき、ARCに変換したいコードを選択可能ですので、非ARCのまま残しておきたいコードは対象コードのチェックを外しましょう。

また、プロジェクトのターゲット設定で、”Build Phases”->”Compile Sources”の”Compiler Flags”に”-fno-objc-arc”が追加されているコードはARCの対象外となります。外部ライブラリを使用している場合など、自分ではコントロールしきれないものはARCの対象から外しておくと便利だと思います。

ちなみに、Refactorのプレチェックは、まだまだおかしなエラーも出ることがあるようです。何度もやると結果が違うので、このあたりはまだ試行錯誤が必要かもしれません。

プレチェック時に出るエラーに関しては、以下のチュートリアルの最後にあるMigration Woesが参考になります。
Beginning ARC in iOS 5 Tutorial Part 1 | Ray Wenderlich

まとめ

少し駆け足になりましたが、Autorelease、キャストと、Xcodeの環境設定についてまとめました。これで、以前の記事も合わせると、ARCに関しては大まかですが一通り理解できたことになります。

最初は少し取っ付きにくい部分がありますが、慣れてしまえばかなり使いやすいと思います。何よりもコード量が減るのがありがたいですね。

ARCを使うことで、不正アクセスによるクラッシュなどはずいぶん減らせるのではないでしょうか。ただし、循環参照やキャストに気をつけないとメモリリークは減りません。むしろ増えてしまうかもしれないので注意しましょう。

質問、間違いの指摘などはツイッターでお願いします。@natsun_happy

参考資料

11月 252011
 

これまでの記事はこちら:

循環参照とは

今回は、強参照(Strong reference)を使うときに注意したい循環参照(Strong reference cycle)についてです。循環参照とは、その名の通り、2つ以上のオブジェクトが強参照し合うことにより、どちらのオブジェクトも破棄できない状態を言います。

ここで、循環参照が発生するのは、お互いに”強参照“しているときです。複数のオブジェクトが親子関係を持つ場合を考えてみます。

アドレス帳オブジェクトAddrBookと、そのエントリーEntryがあるとします。AddrBookはEntryオブジェクトのentryを、Entryは、自身がどのアドレス帳に含まれているかを示すAddrBookオブジェクトaddrBookを持ちます。

このとき、もし、entryもaddrBookも強参照だと循環参照が発生します。

ARC strong reference cycle


この場合、Entryオブジェクトは、AddrBookから強参照されているので、破棄されません。一方で、Entryオブジェクトが破棄されないと、AddrBookオブジェクトへの強参照もなくならないため、こちらも破棄されないことになります。

弱参照を使って解決

このように、複数オブジェクトが親子関係を持つ場合には、片方を弱参照にすることで循環参照を回避することができます。一般的には、親オブジェクトが子オブジェクトのオーナーとなり(強参照し)、子オブジェクトは、親オブジェクトを弱参照するのみとします。

ARC avoid strong reference cycle


これであれば、AddrBookオブジェクトを強参照している変数がなくなれば、AddrBookオブジェクトは破棄され、それと同時にEntryオブジェクトへの強参照もなくなります。

また、先にAddrBookオブジェクトが破棄された場合でも、EntryオブジェクトのaddrBook変数にはZeroingによりnilが代入されますので、破棄済みオブジェクトにアクセスしてクラッシュするような心配もありません。

Delegateパターンの場合

Cocoaではよく使われるdelegateパターンですが、ここでも循環参照を避けるために弱参照を使う必要があります。

ViewControllerから、DetailViewControllerをModalViewなどで開く場合、DetailViewControllerを閉じる動作を決めるために、delegateを設定することがよくあります。

ARC delegate pattern


ここで、ViewControllerがdetailViewControllerを強参照している場合、delegateを弱参照にしないと循環参照が発生します。そもそも、delegateは、親となるViewControllerが存在しなくなった時点で意味をなさなくなるので、弱参照が適しているはずです。

なお、自分で作成したクラスのdelegateにweakプロパティを使っている場合、Zeroingにより、参照先が破棄されたら自動的にnilがセットされますので、わざわざ自分でnilをセットしなくても問題ありません。

Blocksの場合

Blockも一つのオブジェクトだと考えられます。Blockによるキャプチャに注意しておかないと、ここでも循環参照が発生します。

Blockへのcopyプロパティを持つMyObjectを例に考えてみます。

typedef void(^MyBlock)(void);

@interface MyObject : NSObject
@property (nonatomic, copy) MyBlock block;
@property (nonatomic, strong) NSString *str;

- (void)performBlock;
@end
@implementation MyObject
@synthesize block, str;

- (void)performBlock {
    if (self.block) {
        self.block();
    }
}
@end

呼び出し側では以下のようにしたとします。

MyObject *object = [[MyObject alloc] init];
object.str = @"hoge";

object.block = ^{
    NSLog(@"block: str=%@", object.str);
};
[object performBlock];

ARC strong reference cycle blocks

Block構文の中でobjectを参照しています。したがって、object.blockは、objectをキャプチャし保持します。これにより、objectがblockを強参照し、blockがobjectを強参照することになります。これが、Blockによる循環参照です。


解決方法 その1: __block修飾子を使う

Blockによる循環参照を回避する方法はいくつかありますが、その一つ目は、Block内で、処理が終了したらobjectを解放する方法です。そのためには、__block修飾子を使用して、objectを読み書き可能にする必要があります。

なお、ARC以前では、__block変数はキャプチャされませんでしたが、ARCの場合挙動が違います。__block変数が持つ意味は、Block内で読み書き可能となるだけで、キャプチャに関しては通常の変数と変わりません。

__block MyObject *object = [[MyObject alloc] init];
object.str = @"hoge";

object.block = ^{
    NSLog(@"block: str=%@", object.str);
	object = nil;
};
[object performBlock];

これで、blockがobjectの強参照をやめるため、循環参照が解消されます。しかしながら、この方法には一つ欠点があります。objectの解放がBlock内で行われているため、Blockが実行されないと循環参照したままとなります(上記コードで、[object performBlock];をコメントアウトすると循環参照します)。

解決方法 その2: __weak修飾子を使う

もう一つの解決方法です。この方法では、Block内からの参照に弱参照を使います(キャプチャされるのはweak変数)。

MyObject *object = [[MyObject alloc] init];
object.str = @"hoge";

__weak MyObject *weakObject = object;
object.block = ^{
    NSLog(@"block: str=%@", weakObject.str);
};
[object performBlock];

これだと、blockが参照しているweakObjectは弱参照ですので、循環参照は発生しません。

さらに、この方法をもう少し安全にしたのが以下です。

MyObject *object = [[MyObject alloc] init];
object.str = @"hoge";

__weak MyObject *weakObject = object;
object.block = ^{
	MyObject strongObject = weakObject;
	if (strongObject) {
	    NSLog(@"block: str=%@", strongObject.str);
	}
};
[object performBlock];

ここで例にあげたようなシンプルな実装ではここまでする必要はありませんが、非同期でBlocksを使う場合など、weak変数であるweakObjectがnilになってしまう可能性があります。したがって、一度、strong変数として保持しておき、nilチェックを行うということです。

注意: キャプチャされるのは str ではなく object

今回、あえてBlock内で使う変数をobject.strとしてみました。ここで注意が必要なのは、キャプチャされるのは、strではなく、objectそのものだということです。

したがって、仮にMyObjectがNSInteger型のvalueというプロパティを持っていたとして、Block内でobject.valueを利用しても結果は同じです。

Blocksを利用する場合には、Block内で利用している変数とインスタンス変数との関係をよく考えて、循環参照が起きないように注意しましょう。

まとめ

個人的には、ARCの利用で一番怖いかなと思っている循環参照についてまとめました。循環参照していても気がつきにくいので、知らないうちにメモリリークして悲しい事態にならないよう、実装時には細心の注意を払いたいものです。

質問、間違いの指摘はツイッターでお願いします。@natsun_happy

参考資料

 

今回は、ARCを用いた場合のプロパティ利用に関するTipsです。

ARCって何?と言う方は、まずはこちらからどうぞ。

一般的なOutletにはweakプロパティを使う

Interface Builderなどを用いて作成したOutletは、一般的に別のview(例えばUIViewControllerのviewなど)のsubviewであることがほとんどです。したがって、これらのOutletのオーナーとなるのはsuperviewのみで十分だと言えます。

ViewControllerは、自身がOutletのオーナーになる必要がないので、この場合、weakプロパティの利用が適しています。

もちろん、UIViewControllerのviewや、UIWindowのwindowなど、トップレベルのviewに関してはstrongプロパティを使う必要がありますが、自分でこれらのOutletを設定することはまれでしょう。

weakプロパティを使うことで、不要な循環参照 (Strong reference cycle) を避けることができます。

ARC: weak property is used for IBOutlet


viewDidUnloadの簡略化

Outletにweakプロパティを利用することには、もう一つ大きなメリットがあります。

メモリ不足が検知されたとき、viewが表示されていないUIViewControllerはすべてのview(自身のviewだけではなくsubviewもすべて)をunloadします。そのためviewDidUnloadが呼ばれるのですね。

したがって、もしstrongプロパティを使っていた場合、このタイミングで必ずオブジェクトを解放する(参照をやめる)必要があります。つまり、

@property (nonatomic, strong) IBOutlet UILabel *label;

に対しては、viewDidUnload内で必ず、

- (void)viewDidUnload {
	self.label = nil; // nilをセットして参照をやめる(オーナーシップ権を破棄)
	[super viewDidUnload];
}

としてあげる必要があるということです。

ここでもし、self.label = nil の処理を忘れてしまうと、UIViewControllerがlabelオブジェクトをいつまでも参照してしまい、結果としてlabelオブジェクトは破棄されません。せっかくシステムがunloadしてくれて、誰からも利用されていない状態なのに、そのオブジェクトを保持し続けてしまうのです。画面数が多いアプリでこれをやってしまったら、memory warningが上がっても不要なviewが解放されずにいつまでもメモリに空きが出ないでしょう。

しかし、weakプロパティを使っている場合、この処理は不要です。

@property (nonatomic, weak) IBOutlet UILabel *label;

この場合、システムがlabelオブジェクトをunloadした時点で、このオブジェクトはもう誰からも強参照されなくなります。したがって、ARCのルールに則りlabelオブジェクトはここで破棄されます。もちろん、破棄された時点でlabel変数にはnilが代入されます(__weak変数は、参照際のオブジェクトが破棄されたら自動的にnilが代入されるため)。


- (void)viewDidUnload {
	// この時点ですでに、labelにはnilがセットされている
	[super viewDidUnload];
}

これであれば、不要にオブジェクトを保持し続けてしまうことを避けられます。

viewDidUnloadでの処理がOutletの解放のみだった場合、結果的にviewDidUnloadのオーバーライド自体不要になるわけです。

このように、weakプロパティを使うことで、viewDidUnloadの実装を簡略化することができ、ミスによるトラブルを避けることが可能です。

strongプロパティを使うべきとき

上でも少し触れましたが、例外としてstrongプロパティを使わなくてはいけないこともあります。これは主に、Interface Builder(または、Storyboardの各scene)における、トップレベルのviewやwindowをOutletとして設定するときです。

このような場合は、strongプロパティにしておかないと、誰からも強参照されることなくload後に破棄されてしまので注意が必要です。

まとめ

もちろん、Outletのプロパティとしては、weakでもstrongでも動作可能です。しかしながら、weakプロパティを利用することでケアレスミスによるバグをさらに減らすことが可能です。このように、strong, weakは、状況に応じて使い分けることが重要だということです。

次回は、循環参照(Strong reference cycle)についてです。こちらも、strong, weakの使い分けに関する内容です。

質問、間違いの指摘などはツイッターでお願いします。@natsun_happy

参考資料

 

前回、ARC OverviewでARC(Automatic Reference Counting)の基本概念と__strong, __weak等の修飾子についてまとめました。今回は、Objective-Cではよく使われるプロパティについてみていきます。

オーナーシップ属性

ARC対応により、これまでのセッターに関するプロパティ属性に加えていくつかの属性が追加されました。以下の表が、ARCで有効な属性とそのオーナーシップ(修飾子)との関係です(太字がARCによって追加になったもの)。

プロパティ属性 修飾子 オーナーシップ
strong __strong あり
weak __weak なし
copy __strong あり(コピーオブジェクトが代入される)
unsafe_unretained __unsafe_unretained なし
assign __unsafe_unretained なし
retain __strong あり

strong

__strong修飾子に対応するプロパティ属性です。strong属性を用いたプロパティは参照先オブジェクトのオーナーとなります。

weak

__weak修飾子に対応するプロパティ属性です。__weak修飾子を持った変数と同様、weak属性のプロパティも、参照先のオブジェクトが破棄されたら自動的にnilが代入されます。weak属性を用いたプロパティはオーナーシップ権を持ちません。

weak属性は、delegateやOutletの変数に最適です。

なお、iOS 4では__weak修飾子が使えないため、プロパティのweak属性も使えません。この場合は、後述のunsafe_unretainedを使いましょう。

copy

__strong修飾子に対応しますが、実際にはコピーオブジェクトが代入されます。copy属性を用いたプロパティは参照先オブジェクトのオーナーとなります。

unsafe_unretained

__unsafe_unretaind修飾子に対応するプロパティ属性です。iOS 4では、weak属性が使えないため、オーナーシップ権を持たないプロパティを作成したい場合、こちらを利用する必要があります。

assign

主にスカラー変数(intやBOOLなど)のプロパティ属性として利用されます。

オブジェクトに対して使用することも可能ですが、オーナーシップ権を持たないプロパティを作成したい場合、weak(iOS 5以上)もしくはunsafe_unretained(iOS 4を含む)を利用すべきでしょう。

retain

retain属性も利用することは可能です(strong属性と同じ働きをします)。しかしながら、ARC対応のコードでは、strong属性を使った方が明白でよいでしょう。

読み書きに関する属性 (readwrite, readonly)

読み書きに関する属性にはreadwriteとreadonlyの二通りが存在します。ARCを利用すると、オブジェクトに対してreadonly属性を用いるときに注意が必要です。

これまで、

@property (nonatomic, readonly) NSString *name;

のような書き方が可能でした。この書き方ではセッターに関する属性(オーナーシップ属性)を指定していませんが、そもそもreadonlyなのでセッターは不要ですから問題がなかったのですね。

しかしながら、ARCを利用すると、上記のコードでは以下のようなコンパイラエラーとなります。
“ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute”

ARCでは、オーナーシップ属性のないプロパティは実装できないということです。このコードは、以下のように書き換えることでエラーを回避できます。

@property (nonatomic, strong, readonly) NSString *name;

実際にはreadonlyプロパティなのでstrongは意味をなしませんが、このように記述する必要があるようです。

なお、スカラー値のプロパティはデフォルトとしてassign属性となりますので、あえて明示的にassignを設定しなくても問題はありません。

まとめ

前回の修飾子に加えて、プロパティ属性についてまとめました。これで、ARCの基本中の基本は抑えたことになります。

次回は、プロパティ関連の続きとして、Outletはweak属性を使うと便利だという件についてまとめたいと思います。

質問、間違いの指摘などはtwitterでお願いします。@natsun_happy

参考資料

© 2012 iOS 開発ブログ Natsu's note Suffusion theme by Sayontan Sinha