Cocoaの素@ITのメモ
2011/07/19(火) 18:07 Objective-C親記事へこのエントリーをはてなブックマークに追加

Objective-Cのクラス定義を理解しよう - @IT

ObjCでコンパイラディレクティブを使ってから、それがどういう意味なのかっていう展開より、
逆にgetter/setterはこういう感じで作れて、@propertyと@synthesizeを使うと簡単にできるよって順番でやった方が分かりやすい気がする。まあ人によりますね。
そういう意味で、このコラムは用語の意味が分かりやすい。
コンパイラディレクティブを使って宣言と実装部を始める。
/* クラスの宣言部 */
@interface MyClass : NSObject {
}
@end

/* クラスの実装部 */
@implementation MyClass : NSObject
@end 
@interface MyClass : NSObject {
    @private
        int myInt1;

    @protected
        int myInt2;

    @public
        int myInt3; 
}
@end
クラスのメンバ変数は、クラスのインスタンス(実体)ごとに違った値を保持できる「インスタンス変数」と、クラスの型ごとに一意の値となる「静的変数(クラス変数)」の2種類があって、Objective-Cのメンバ変数は基本的にすべてインスタンス変数になる。(例外もある)
インスタンス変数のアクセス権の範囲は「private」「protected」「public」があって、
  • private その変数が宣言されてるクラス内部からのみ
  • protected private+そのクラスのサブクラスからも(無指定の時のデフォルト値)
  • public どこからでも

関数宣言と呼び出しの比較

- (void)myMethod1;
// 戻り値のない引数
[test myMethod1];
- (NSString *)myMethod2;
// 戻り値のあるメソッド
NSString *res = [test myMethod2];// 戻り値を合わせる
- (void)myMethod3:(NSString *)argStr;
// 引数があるメソッド
[test myMethod3:@"test"];// :引数 で渡す
||<<
>|objc|
- (NSString *)myMethod4:(NSString *)argStr;
// 戻り値 + 引数
NSString *res = [test myMethod4:@"test"];
// 二つ目の引数は ラベル:(型)引数名
- (NSString *)myMethod5:(NSString *)argStr
                    myInt:(int)argInt;
//引数が複数ある
NSString *res = [test myMethod5:@"test" myInt:5];// 二つ目からはラベル:値を指定
特に最後のがややこしい感じになっている。
この例は特に分かりやすい感じ。
- (void)setWidth:(int)argWidth
        height:(int)argHeight
        depth:(int)argDepth;

↓メソッドの実行例
[test setWidth:5 height:6 depth:7]; 

インスタンスメソッドとクラスメソッド

メソッド名の先頭には-(マイナス)の記号をつける。
インスタンスメソッド
先頭に-をつけたメソッドは、クラスのインスタンスがあって初めて実行できるメソッド
クラスメソッド
先頭に+をつけたメソッドは、クラスの型から直接実行する事が可能なメソッド
クラスメソッドはallocなど特殊な用途に使われる事が多い。

プロパティとアクセサ

インスタンス変数の有効範囲はデフォルトでprotectedで、そのような(publicでない)インスタンス変数はメソッドを通してアクセスするのが普通です。
こういうインスタンス変数をプロパティといい、またプロパティにアクセスするためのメソッドをアクセサ(getter/setterの事)といいます。

ObjCのアクセサはプロパティ名が「myProp」だとすると、セッターメソッドは「setMyProp:」、ゲッターメソッドは「myProp」となります。
(getterにgetをつけないのが特徴)

/* クラスの宣言部 */
@interface MyClass : NSObject {
    // protectedなインスタンス変数
    int myProp;
}

// アクセサの宣言 getter/setter
- (int)myProp;
- (void)setMyProp:(int)value;

@end

/* クラスの実装部 */
@implementation MyClass

// アクセサの実装 getter/setter
- (int)myProp {
    return myProp;
}
- (void)setMyProp:(int)value {
    myProp = value;
}
@end
このようにインスタンスメソッドで、アクセサを実装する。(アクセサメソッド)
インスタンスメソッドなので、インスタンス化してからアクセサを通じてアクセスします。
test.myProp = 5;     // [test setMyProp:5];    と同じ
int i = test.myProp; // int i = [test myProp]; と同じ
ドット演算子を使ってアクセサメソッドを実行するのはObjC2.0から導入されたもの。
ドット演算子とメソッド呼び出しの違いについては注意点があって、インスタンス変数の型に注意する必要がある。
これはインスタンス変数testがアクセサメソッドを実装する型(@interface MyClass部分のクラスのこと)と一致してるので問題ない。
MyClass *test = [[MyClass alloc] init];
test.myProp = 5;
int i = test.myProp;
だけど、testをid型などで定義した場合は、アクセサメソッドの型に合わせるようにキャストしないといけない。
id test = [[MyClass alloc] init];// id型は何でもいける感じなので
((MyClass *)test).myProp = 5;
int i = ((MyClass *)test).myProp;
詳細は以下を読むといい。

アクセサメソッドの自動生成

先ほどのアクセサメソッドの実装などは手動でもできるけど、いろいろと面倒なので、Objective-C2.0からは@propertyというアクセサの自動生成するコンパイラディレクティブが導入されている。
@propertyと@synthesizeのコンパイラディレクティブを使う。
/* クラスの宣言部 */
@interface MyClass : NSObject {
    // インスタンス変数
    int myInt;
    NSString *myStr1, *myStr2, *myStr3;
}

// プロパティの宣言
@property int myInt;
@property (assign) NSString *myStr1, *myStr2;
@property (retain, readonly) NSString *myStr3;

@end

/* クラスの実装部 */
@implementation MyClass

// アクセサメソッドの生成
@synthesize myInt, myStr1, myStr2, myStr3;

@end
プロパティの宣言
@propertyのコンパイラディレクティブを使って、インスタンス変数に対応するプロパティを宣言する。
@property (options)にoptionを指定できる。
アクセサメソッドの実装
@synthesizeというコンパイラディレクティブを使って、@propertyで宣言したプロパティへのアクセサメソッドを生成できる。
を読むと、どんな感じでgetter/setterが生成されてるか確認。
また、アクセサを強制する事はメモリ管理的にも意味があったりするので、@property と @synthesize でアクセサを実装するのはよくあるやり方らしい。
クラスのインスタンス生成と初期化
クラスのインスタンス化と初期化について。
allocメソッド(クラスメソッド,NSObjectに定義されている)で、クラスをインスタンス化できる(メモリ上に割り当てを行う)
次に、allocメソッドの戻り値である生成されたインスタンスに対して初期化処理を行う。これがinitメソッドでこれもNSObjectに
initメソッド(インスタンスメソッド)として実装されていて、戻り値は初期化が済んだクラスインスタンスになる。
この初期化処理に独自処理を加えたい場合は、initメソッドをオーバーライドする。通常initは引数を取らないので、引数が必要なinitが欲しい場合はinitWithxxxという感じで別に胃に社来座を定義する。
#import <Foundation/NSObject.h>
#import <stdio.h>

/* クラスの宣言部 */
@interface MyClass : NSObject {
    // インスタンス変数
    int myVar;
}

@property int myVar;

// イニシャライザの宣言
- (id)init;                     // 引数なし
- (id)initWithMyVar:(int)value; // 引数あり

@end

/* クラスの実装部 */
@implementation MyClass

@synthesize myVar;

// 引数なしのイニシャライザの実装
// 元々はNSObjectにあるので、オーバライドしてる
- (id)init {
    self = [super init];// まず親クラスのinitメソッド実行する
    if (self != nil) {// [super init]が失敗ならnilが返り値
        self.myVar = 3;
    }
    return self;
}

// 引数ありのイニシャライザの実装
- (id)initWithMyVar:(int)value {
    if ((self = [super init])) {// こういう書き方もよく見る
        self.myVar = value;
    }
    return self;
}

@end

/* インスタンス生成の実行例 */
int main(void) {

    // 引数なしでインスタンスを生成
    MyClass *test1 = [[MyClass alloc] init];
    printf("%d \n", test1.myVar);

    // 引数を渡してインスタンスを生成
    MyClass *test2 = [[MyClass alloc] initWithMyVar:5];
    printf("%d \n", test2.myVar);

    return 0;
} 

変数のデータ型や文字列の扱いを理解しよう - @IT

intとかdoubleとかcharとかC言語と同じものも使える。
charはNSStringが使えるのであんまり使う事ないかもしれない。
真偽値を表すデータ型として、BOOL型にYES,NOというキーワードが定義されてる。
BOOL isFoo = YES; // 1
BOOL isBar = NO;// 0
クラスオブジェクトを表すデータ型
クラスオブジェクトを表す変数の宣言は常にポインタとして宣言する。もう一つはid型を利用する方法
MyClass *myClass;
// id型は全てのクラスオブジェクトに使える
id myClass;
(myClassはMyClassを示している。)
id myClass = nil;// 初期化

ラッパークラス

Foundationのクラス群にはオブジェクト型(id型)で受けるように作られたメソッドも沢山あある。
なので、int型などで扱っていた変数をオブジェクト型として引数にするなどもあり得ます。そのときにラッパークラスを使うと値をオブジェクト化できる。
数値の場合 - NSNumber
#import <Foundation/NSObject.h>
#import <stdio.h>

int main(void) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  //(1)ラッパーオブジェクトの生成
  // int -> NSNumberのオブジェクト型になってる
  NSNumber *wrappedInt    = [NSNumber numberWithInt:123];
  NSNumber *wrappedDouble = [NSNumber numberWithDouble:0.333];

  //(2)値の取得
  printf("wrappedInt intValue : %d\n", [wrappedInt intValue]);
  printf("wrappedInt doubleValue : %lf\n", [wrappedInt doubleValue]);
  printf("wrappedDouble doubleValue : %lf\n", [wrappedDouble doubleValue]);
  printf("wrappedDouble stringValue : %s\n", [[wrappedDouble stringValue] UTF8String]);

  [pool drain];
  return 0;
}
文字列の場合 - NSString
C言語だと文字列の扱いはとっても面倒なので、NSStringは同じラッパクラスでもよく使う。
NSStringはいろいろ便利なメソッドがある。
比較とかはC言語と同じように==ではなくメソッド経由で行う。
"NSStringクラスの場合、そのインスタンスが保持する値(文字列)を直接変更することができません。先ほどのコード例で文字列の操作にかかわるメソッドをいくつか紹介しましたが、これらはすべて、インスタンスの値を直接変更するわけではなく、処理結果を新たな文字列オブジェクトとして返しています"

「NSXxxx」と「NSMutableXxxx」

ObjCには「NSXxxx」と「NSMutableXxxx」という感じのクラスがあって、「NSXxxx」の場合は基本的インスタンスの値を変更するわけじゃなくて、新しいオブジェクトして返すような作りになっています。
「NSMutableXxxx」はそのインスタンスそのものを変更するクラスを示している。
つまり、変更不可のクラスと変更可能なクラスの違い。

コードをもっとオブジェクティブに - @IT

あるクラスを継承して、同じ機能の振る舞いを変えたい時はオーバーライドする。
NSObject
ルートクラス(jsのObjsctと同じ)
継承元
スーパークラス、親クラス
継承先
サブクラス、子クラス
#import <Foundation/Foundation.h>
#import <stdio.h>

// 父親クラス
@interface Father : NSObject {
    NSString *job;
}
@property (retain) NSString *job;
- (void)work;
@end

@implementation Father
@synthesize job;
- (void)work {
    printf("%s\n", [job UTF8String]);
}
@end

// 長男クラス - Fatherクラスを継承
@interface EldestSon : Father {
}
@end

@implementation EldestSon
- (void)work {
    printf("%s and smoking\n", [job UTF8String]);
}
@end

// 次男クラス - Fatherクラスを継承
@interface SecondSon : Father {
}
- (void)cook;
@end

@implementation SecondSon
- (void)cook {
    printf("cooking\n");
}
@end


int main(void) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        Father *father = [[Father alloc] init];
        [father setJob:@"accounting"];
        [father work];

        EldestSon *eldestSon = [[EldestSon alloc] init];
        [eldestSon setJob:@"programming"];
        [eldestSon work];

        SecondSon *secondSon = [[SecondSon alloc] init];
        [secondSon setJob:@"designing"];
        [secondSon work];
        [secondSon cook];

    [pool drain];
    return 0;
}
プロトコルは各クラスにあるメソッドを実装するなどの約束事を決められる。
@protocol というコンパイラディレクティブを使って定義する。

@protocol Zukei                         // 図形プロトコル
- (void) draw;                          // 「draw」メソッドは必須
@end

@interface Maru : NSObject <Zukei>      // 採用
@end
@implementation Maru
- (void) draw { printf("○\n"); }       // 実装
@end

@interface Sankaku : NSObject <Zukei>   // 採用
@end
@implementation Sankaku
- (void) draw { printf("△\n"); }       // 実装
@end

@interface Sikaku : NSObject <Zukei>    // 採用
@end
@implementation Sikaku                  // 実装していない
@end                                    // ので警告が出る
プロトコルで宣言するメソッドは@requiredと@optionalがある。@requiredがデフォルト値になってる。
@protocol Zukei
    @required    // ←これはデフォルトなので省略できます
    - (void) draw;
    - (void) paint;

    @optional
    - (void) move;
    - (void) resize;
@end

プロトコル同士の継承と多重採用

プロトコルは一度に複数採用する事でもできる。
@interface TestCls : NSObject <ProtocolA, ProtocolB>
プロトコル同士も継承ができるため、複数の約束事を引き継いだプロトコルを定義できる。
@protocol ProtocolA
- (void) testMethod1;
@end

@protocol ProtocolB
- (void) testMethod2;
@end

@protocol ProtocolC <ProtocolA, ProtocolB>
- (void) testMethod3:(int)arg;
@end
ObjCでは引数に合わせてメソッド名を変更するのが一般的。
xxxWithInt , xxxWithString などのような命名をする。

オブジェクトの判定

オブジェクトがどのクラスのインスタンスであるかを判定する。
#import <Foundation/Foundation.h>
#import <stdio.h>

@protocol ProtocolA
- (void) testMethod1;
@end

@interface MyBaseCls : NSObject
@end
@implementation MyBaseCls
@end

@interface TestCls : MyBaseCls <ProtocolA>
@end
@implementation TestCls
- (void) testMethod1 {}
- (void) testMethod2:(int)arg1 arg2:(int)arg2 { printf("testMethod2\n"); }
@end


int main(void) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    id obj = [[TestCls alloc] init];

    //(1)あるクラスのオブジェクトか
    // instanceOfみたいな
    BOOL bool1 = [obj isMemberOfClass:[TestCls class]];

    //(2)あるクラスのサブクラスか
    BOOL bool2 = [obj isKindOfClass:[MyBaseCls class]];

    //(3)あるプロトコルを採用しているか
    BOOL bool3 = [obj conformsToProtocol:@protocol(ProtocolA)];

    //(4)あるメソッドを実装しているか
    // SEL型であるメソッドを表現する
    BOOL bool4 = [obj respondsToSelector:@selector(testMethod2:arg2:)];
    BOOL bool5 = [TestCls instancesRespondToSelector:@selector(testMethod2:arg2:)];

    //(5)クラス名を取得する
    NSString *className = [obj className];

    NSLog(@"%d\n", bool1);
    NSLog(@"%d\n", bool2);
    NSLog(@"%d\n", bool3);
    NSLog(@"%d\n", bool4);
    NSLog(@"%d\n", bool5);
    NSLog(@"%@\n", className);

    [pool drain];
    return 0;
}
セレクタオブジェクトは@selector()やNSSelectorFromString(@"testMethod2:arg2:")から取得できる。

クラスクラスタ

1つのクラス定義に見えても、内部で状況に応じて複数のサブクラスが使い分けられている場合があります。Objective-Cのこのような仕組みをクラスクラスタといいます
NSStringとかその辺が実はそういうものになってる。
クラスクラスタは内部的にいくつかサブクラスを持っていたりしますが、そのときのサブクラスの事をコンクリートクラスという。
コンクリートクラスは普通は意識しないけど、クラス判定を直接するロジックを使うときは気を遣う必要がある。

クラスのカテゴライズ

テゴリの仕組みは、クラスの拡張方法としても利用できます。オブジェクト指向の世界で最も一般的なクラスの拡張方法は、すでに紹介した継承の仕組みですが、継承による拡張ではあくまでも新たなクラスを1つ作成することになります。

それではちょっと大げさ過ぎる、もっと手軽にメソッドをいくつか追加したい、という場合には、カテゴリの仕組みが便利
クラスをカテゴライズする仕組み。
「@interface クラス名 (カテゴリ名)」「@implementation クラス名 (カテゴリ名)」のように記述する事でカテゴリ指定ができる。
またカテゴリ毎にファイルを分割するときは
クラス名+カテゴリ名.拡張子 というファイル名でつけるのが一般的。
インターフェースは一つのファイルにカテゴリ毎の記述ができて、実装はカテゴリ毎にファイルを作れて明確に分けられる。

メモリ管理を理解する(前編) - @IT

ObjCはオブジェクトが必要か不必要なのかを数値のカウンタで判定している。これは参照カウンタという仕組み。
参照カウンタが0になると、そのオブジェクトは不必要となってメモリ領域が開放される。
alloc
参照カウンタを1にする
retain
参照カウンタを+1する
release
参照カウンタを-1する

クラスのインスタンス変数の所有権

setterメソッドでretainをする理由。
関数は関数側で責任をもってメモリーの解放をする。

クラスのインスタンス変数の解放

クラスのインスタンス変数の解放はクラス自身が責任を持つ。
なので、クラスが破棄されるときに一緒にインスタンス変数を破棄するようにする。
そのため、deallocメソッドをつかう。
deallocメソッドはNSObjectに定義されているメソッドで、参照カウンタが0になってクラスのインスタンスが解放される直前に実行される。
なので、初期化メソッドやセッターメソッドなどで確保したオブジェクトはここでまとめて解放する。
#import <Foundation/Foundation.h>

@interface MyClass : NSObject {
    NSObject *myObj;
}
- (NSObject *)myObj;
- (void)setMyObj:(NSObject *)argMyObj;
@end

@implementation MyClass
- (NSObject *)myObj {
    return myObj;
}
- (void)setMyObj:(NSObject *)argMyObj {
    if (myObj != argMyObj) {
        [myObj release];
        myObj = [argMyObj retain];
    }
}
// 自動的によびだされる。
- (void)dealloc {                               // (a)
    NSLog(@"dealloc called. release `myObj`."); // (b)
    [myObj release];                            // (c)
    [super dealloc];                            // (d)
}
@end

MyClassをインスタンス化してから、使った後にreleaseで解放したタイミングでdeallocが呼び出される。

メモリ管理を理解する(後編) - @IT

"プログラムのメモリ管理の責任範囲は、クラスとメソッドの単位で切り離して考えればよいことになります"
クラス単位でもつオブジェクトは、そのメンバであるインスタンス変数。
メソッド単位でもつオブジェクトは、そのメソッド内部で生成した(retainされた)オブジェクト。これはメソッドの中だけで使用済みになった時点で解放する必要がある。
メソッド内部で生成したオブジェクトをクラスで保持しないで(インスタンス変数にセットするわけではなく)、メソッドの返り値として返す場合、この場合のautoreleaseメソッドを使って自動的に解放されるように予約しておく。
メソッドの引数として受け取ったオブジェクトについてはメソッドに責任はない。

半自動のメモリ管理(autorelease)

プログラムの特定の範囲をまとめて自動的に解放する仕組みがある。
今まで、見てきたNSAutoreleasePoolがそれに当たる。
int main(void) {

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // (c)

    MyClass *myClass = [[MyClass alloc] init];

    NSObject *testObj1 = [myClass createObj1];  // (d)
    printf("%d\n", [testObj1 retainCount]);     //

    NSObject *testObj2 = [myClass createObj2];  // (e)
    printf("%d\n", [testObj2 retainCount]);     //

    [pool drain];                               // (f)

    printf("%d\n", [testObj1 retainCount]);     // (g)
    //printf("%d\n", [testObj2 retainCount]);   //

    return 0;
}

autorelease済みのオブジェクトを返すメソッド

autorelease済みのオブジェクトを返すメソッドにも命名規則がある。
initWith~
自分で返ってきたオブジェクトのreleaseをする
xxxWith~
autorelease済みのオブジェクトを返してくれる
initWithCString:encoding:は自分でreleaseをする
stringWithCString:encoding:はautorelease済みなので、オブジェクトの解放については意識しなくていい。

全自動のメモリ管理(ガベージコレクション)

ObjC2.0にはGCがあるよ。
iOSは使えないけど…

名前:  非公開コメント   

  • TB-URL  http://efcl.info/adiary/0126/tb/