A Day In The Life

とあるプログラマの備忘録

Obejctive-C 3分クッキング

この記事では C++Java、C♯、Rubyなどのオブジェクト指向言語の使用経験がある方のために Objective-C の特徴がさくっと簡単にわかるようにまとめてみました。

Objective-C ってどんな言語?

オブジェクト指向言語は大きく「クラスベース動的型付け」「クラスベース静的型付け」「プロトタイプベース*1動的型付け」の3つにわけることが出来ます。Objective-C は「クラスベース動的型付け」に分類されるオブジェクト指向言語です*2Objective-C を他のオブジェクト指向言語と比較して分類してみると以下の図のようになります。
いろいろなオブジェクト指向言語
クラスベース動的型付けオブジェクト指向言語の元祖といえるのが Smalltalk です。Objective-CC言語をベースに Smalltalk 型のオブジェクト指向機能を持たせた言語です。

必要な前提知識

Objective-CC言語がベースになっていますのでC言語の基本構文(if 文や for 文、変数宣言など)を理解している必要があります。またこの記事ではオブジェクト指向の概念や用語の説明を省いているので、オブジェクト指向についてある程度理解していることが前提となります。

ファイル構成

Objective-C のプログラムは C++ と同じようにヘッダファイルと実装ファイルに分かれています。

  • .h ヘッダファイル
  • .m 実装ファイル

基本構文

  • 制御文(if,for,switch,enum等)はC言語と同じ
  • コメントはC++,C♯,Javaと同じ

クラスやライブラリのインポート

  • C言語の include を拡張した import を使う

    #import <Foundation/NSObject.h>
    #import "MyViewController.h"
    

    特定のフレームワークを import する場合は「<」と「>」で囲み、ヘッダファイルを import する場合は「"」で囲む。

  • 特定のクラスだけを使用する場合は @class を使うことができる。

    @class NSString
    

ルートクラス

  • Java の Object クラスと同じように Objective-C にもルートクラスが存在する。
  • ルートクラスは明示的に継承すること(自動で継承されることはない)。
  • NSObject(NS は NEXTSTEP の略と言われている)
  • オブジェクトの生成で使用する alloc メソッドは NSObject クラスのクラスメソッド
  • NSObject クラスを継承しないとオブジェクト生成ができない。

クラスの定義

  • .hファイルに書くこと
  • 定義方法

    @interface クラス名 : 親クラス名
    
    プロパティ宣言
    
    メソッド宣言 
    
    @end
    

  • インスタンス変数は実装ファイルに宣言する(後述)
  • クラス変数という概念がないので注意

メソッド宣言

基本形

引数が1つある場合

  • 引数の前に「:」を付ける

    - (戻り値の型)メソッド名:(引数の型)引数;
    

引数が2つ以上ある場合

  • 2つ目以降の引数にはラベルをつける*3
  • C++Java,C♯にない特徴なので注意

    - (戻り値の型) メソッド名:(引数の型)引数1 ラベル:(引数の型)引数2 ラベル:(引数の型)引数3;
    

  • 以下の例の場合 withEvents: がラベルになる。

    - (void)touchMove:(Touch *)touch withEvents:(Event *)event;
    

コンストラクタ

メソッドのスコープ
  • ヘッダファイルに宣言されたメソッドはすべて public
  • ヘッダファイルに宣言を書かず実装ファイルにだけメソッド実装を書くことで擬似的にローカルメソッドようにすることができる。

プロパティ

  • インスタンス変数を外部に公開するための仕組み
  • @property を使ってプロパティを宣言する

    @interface Hoge : NSObject
    @property (strong, nonatomic) NSString *value;
    @end
    

  • インスタンス変数 + アクセサメソッド(getter, setter)を自動生成してくれる。インスタンス変数の名前はプロパティ名に接頭辞として「_」をつけたものが自動生成される
  • @property に指定する項目の説明

    • オブジェクトを強い参照で保持する場合は strong、弱い参照で保持する場合は weak を指定する。デフォルトは strong
    • プロパティをスレッドセープにする場合は atomic、スレッドセーフにしない場合は nonatomic を指定する。デフォルトは atomic
    • プロパティを読み取り専用にする(setter メソッドを生成しない)場合は readonly を指定する

クラス定義の例

  • プロパティとメソッドの宣言を含むクラス定義の例

    @interface Point : NSObject
    // プロパティ宣言
    @property (nonatmic) int value;
    // クラスメソッド宣言
    + (id)pointWithX:(int)x withY(int)y; 
    // コンストラクタ宣言
    - (id)initWithX:(int)x withY(int)y; 
    // メソッド宣言
    - (void)touchMove:(Touch *)touch withEvents:(Event *)event;
    @end
    

クラス型変数の宣言

  • クラス名を明示的に指定する

    // ポインタ変数として宣言する
    NSString *hoge;
    NSArray *fuga;
    

  • クラス型変数を使う代わりにクラス名を明示しない id 型を使って変数の宣言をすることが出来る

    id hoge;
    id fuga;
    

  • id 型はオブジェクトへのポインタ。以下のように宣言されている

    typedef struct objc_object *id;
    

文字列

  • NSStringクラスのオブジェクト
  • @""を使う

    NSString *hoge = @"Hello World!";
    

メソッドの呼び方

  • 引数なしの場合

    [オブジェクト メソッド]
    

  • 引数ありの場合

    [オブジェクト メソッド: 引数]
    

  • メソッドの入れ子呼び出し

    [オブジェクトA メソッド: [オブジェクトB メソッド]]
    

  • メソッドチェイン

    [[オブジェクト メソッド] メソッド]
    

プロパティへのアクセス

  • 「.」演算子を使う

    オブジェクト.プロパティ
    

self

  • インスタンスそのものを表すための変数
  • 暗黙的に宣言されている
  • 自身のメソッドインスタンス変数、プロパティにアクセスするときは self を使う。
  • Java や C♯の this とほぼ同じ。
  • 自己メソッド呼び出しの場合

    [self メソッド]
    

  • 自己インスタンス変数へのアクセス

    self->インスタンス変数
    

  • 自己プロパティへのアクセス

    self.プロパティ
    

super

  • 親クラスで定義されているメソッドインスタンス変数、プロパティにアクセスするときは super を使う。
  • Java の super ,C♯の base とほぼ同じ。
  • 使い方は self と同じ

オブジェクト(インスタンス)の生成

クラスの実装

  • .mファイルに書く

    @implementation クラス名 {
      // インスタンス変数の宣言
    }
    // メソッドの実装をここに書く
    @end
    

メソッドの実装

基本形

  • 「{」と「}」の間に実装を書く

    @implementation Hoge
    - (戻り値の型)メソッド名:(引数の型)引数1 ラベル:(引数の型)引数2
    { 
      …処理
      return 戻り値;
    } 
    @end
    

  • 実装例

    @implementation Hoge
    - (NSString *)fugaWithString:(NSString *)str withInt:(int)num
    { 
      …処理
      return @"hogehoge";
    } 
    @end
    :
    // メソッド呼び出しイメージ
    [hoge fugaWithString:@"ABC" withInt:1000];
    :
    

コンストラクタの実装

  • コンストラクタでは親クラスのコンストラクタを呼び出すこと
  • 戻り値は self
  • 実装例

    @implementation Hoge
    - (id)init
    {
      self = [super init]; // 親クラスのコンストラクタ呼び出し
      if (self) {
        // オブジェクトの初期化コードをここに書く
      }
      return self;
    }
    @end
    

デストラクタの実装

  • オブジェクトが破棄される(参照カウントが0になった)時に呼ばれるメソッド
  • プロパティやインスタンス変数の解放処理などを行う
  • NSObject クラスで定義されている
  • 実装例

    @implementation Hoge
    - (void)dealloc
    {
      /*
       * 解放処理
       */
      [super dealloc]; // ARC 環境では呼び出し不要
    }
    @end
    

インスタンス変数の宣言

  • インスタンス変数はヘッダファイルに宣言することも出来るが最近は実装ファイルに定義するのが主流。
  • インスタンス変数には接頭辞として「_」をつけるのが慣例になっている
  • 実装例

    @implementation クラス名 {
      // インスタンス変数の宣言
      NSString *_hoge;
    }
    @end
    

  • インスタンス変数の初期化はコンストラクタで行う

    @implementation クラス名 {
      NSString *_hoge; 
      // 下記はコンパイルエラーになる
      // NSString *_hoge = @"test";
    }
    - (id)init
    {
      self = [super init];
      if (self) {
        // インスタンス変数の初期化
        _hoge = @"test"; 
      }
      return self;
    }
    @end
    

  • インスタンス変数のスコープ は @public,@protected,@private の3種類。無指定は protected。public は原則使用禁止。代わりにプロパティを使うこと

    @implementation クラス名 { 
        int x; //protected 
      @public 
        int a; //public 原則使用禁止。プロパティを使うこと
        int b; //public 原則使用禁止。プロパティを使うこと
      @protected 
        int c; //protected 
        int d; //protected
      @private
        int e; //private
        int f; //private
    } 
    @end
    

プロトコル

  • Java のインターフェースに相当するもの
  • @optinal以下のメソッドは任意実装
  • プロトコルの宣言

    @protocol プロトコル名 
    メソッド宣言1
    @optional
    メソッド宣言2
    メソッド宣言3
    @end
    

  • プロトコルの導入

    @interface クラス名:親クラス名 <プロトコル名1,プロトコル名2...>
    @end
    

  • 実装例

    @protocol Comparable 
    - (int)compareTo:(id)o; // 実装必須メソッド
    @optional
    - (void)hoge; // 実装任意メソッド
    @end 
    
    @interface Hoge:NSObject <Comparable> 
    //ここでプロトコルのメソッドを宣言する必要はない 
    @end 
    
    @implementation Hoge 
    - (int)compareTo:(id)o
    { 
      …処理 
    } 
    @end
    

例外

実行時例外

  • C♯やC++,Javaと同じ
  • 例外のキャッチ

    - (void)hoge
    {
      @try {
        : 処理
      } @catch (NSExceptio *e) {
        : 処理
      } @finally {
        : 処理
      }
    }
    

  • 例外のスロー

    - (void)hoge
    {
      : 処理
      if (エラー) {
        NSException *exception = [NSException exceptionWithName:@"Exception"
                                                         reason:@"Reason"
                                                       userInfo:nil];
        @throw exception;
      }
      : 処理
    }
    

検査例外の代わりに使う NSError

  • Java の検査例外のような仕組みは存在しない(C♯やC++と同じく実行事例外のみサポート)
  • NSError オブジェクトの参照を渡す方法で Java の検査例外と同じようなことができる
  • 使用例

    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSError *error = nil;
    // NSError オブジェクトの参照を渡す
    [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
    // エラーがあれば
    if(error){
      NSLog(@"audioSession: %@ %d %@", [error domain], [error code], [[error userInfo] description]);
    }
    


  • 「NSError**」のようにポインタ2つでオブジェクトの参照渡しを宣言する。エラーが発生したらNSError オブジェクトにエラーの内容を書き込む。

    - (BOOL)setCategory:(NSString*)theCategory error:(NSError**)outError
    {
      : 処理
      if (エラー発生) {
        *error = [NSError errorWithDomain:@"ドメイン" code:-1 userInfo:nil];
        return NO;
      }
      return YES;
    }
    

    上記のパターンはObjective-Cのいろんなフレームワークで使われている。


カテゴリ

  • クラスに動的にメソッドを追加することができる
  • Javaにはない概念
  • C♯のパーシャルクラスに近い
  • Rubyのクラス拡張と同じようなことができる。既存クラスの拡張で使うことが多い。
  • 実装例(NSStringクラスを独自拡張)

    // Hogeはカテゴリ名
    @interface NSString (Hoge)
    - (BOOL)isNilOrEmpty;
    @end
    
    @implementation NSString (Hoge)
    - (BOOL)isNilOrEmpty
    {
      : 処理
    }
    @end
    

  • 詳細はカテゴリ - 動的なメソッドの追加によるクラスの拡張を見ること

セレクタ

  • C言語の関数ポインタやC♯のデリゲートのような仕組み
  • Javaにはない概念
  • @selectorディレクティブを使う
  • イベントドリブンなプログラムをするときに頻繁に使う
  • 実装例(UIButtonのオブジェクトにボタン押下時の処理をセレクタで指定する)

    - (void)viewDidLoad
    {
      :
      UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
      [button addTarget:self
                 action:@selector(buttonClick)
       forControlEvents:UIControlEventTouchDown];
      :
    }
    - (void)buttonClick
    {
      // ボタンが押された時の処理
    }
    

  • 詳細はセレクタを見ること

コレクション

  • 配列を扱うための NSArray クラス、辞書データ(Java の Map)を扱うための NSDictionary クラス、セットを扱うための NSSet クラスがある
  • NSArray の例

    // 文字列配列
    NSArray *array = @[ @"Apple", @"Banana", @"Orange" ];
    // 要素へのアクセス
    NSString *apple = array[0];
    NSString *banana = array[1];
    NSString *orange = array[2];
    

  • NSDictionary の例

    NSDictionary *dict = @{ @"Key1" : @"Value1", @"Key2" : @"Value2", @"Key3" : @"Value3" };
    // 要素へのアクセス
    NSString *value1 = dict[@"Key1"];
    NSString *value2 = dict[@"Key2"];
    NSString *value3 = dict[@"Key3"];
    

  • NSSet の例

    NSSet *set = [NSSet setWithObjects:@"Apple", @"Banana", @"Orange", nil];
    // 要素へのアクセス
    NSString *value = [set anyObject];
    

  • コレクションのネスト

    NSArray *array = @[
      @{ @"Key1-1" : @"Value1-1", @"Key1-2" : @"Value1-2" },
      @{ @"Key2-1" : @"Value2-1", @"Key2-2" : @"Value2-2" },
      @{ @"Key3-1" : @"Value3-1", @"Key3-2" : @"Value3-2" }
    ];
    // 要素へのアクセス
    NSString *value11 = array[0][@"Key1-1"];
    NSString *value22 = array[1][@"Key2-2"];
    

  • 上記の NSArray, NSDictionary, NSSet クラスは不変オブジェクト(値の追加削除が出来ないオブジェクト)しか作成できないので可変オブジェクトを作成する時はそれぞれ NSMutableArray クラス、NSMutableDictionary クラス、NSMutableSet クラスを使用すること
  • NSMutableArray の例

    // 不変オブジェクト
    NSArray *array = @[ @"Apple", @"Banana", @"Orange" ];
    // 可変オブジェクト
    NSMutableArray *mutableArray = [array mutableCopy];
    

  • NSMutableDictionary の例

    NSMutableDictionary *dict = @{ @"Key1" : @"Value1", @"Key2" : @"Value2", @"Key3" : @"Value3" };
    // 可変オブジェクト
    NSMutableDictionary *mutableDict = [dict mutableCopy];
    

  • NSMutableSet の例

    NSMutableSet *mutableSet = [NSMutableSet setWithObjects:@"Apple", @"Banana", @"Orange", nil];
    

数値オブジェクト

  • NSNumber クラスを使って int や long、floate、double、BOOL 型などの数値をオブジェクトとして扱うことができる
  • 数値の前に「@」を付加することでオブジェクトとして扱える

    // 整数
    NSNumber *num = @9;           // [NSNumber numberWithInt:9] と同じ
    NSNumber *numUnsigned = @9U;  // [NSNumber numberWithUnsignedInt:9U]
    NSNumber *numLong = @9L;      // [NSNumber numberWithLong:9L]
    NSNumber *numLongLong = @9LL; // [NSNumber numberWithLongLong:9LL]
    // 小数
    NSNumber *numFloat = @9.123456789F;  // [NSNumber numberWithFloat:9.123456789F]
    NSNumber *numDouble = @9.1234567891; // [NSNumber numberWithDouble:3.1415926535]
    // BOOL型
    NSNumber *numYes = @YES;      // [NSNumber numberWithBool:YES]
    NSNumber *numNo = @NO;        // [NSNumber numberWithBool:NO]
    // 計算式
    NSNumber *answer = @(100 + 300);
    int a = 100;
    int b = 300;
    NSNumber *answer = @(a + b);
    // メソッド呼び出し
    NSString *str = [@9 stringValue];
    

  • コレクション要素に数値オブジェクトを設定することも可能

    NSArray *array = @[ @100, @200, @300 ];
    NSDictionary *dict = @{ @"Key1" : @3, @"Key2" : @5, @"Key3" : @8 };
    

名前空間は存在しない

  • Java で言うところのパッケージ、C♯で言うところの名前空間という概念は存在しない。
  • ライブラリとして外部に公開するようなクラスには接頭辞をつけるのが慣例
    • 文字列クラス:NSString
    • 配列クラス:NSArray
    • 位置情報管理クラス:CLLocationManager

プログラムのエントリーポイント

  • C言語と同じ main 関数からプログラムの実行が開始される
  • iOS の main 関数の例

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

その他

メモリ管理

改訂履歴

2012年9月19日 記事内容を全面的に見直し修正。Modan Objective-C Syntax に対応

もっと詳しく知りたい人はこちらの本がおススメです。

詳解 Objective-C 2.0 第3版
荻原 剛志

*1:クラスを持たないオブジェクト指向言語。新しいオブジェクトをクラスからではなく、既存のオブジェクトのクローンから作成します。Smalltalk をベースにクラスの複雑性を排除した Self 言語が有名です

*2:クラス型の変数を宣言できるので静的型付けと思われるかもしれませんが、プログラム実行時の動きは動的型付け言語と同じ特徴を持っています

*3:この特徴は Smalltalk 型のオブジェクト指向機能が由来になっています