読者です 読者をやめる 読者になる 読者になる

この春はゆるふわ愛されObjective-Cでキメちゃおう☆

iPhoneアプリで一儲けしたくなっちゃったからObjective-C頑張っちゃうZO☆


とかいうドン引きな冗談はさておき、なんとなく勉強し始めたObjective-Cについての徒然なるまとめです、真面目です。C++がそれなりに出来るObjective-C初学者*1が対象です。体系的に学習してないですし、言語仕様もほとんど見てなく、何が言いたいかというと要ツッコミ。

書き溜め書き足ししていたら、全体的に大変なことになってしまいましたが、ゆるふわなのでご愛嬌。実は、一番ためになるのはリンク部分かもしれません、Let [us goTo:最後の方]。

Objective-Cとかいう言語

Objective-Cは、C言語にぐちゃぐちゃプリプロセッサ的なものを追加した珍妙なクソ言語(失礼*2)です。

トラップの宝庫C++とは全く別物だと捉えるのが正解ですが、実はC++も混ぜられる*3とのことで、中二病的に言えば究極混沌言語=アルティメットカオスランゲー…いやもやは何も言うまい*4

ファイル

C++同様、インターフェースの宣言などを書くヘッダと実装でファイルが別れます。拡張子はヘッダが.h、実装が.mです。

コンパイラディレクティブ

クラスの宣言・実装など、あらゆる所で@で始まるものを使いますが、これはコンパイラディレクティブと呼ばれるもので、コンパイラへの命令です。イメージとしてはプリプロセッサ的な感じ(?)で、これを書くことで、コンパイラオブジェクト指向など上手いことしてくれるようになります。

種類はとてもたくさんあるようで…困ったのものです。

まず気持ち悪いのが、この型です。CとObjective-C、果てはランタイムによる型が混在します。

例えば、ブーリアン。C99由来(?)のbool型(true/false)、Objective-C公認(?)のBOOL型(YES/NO)、Mac OSのランタイムの(?)Boolean型(true/false)があります。基本的にはBOOL型を用いるのが正解のようです(参考)。

例えば、ヌル。C由来のNULL、Objective-C公認のnil(またはNil?)、NullオブジェクトのNSNullなど困ったものです*5参考1, 参考2)。

例えば、文字列。char*型の""、NSString*型の@""があるようです。アプリ開発ではNSStringを使うのが一般的だと思います。

プリプロセッサ

必殺技#importがあります。一言で言えば#include+#pragma onceのようです。PHPでいうならrequire_onceでしょうか。これは便利!

@import(コンパイラディレクティブ)にしなかった理由は、何かあるのかなぁと疑問に思ったりもしましたが、とりあえず放置*6

クラス

とりあえずクラスをマスターすれば、プログラムは組めそうなので、少し詳しくまとめます。

宣言(.h側)

@interface ClassName : ParentName {
	PrivateVariable *v1, *v2;
}

@property(nonatomic, retain, etc...) PrivateVariable *v1;

- (TypeOfReturnValue)instanceMethod: (type)firstArg label:(type)secondArg;
+ (TypeOfReturnValue)classMethod;

@end

特徴をまとめると、

  • @interfaceに始まり、@endに終わる。
  • 継承はC++のようにアクセス修飾子指定はなく、原則public継承で多重継承はできない。
  • @interface行の直後の中括弧内にプライベート変数を記述。
  • @propertyは必須ではないが、getter/setterを作る時に便利。
  • "-"から始まる行はインスタンスメソッド。"+"で始まる行がクラスメソッド。
    • メソッドは全てpublic。
    • メソッドの宣言の仕方は
      1. 括弧で戻り値の型 (TypeOfReturnValue)
      2. メソッド名とコロン instanceMethod:
        • 引数が無いならコロンではなくセミコロンで終了。
      3. 括弧で型と引数名 (type)firstArg
      4. 第二引数以降は特殊で、ラベルを指定できる。ラベル、コロン、括弧で型、引数名で指定label:(type)secondArg。引数の区切りはスペース。
        • ラベルは必須ではないが、指定するのがObjective-C流らしい。
        • ラベルの有無でも関数の型が変わる。

と言った感じです。

実装(.m側)

#import "ClassName.h"

@interface ClassName ()
- (void)privateInstanceMethod;
@end

@implementation ClassName

// getter/setterがこれで生成できる
@synthesize v1;

// 先ほどのヘッダで宣言したもの
- (TypeOfReturnValue)instanceMethod: (type)firstArg label:(type)secondArg {
  [super instanceMethod]
  [self privateInstanceMethod]
}
+ (TypeOfReturnValue)classMethod {}
// 上で宣言したプライベートメソッド
- (void)privateInstanceMethod {}

// コンストラクタ
- (id)init {}
// デストラクタ
- (void)dealloc() {}

@end

ポイントは以下の通りです。

  • 3行目の@interface〜@endの部分でプライベートなメソッドを定義できる。
    • enumなども可
    • 正式には「カテゴリ」と呼ぶらしい。
  • @propertyと@synthesizeのタッグでgetter/setterを宣言できる(参考)。
  • 宣言したメソッドは全部実装すべし。
    • メソッド呼び出しは[レシーバ 関数名 引数]の形式。
      • Objective-C的には、この「関数名」を「メッセージ」と呼ぶ模様。
      • オーバーライドする時、親クラスを呼ぶにはレシーバにsuperを指定[super 関数名 引数]
      • 自分を指すにはself。
  • 言語機能としてはコンストラクタ、デストラクタはないが、init/deallocがその役割を担う。
    • Objective-CにはRubyJavaのようにルートとなるクラスがある(NSObject)。init/deallocはNSObjectのものをオーバーライドする形になる。
    • idというのはJavaでいうvar的なもの。
    • コンストラクタに引数を渡したい時は指定イニシャライザを利用(参考1参考2。簡単にいえば、initでselfを返し、それをレシーバとして引数付き初期化メソッド*7を呼ぶ感じ)。

モダンな書き方

ここまで書いておいてなんですが、実は、上記の方法はレガシーな方法です。一番*8モダンな書き方は以下の通りです(詳しくは最後のリンク集を参照)。

ヘッダ
// {}は使わない!
@interface ClassName : ParentName
// public変数だけ@propertyで宣言(IBOutletなどもちゃんと書ける)
@property(...) IBOutlet UIButton *btn1, *btn2;
// publicなメソッドだけ書く
- (TypeOfReturnValue)instanceMethod: (type)firstArg label:(type)secondArg;
+ (TypeOfReturnValue)classMethod;
@end
実装
@interface ClassName ()
// カテゴリ内にprivateメソッドやprivateなenumを定義
- (void)hoge;
// ここで@propertyでprivateインスタンス変数を作るのが従来のベストプラクティス
//@property(...) NSString *huga;
@end

@implementation ClassName
// この{}内にprivateインスタンス変数を書くのが一番モダンな方法
{
  NSString *huga;
}
// publicなものは@synthesizeで、アンダースコア付き変数で=する(慣例)。
// 変数には self.btn1 か _btn1 でアクセス可能。
@synthesize btn1 = _btn1, btn2 = _btn2;
// 以降は以前と同様
@end

クラスの使い方

色々あります。

// 基本形
ClassName* instance1 = [[ClassName alloc] init]
[instance1 release]
// idで型指定省略&クラスメソッドnewの利用
id instance2 = [ClassName new]
[instance2 release]
// その他、色々
id instance3 = [[[ClassName alloc] init] initWith "arg"]
id instance4 = [[[ClassName alloc] init] autorelease]
id instance5 = [[[[ClassName alloc] init] retain] autorelease]
//...

以下、ポイントです。

  • 基本的には、[[ClassName alloc] init][instance release]を使えば良さそう。
    • たまに、メモリ解放はfreeメッセージといった記述を見るが、恐らくreleaseが正解*9
  • autoreleaseは便利だが、長く使われる時には使わないほうが良さそう。
    • Appleのガイドライン的にも、あまり勧められてないらしい
    • C++で無闇矢鱈にnewするな(適切にnewしろ)という話と同様だと思う。

その他、ポイント

Objective-Cでプログラミングする際に、厄介だけどよく使うのでどうしても外せない点を箇条書きしておきます。各詳細はリンク集にまとめてあります。

  • インスタンスの参照カウント関連
    • @propertyの属性 nonatomic, assign, retain, copy
    • alloc, release, autorelease, retainメソッド
    • これらはメモリ管理*10に密接に関係しているので極めて重要
  • クラスのプロパティとメソッド
    • プロパティreceiver.propertyでアクセス出来る
      • 代入の際にsetter/getterが暗黙的に呼ばれる点に注意
    • メソッドは[receiver method]で呼び出す。

リンク集

参考になるページと概要一覧です。下に行くほど最近見たものになっています。

ようやく少しObjective-Cが書けるような気がしてきました。やったね!

余談

いかにもなタイトルはホッテントリメーカーによるものです*12

*1:=私

*2:もっとも、歴史的に見れば、最初のリリースはPerlよりも古い言語のようなので、文句は言うまい…いや、やっぱりブツブツ

*3:Objective-C++

*4:C++/CLIとどっこいか…と思ったけど、C++/CLIはアンマネッジな部分のラッパーという大義がある。Objective-CApple公式言語というのが厄介…CarbonならC++でも開発できるようだけれど…。

*5:Objective-C++ならC++由来のnullも混ざるわけで…恐ろしや

*6:確かに#includeを踏襲しているものだから、コンパイラディレクティブというよりは、ただのコンパイラ依存のプリプロセッサという印象の方が強い気もしなくはない

*7:慣例としてinitWith

*8:2013年始時点では多分

*9:でも何か裏事情はありそう

*10:C++のnew、delete的な

*11:確かに、ヘッダはなるべく最小限の情報にしたいのに、普通の方法ではそうならず、微妙な気分ではあった

*12:123ブクマになるらしいです…ないな