2012-02-19
他言語習得者用、Objective-Cの構文とキーワード一覧
他の言語を習得している人がObjective-Cを勉強しようとすると、
独特の構文や不思議なキーワードが登場して、大抵面食らうと思います。
膨大な数のAPIに圧倒されることもあって、
何から手をつけていいのやらわからなくなるようです。
まず、Objective-Cの構文やキーワードと、
自分の知っている構文やキーワードと、どう関係があるのかを知ることが、
一番の近道ではないかと思います。
ということで、Objective-Cの主要キーワードを概要だけ、ざらっと解説します。
ちなみに、Cの完全上位互換ですので、Cで使えるものはObjective-Cでも使えます。
@interface
クラスを宣言します。
インタフェースと書いてありますが、はっきり言ってクラスです。
メソッドの呼び出し
[object メソッド名]
で呼び出します。
アロー演算子(->)やドット(.)では呼び出せません。
ちなみに、不明なメソッドを呼び出そうとしても、コンパイルは通ります。
実行時にメソッドが見つからない場合、実行時エラーが発生します。
@implementation
@interfaceで宣言したクラスの実装を書く部分です。
@protocol
プロトコルは、JavaやC++あたりでいうところのインタフェースです。
ただ、Objective-Cのプロトコルは、デリゲート以外で使われることはほとんどありません。
ちなみにデリゲートはGOFのTemplate Methodだと思っておけば大体あっていると思います。
@property
@propertyは、setter, getterアクセサを宣言します。
のように使います。
C#やActionScriptなどにもアクセサ生成がありますが、
それに比べると、オプションがやたら豊富です。
この機能はよく使いますので、詳しく調べておくと便利です。
@synthesize
propertyで宣言されたプロパティのsetter,getterを自動生成します。
とすると、プロパティで使用する変数を明示的に指定することができます。
@implementationの中に記述します。
@synchronize
Javaのsynchronizeと同様、同期を行うためのものです。
実際には、NSLockが利用されるようです。
#import
要するに#includeですが、
#ifndef AAA
#define AAA
#endif
のようなインクルードガードを書く必要がなくなります。
継承
@interfaceの継承は1つだけで、@protocolの継承は複数可能です。
@interfaceの多重継承はできません。Java風味です。
ちなみに、@protocolの継承は、「プロトコルを採用する」とも言うようです。
オブジェクトの生成と破棄
基本は[クラス名 alloc]で生成、破棄は[オブジェクト release]で行います。
オブジェクトは参照カウンタ方式で管理されています。
参照カウンタを増やす場合はretainです。
releaseを呼び出す度に参照カウンタが減ります。
0になったらオブジェクトが破棄されます。
コンストラクタ、デストラクタ
厳密に同じではありませんが、
init, deallocは役どころが大体似ています。
その場合、習慣的に、initWith○○というメソッド名が用いられます。
initは、
[ [クラス名 alloc] init]
のように、大抵allocと同時に呼び出されます。
new、というものもあるのですが、使わない方がいいでしょう。
アクセス指定子
変数には、@private, @protectedというように指定できます。
メソッドには適用できません。メソッドは基本的に全てpublicです。
Objective-Cのメソッドは、外から全く見えていなくても、
存在さえしていれば呼び出しが可能です。
とはいえ、ヘッダ部分に外から呼び出して欲しくないメソッドを書くのは可読性を下げますので、
その場合は無名カテゴリを使うと良いでしょう。
クラスメソッド
- (void)foobar
のように、先頭がハイフン(-)だとインスタンスメソッドですが、
- (void)foobar
のように、先頭をプラス(+)にすると、クラスメソッドになります。
this(自己参照)
selfです。
ドット構文
プロトコルは、ドット構文で呼び出すことができます。
@property (assign) int width;
というプロトコルを宣言しているならば、
self.width = 10;
ならば、widthのsetterを呼び出し、
int w = self.width;
ならば、widthのgetterを呼び出します。
実態は、[self setWidth:10], [self getWidth]と全く同じです。
無名関数 (ブロック構文)
^(変数){
...
}
といった形式で、無名関数を作ることができます。
JavaScriptの無名関数のようにバリバリ使われることは稀です。
大体GCD(Global Central Dispatch)か、アニメーションの定義で使われることがほとんどかと思います。
template
ただし、メタプログラミング的な機能はあります。
ありますが、私は使ったことがありません。
なんかやたらマニアックです。
まあ、template自体も大概マニアックですが。
以下2つはObjective-C独自。
カテゴリ
@interface クラス名 (カテゴリ名)
のように、インタフェース宣言の後ろに括弧付きで宣言することで、
インタフェースのメソッドを拡張できます。変数の追加はできません。
@interface foo (bar)
@interface foo : bar
の大きな違いは、
継承は、[ [foo alloc] init]を、[ [bar alloc] init]にする必要がありますが、
カテゴリは[ [foo alloc] init]を書き換える必要がありません。
カテゴリ名は無名でも可能です。
無名カテゴリは外部に公開したくないメソッドを宣言するのに利用できます。
その場合、カテゴリ宣言を.mファイル内に記述します。
他言語で一番似ているのはJavaScriptのprototypeでしょうか。
自動解放プール
オブジェクトに対してautoreleaseを呼び出すと、
そのオブジェクトは自動解放プールに投げ込まれます。
自動解放プールは、一時オブジェクトのゴミ箱みたいなもので、
NSAutoreleasePoolというオブジェクトが管理しています。
ただし、GCのようにタイミングを見計らって掃除をしてくれるのではなく、
自分でゴミ箱を空にする必要があります。
あくまでも、解放タイミングを遅らせるためのものです。
@selector
@selector(メソッド)というように使います。
@selector()はSEL型を返します。
関数ポインタは型が完全一致していないとコンパイルエラーがでますが、
それに比べるとSEL型は緩いです。
ちなみにリフレクションをするときにはSEL型を使います。
@"文字列"
2012-02-03
NSAutoreleasePoolとGCD
昨日の続き。
NSAutoreleasePoolは、新しいスレッドを作成したとき、
必ずスレッドごとにNSAutoreleasePoolも一緒に作成する必要があります。
メモリ管理プログラミングガイドにも、そのように書いてあります。
C/C++,C#,Javaあたりでも、オブジェクトを生成したスレッドと、破棄するスレッドが異なると、
ほぼ確実にクラッシュするか例外をスローしてします。
NSAutoreleasePoolも同様で、iOSはそこまでケアしてくれないということになります。
じゃあGCDはどうなの?必要なの?ということですが、
GCDの場合は取り立てて必要ないようです。
ちなみにGCD(Grand Central Dispatch)は、iOSに用意されている、
並列プログラミング用の機構のひとつです。
NSThreadでスレッドを作成するよりも簡単で、省メモリで、
アフィニティも勝手に考慮してくれて高速処理してくれるという優れものです。
実際に使用する場合は、dispatch_queue_tというキューオブジェクトを生成し、
ブロック構文を使って実行します。
適当にググるとサンプルが見つかると思います。
また、並列プログラミングガイドというAppleの資料にも詳しく載っています。
で、話は戻りますが、GCDを使う場合は、キューごとにNSAutoreleasePoolが用意されるので、
絶対に用意しなければならないというものではないそうです。
ただし、昨日のエントリにも書いたように、
コンビニコンストラクタで作った一時オブジェクトが溜まりまくる可能性があるので、
for文などで大量に一時オブジェクトを生産する場合は、
NSAutoreleasePoolを適切に作ってあげる必要があります。
ちなみに、この話は、以下のQAを参考(というかほぼそのまま?)にしています。
それにしても自動解放プールという名前がなんとなく実像とあってないのではと思います。
全然自動じゃないので。
だってautoreleaseという名前だと、
スコープ抜けたら勝手に破棄してくれるんじゃないかと期待するじゃないですか。
遅延解放プールとかゴミ箱とかにのほうがあってる気がします。
2012-02-02
autoreleaseは結構怖かった
iOSのautoreleaseって何の気なしに使っていたのですが、
これって下手するとメモリリーク状態になるんですね。
メモリ管理プログラミングガイドだけはちゃんと読んでおくべきでした。
autorelease付きのオブジェクトは、NSAutoreleasePoolという自動解放プールにスタックされていきます。
自分でNSAutoreleasePoolを作らなければ、
main関数で最初に用意されるNSAutoReleasePool1つだけで、
自動解放プールのスタックを管理することになります。
そして、この自動解放プールが実際にスタックに溜まったオブジェクトを破棄するタイミングは、
NSAutoreleasePoolが破棄されるときです。
つまり、NSAutoreleasePoolがmain関数で用意される1個だけだと、
main関数抜けるまで、autoreleaseで解放することにした一時オブジェクトは解放されないわけです。
いやもう大体のiOS開発者はご存知かと思いますが。
面倒くさいからautorelease使わないようにしよう、としたとしても、
確か[NSString stringWithFormat:]や、[UIImage imageNamed:]などでは、
autorelease付きでオブジェクトが戻ってきたはずです。
第一、普通に[ [hoge alloc] init]した場合でも、hogeのinit実装によっては、
autorelease付きオブジェクトが生成される可能性があります。
ということで、WebAPIなどを使って動的に画面更新を繰り返している場合、
結局autoreleaseオブジェクトが解放スタックにボンボン投げ込まれると思います。
そうしたとき、局所的に作ったNSAutoreleasePoolを、適切なタイミングで破棄するよう、
メモリ管理をしなければならないということになります。
動的更新している場合、適切なタイミングっていつだよって話になるわけで、
C++のように普通にnew/deleteをさせてくれた方が余程ありがたい気がします。
2011-11-16
iOS5が色々酷い
iPhoneアプリを作っていた人が退職するに当たり、
なぜか全く関係ない私の下に仕事の丸投げをしてった上、
ろくに説明もしないで出てったその前任者のコードがアンチパターンの見本市だったという、
若干やるせない日々を送っているのが近況のわけですが、
泣きっ面に蜂のごとく、iOS5がトドメを刺してくれました。
といってもiOS5は実際まだ1時間くらいしか触ってないのですが、
色々と動作が不安定で、一体何が原因でクラッシュしたのか検討もつかないという不思議OSです。
最新版のXCodeの使い方がよくわかっていないせいかもしれませんが。
iOS4.xでは何も問題なく動いている分、実に不思議です。
動作云々の前に、iOS4からそのまま移植したコードをコンパイルしたら、
画面が真っ白になるという現象にまず出くわして、先制パンチを食らいました。
window.backgroundColor = [UIColor blueColor];
のようにコードを書いても、全く色が変わりません。
bringSubViewToFromを呼んでも、全く効果がありませんでした。
なんというか、UIWindowの上に、白いビューがべたっと貼られている感じです。
原因は、xibファイルでした。
いつまで経っても一向に使いやすくならないInterfaceBuilderを立ち上げすらせずに放置し、
コードで直接
UIView* view = [ [UIView alloc] initWithFrame:]のように部品を作る人はたぶん結構いると思いますが、
そういう人は、MainWindow.xibが真っ白なままになっていると思います。
iOS5では、どういうわけかMainWindow.xibのビューが最前面に張り付いてしまい、
下のビューを隠してしまいます。
Windowの初期化コードをまだあまりよくチェックしてないので、
そのあたりのコードでよろしくない書き方をしてしまっていたのかもしれませんが、
まあともかく、この現象が起きたらxibファイルを疑ってみてください。
「iOS5 真っ白」で検索すると、そこそこヒットするので、
同じ嵌り方をしたアプリがちらほらあるようです。