Archive for the ‘Objective-C’ Category:

Objective-Cのブロック

Friday, March 30th, 2012

正確に言うと「ブロック」はObjective-CではなくAppleがC言語に標準として追加することを提案している機能です。ブロックとは何かというと、最近の言語だとだいたい含まれている「クロージャ」のことです。(だったら、「クロージャ」という名前にすればよいのだと思うのですが、なぜか「ブロック」という非常に一般的な言葉を選んでいます。さらに、C言語ではすでに{}で囲まれたところをブロックと呼んでいると思うのですが…)
参考にしたのは、以下のAppleの資料です。

ブロックの基本

C言語では昔から関数ポインタがあり、関数のポインタを別の関数の引数に渡したりすることができました。ただ、そのためには、関数を定義し、それを関数ポインタとして渡すという二段階の手順が必要でした。対して、RubyやC#のようにラムダ式やクロージャをインラインで定義できると、ちょっとした数行の処理を関数に渡したりすることができ便利です。

ブロックは、ブロックをコード内にインラインで定義する機能を持っています。そして、その定義したブロックを関数に渡すことができます。実際の例を以下に示します。

int calc(int a, int b, int(^f)(int, int)) {
  return f(a, b);
}

int main(int argc, const char * argv[])
{
  @autoreleasepool {
    int result1 = calc(1, 2, ^(int a, int b) {
      return a + b;
    });
  }
}

構文が今までみたことないようなものなので、少しわかりずらいですが、calc関数の呼び出しの第三引数である以下がブロックです。先頭の^がブロックであることを示しています。

^(int a, int b) {
  return a + b;
}

足し算をするブロックを定義し、それをすぐにcalc関数に渡しています。今までのC言語の関数ポインタでは、add_funcといった名前の関数を定義し、その関数ポインタをcalc関数に渡すという処理が必要でしたが、ブロックでは定義と引数として渡す処理が一緒にできます。

多くの場合は、インラインでブロックを書くことになりますが、関数ポインタのように、ブロックの参照(ブロック参照)を取得し、それを関数の引数に渡すようにすることもできます。

int calc(int a, int b, int(^f)(int, int)) {
  return f(a, b);
}

int main(int argc, const char * argv[])
{
  @autoreleasepool {
    int(^add_func)(int, int) = ^(int a, int b) {
      return a + b;
    };
    int result1 = calc(1, 2, add_func);
  }
}

main関数内では、最初にadd_funcというブロック参照を定義しています。(非常にわかりづらいですが、これでブロックを格納する変数を定義しています)

int(^add_func)(int, int);

これで、int型の2つの引数を受け取り、intを戻すブロック参照を定義したこととなります。そして、単純にブロックをこのブロック参照に代入し、そのブロック参照を別の関数の引数に渡すことができます。

注意として、ブロックの定義”int(^f)(int,int)”では、最初のintで戻り値の型を明示していますが、ブロックでは”^(int, int)”のように戻り値の型は指定しません。コンパイラがブロック定義より正しい戻り値の型を推測します

ブロックを用いて、同じ関数に違う処理をさせる例を示します。

int foreach(int data1[], int data2[], int n, int(^f)(int, int)) {
    int result = 0;
    for(int i = 0; i < n; ++i) {
        result += f(data1[i], data2[i]);
    }
    return result;
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        int data1[] = {1, 2, 3, 4, 5};
        int data2[] = {2, 4, 6, 8, 10};
        // add
        int result1 = foreach(data1, data2, 5, ^(int a, int b){
            return a + b;
        });
        // sub
        int result2 = foreach(data1, data2, 5, ^(int a, int b){
            return a - b;
        });
    }
}

ブロックと変数

ブロックでは、他の言語のクロージャやラムダ式と同様に、ブロックが定義された場所で可視な変数を使うことができます(これを変数のキャプチャという)。

int foreach(int data[], int n, int(^f)(int)) {
    int result = 0;
    for(int i = 0; i < n; ++i) {
        result += f(data[i]);
    }
    return result;
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        int data[] = {1, 2, 3, 4, 5};
        int value = 2;
        int result = foreach2(data, 5, ^(int a){
            return a * value;
        });
    }
}

ここで、main関数内で定義されたvalueをブロック内でキャプチャしています。ただし、value変数は読み取り専用としてしかアクセスできません。書き込みたい場合は、__blockという修飾子を付ける必要があります。

void foreach(int data[], int n, void(^f)(int)) {
    for(int i = 0; i < n; ++i) {
        f(data[i]);
    }
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        int data[] = {1, 2, 3, 4, 5};
        int value = 2;
        __block int result = 0;
        foreach(data, 5, ^(int a){
            result += a * value;
        });
    }
}

この例では、resultを__blockを付けて宣言しています。

ブロックとメモリ管理

インラインでブロックを定義し、その場で呼び出しているだけであれば、あまりメモリ管理については気にする必要がありません。ですが、ブロックが活躍するのはコールバックや非同期処理です。これらの場合、ブロックの呼び出しは、定義した所のずっと後という可能性があります。するとキャプチャした変数が、もう解放されて、なくなっている可能性が出てきます。

ブロックに引数として渡された変数やグローバル変数は、当然ながら、ずっとブロック内から参照可能です。また、キャプチャされた変数も問題なく参照できます。

ブロック内で参照されているObjective-Cのオブジェクトは、自動的に参照カウンタ(retain counter)が+1されます。ですので、解放されることはありません。

C++のオブジェクトの場合、__block修飾子付きでスタックに作成された場合は、コピーコンストラクタでコピーが作成され、それにアクセスできます。__blockが無い場合は、constコピーコンストラクタでコピーが作成されます。

上記の変数についてはなんら問題ないことがわかります。注意するのは、スタック上のデータをポインタで参照している場合や、ヒープ上に作成したC/C++のデータをポインタで参照している場合です。これらの場合、ブロックが実行される際にメモリ上のデータが消えていないことは、開発者が保証しなければなりません。

Archive for the ‘Objective-C’ Category:

Objective-Cのブロック

Friday, March 30th, 2012

Objective-Cの機能のネーミングは、わかりにくい気がします。今回は名前からは想像できない機能を持つ「カテゴリ」です。

カテゴリは既存のクラスを、継承することのなく機能拡張してしまう機能です。なんと恐ろしい。

@interface MyClass : NSObject
@end

@implementation MyClass
@end

@interface MyClass (MyCategory)
-(void)print;
@end

@implementation MyClass (MyCategory)
-(void)print;
{
  NSLog(@"Hello World!");
}
@end

これで、MyClassを拡張して、新たにprintメソッドを追加しています。@interfaceと@implementationを以下のように書くところがポイントです。

@interface 拡張したいクラスの名前 (カテゴリ名)
...
@implementation 拡張したいクラスの名前 (カテゴリ名)
...

同じファイルに基のクラス定義とカテゴリの定義を書いてしまうと、すごさがわかりずらいけど、このクラスの拡張はラインタイム時に行われるので、この定義を参照しているところだけでなく、すべてのコードに反映されます。
そして、これはNSObjectやNSStringに対してもできてしまう!!
機能としてはC#のpartialクラスが近いだろうけど、C#は基のクラスもpartialにしなければならないから、最初から拡張されると定義されたクラスにしかできない。でもObjective-Cのカテゴリは関係なく、どのクラスに対してもできてしまいます。

利用シーンとしては、C#のpartialと同様に、ツールによる生成部分とソースコードを分けるために使うというのがあります。後は、それこそ、継承せずに既存のクラスを拡張してしまうという使い方ですが、これをしてしまうとコードの可読性はおそらく悪くなるのでおすすめはしません。

おそらく多くの人にとって有用なのはGoogleのコーディング規約にもあるように、クラスが内部で利用するメソッドと、外部へ公開するメソッドに分割するためにカテゴリを使うという手法でしょう。
特にObjective-C 2.0からはカテゴリ名を無名にしたクラスエクステンションという機能により、これを実現できるようになりました。MyClassを新たにクラスエクステンションを用いて、公開メソッドとプライベートメソッドに分けて定義してみます。まずはヘッダーファイル(.h)です。
MyClass.h:

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
-(void)print;
@end

公開メソッドであるprintしか定義がありません。次に、実装ファイル(.m)です。
MyClass.m:

#import "MyClass.h"

@interface MyClass ()
-(void)innerMethod;
@end

@implementation MyClass
-(void)print
{
    [self innerMethod];
}
-(void)innerMethod
{
    NSLog(@"Hello World!");
}
@end

ここで注意するのが以下の部分です。

@interface MyClass ()
-(void)innerMethod;
@end

括弧内にカテゴリ名を記述しないことでクラスエクステンションとなります。そして内部メソッドであるinnerMethodを定義しています。@implementationには、公開メソッドであるprintと、プライベートメソッドであるinnerMethodの両方の実装を記述しています。これで、printのみが外部からアクセスでき、innerMethodにはアクセスできなくなります。

Archive for the ‘Objective-C’ Category:

Objective-Cのブロック

Friday, March 30th, 2012

selectorに続いて、関連の深いmessage forwardingについて。
あるオブジェクトから他のオブジェクトに処理の委譲をしたい場合、selectorを使うと以下のように書けます。

@interface DelegateClass : NSObject
-(void)hello;
@end

@implementation DelegateClass
-(void)hello
{
    NSLog(@"Hello");
}
@end

@interface MyClass : NSObject
@property(strong, nonatomic) DelegateClass *obj;
-(void)hello;
@end

@implementation MyClass
@synthesize obj;
-(id)init
{
    obj = [[DelegateClass alloc]init];
    return self;
}
-(void)hello
{
    if([obj respondsToSelector: @selector(hello)]) {
        [obj hello];
    }
}
@end

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        SEL aSelector = @selector(hello);
       
        MyClass *obj1 = [[MyClass alloc] init];
        [obj1 performSelector: aSelector];
    }
    return 0;
}

ここではMyClassのhelloメソッドの処理を、DelegateClassへ委譲しています。NSObjectのrespondsToSelectorで、そのオブジェクトがselectorに対応したメソッドを実装しているかを確認できます。
この方法だと、委譲するメソッドの数が増えるとMyClassにも同じようなことをいくつも書かなければいけないという問題があります。
ここで、message forwardingを使うと、2つのメソッド(forwardInvocationとmethodSignatureForSelector)ですべての処理を委譲できるようになります。

@interface DelegateClass : NSObject
-(void)hello;
@end

@implementation DelegateClass
-(void)hello
{
    NSLog(@"Hello");
}
@end

@interface MyClass2 : NSObject
@property(strong, nonatomic) DelegateClass *obj;
-(void)forwardInvocation:(NSInvocation *)anInvocation;
@end

@implementation MyClass2
@synthesize obj;
-(id)init
{
    obj = [[DelegateClass alloc]init];
    return self;
}
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
    if ([obj respondsToSelector: aSelector]) {
        return [obj methodSignatureForSelector: aSelector];
    }
    return [super methodSignatureForSelector: aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation;
{
    if([obj respondsToSelector: [anInvocation selector]]) {
        [anInvocation invokeWithTarget: obj];
    }
    else {
        [super forwardInvocation:anInvocation];
    }
}
@end

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        SEL aSelector = @selector(hello);
       
        MyClass2 *obj2 = [[MyClass2 alloc] init];
        [obj2 performSelector: aSelector];
    }
    return 0;
}

MyClass2にselectorでメッセージングした際、もしそのselectorに対応できるメソッドが無い場合、まずmethodSignatureForSelectorが呼び出されます。ここで、DelegateClassの対応するメソッド(今回はhelloメソッド)のメソッドシグネチャーを返します。次にforwardInvocationが呼び出されるので、実際にDelegateClassのオブジェクトのhelloメソッドを呼び出します。このやり方ですとDelegateClassに新しいメソッドが追加されてもMyClass2への変更はいらなくできます。

Archive for the ‘Objective-C’ Category:

Objective-Cのブロック

Friday, March 30th, 2012

Objective-Cのちょっと他には無い機能について、自分自身のメモのためにも書き留めておこうと思います。
まずは、selectorから。
C/C++の関数ポインタに近いですが、動的言語としての面も持ち合わせているObjective-Cらしい所もあり、Javaのリフレクション/イントロスペクションの方が近いかもしれません。

@interface MyClass : NSObject
-(void)hello;
@end

@implementation MyClass
-(void)hello
{
  NSLog(@"Hello");
}
@end

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        SEL helloSelector = @selector(hello);

        MyClass *aObj = [[MyClass alloc] init];
        [aObj performSelector: helloSelector];
    }
    return 0;
}

MyClassというクラスにhelloというメソッドがあります。これを、通常のメッセージングではなく、selector経由で呼び出しています。そのためには、まずメソッドhelloのselectorを取得します。

SEL helloSelector = @selector(hello);

“@selector(メソッド名)”でselectorを取得し、SELという特別なselectorを格納するための型の変数を宣言し、そこに代入します。
selectorを用いたメソッド呼び出しは、NSObjectのperformSelectorを使います。

[aObj performSelector: helloSelector];

Javaのリフレクション/インストロスペクションに近いというと、文字列でメソッドを呼び出したいですが、NSSelectorFromStringを用いると文字列でselectorを生成できます。

SEL helloStringSelector = NSSelectorFromString(@"hello");

MyClass *aObj = [[MyClass alloc] init];
[aObj performSelector: helloStringSelector];

このSEL型ですが、おもしろいのはどのクラス定義とも関連づけられていなく、純粋なメソッドの定義から作られているということです。どういうことかというと、以下のように異なるMyClassとMyClass2に対しても、同じメソッド定義ならば、同じselectorが使えます。

@interface MyClass : NSObject
-(void)hello;
@end

@implementation MyClass
-(void)hello
{
  NSLog(@"Hello");
}
@end

@interface MyClass2 : NSObject
-(void)hello;
@end

@implementation MyClass2
-(void)hello
{
  NSLog(@"Hello");
}
@end

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        SEL helloSelector = @selector(hello);

        MyClass *aObj = [[MyClass alloc] init];
        [aObj performSelector: helloSelector];
        MyClass2 *obj2 = [[MyClass2 alloc] init];
        [obj2 performSelector: helloSelector];
    }
    return 0;
}

Javaのリフレクションでは、java.lang.reflect.Methodは、クラス定義と紐づいているのでこういったことはできません。

そのためか、引数を持つメソッドの場合、引数を持つということも含めて指定してselectorを作成する必要があります。

@interface MyClass : NSObject
-(void)hi:(NSString*)name;
@end

@implementation MyClass
-(void)hi:(NSString*)name;
{
    NSLog(@"Hi, %@", name);
}
@end

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        MyClass *aObj = [[MyClass alloc] init];

        SEL hiSelector = @selector(hi:);
        [aObj performSelector: hiSelector withObject: @"John"];

        SEL hiStringSelector = NSSelectorFromString(@"hi:");
        [aObj performSelector: hiStringSelector withObject: @"John"];
    }
}

@selector(hi:)のようにコロン(:)を一つ含めていることで、引数を一つ持つメソッドであることを宣言しています。引数が二つになればコロンが二つになります。

@interface MyClass : NSObject
-(void)hi:(NSString*)name name2:(NSString*)name2;
@end

@implementation MyClass
-(void)hi:(NSString*)name name2:(NSString*)name2
{
    NSLog(@"Hi, %@ and %@", name, name2);
}
@end

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        MyClass *aObj = [[MyClass alloc] init];

        SEL hiTwoArgsSelector = @selector(hi:name2:);
        [obj2 performSelector: hiTwoArgsSelector withObject: @"John" withObject: @"Bill"];

        SEL hiTwoArgsStringSelector = NSSelectorFromString(@"hi:name2:");
        [obj2 performSelector: hiTwoArgsStringSelector withObject: @"John" withObject: @"Bill"];
    }
}

Archive for the ‘Objective-C’ Category:

Objective-Cのブロック

Friday, March 30th, 2012

Objective-Cには「Key-Value Coding (KVC)」というものが備わっており、文字列による名前指定でプロパティの値の取得、設定といったことができるらしい。Javaだと、Java Beans + BeanUtilsを組み合わせた機能といったところでしょうか。こういった機能が言語の標準機能として備わっているというのは面白い。
参考にした資料はKey-Value Coding Programming Guideです。

Objective-CにおけるKey-Value Coding

Key-Value Codingとは

オブジェクト指向言語であるObjective-Cでは、インスタンス変数へ直接アクセスすることは許されず、以下のようにsetter/getterを定義することとなります。

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
    NSString *name;
}
-(NSString*)name;
-(void)setName:(NSString*)_name;
@end

@implementation MyClass
-(NSString*)name {
    return name;
}
-(void)setName:(NSString *)_name
{
    name = _name;
}
@end

int main (int argc, const char * argv[])
{
    @autoreleasepool {
        MyClass *obj = [[MyClass alloc]init];
        [obj setName:@"James"];
        NSLog(@"%@", [obj name]);
    }
    return 0;
}

当然ながら、setter/getterを自分で実装するのではなく、プロパティとして定義することも可能です。

@interface MyClass : NSObject
@property(strong) NSString *name;
@end

@implementation MyClass
@synthesize name;
@end

どちらにしてもnameというインスタンス変数にアクセスするために、メソッドnameとsetNameを使います。
ただ、特にGUIプログラミングでは、キー名を文字列で指定して、プロパティにアクセスできると相当のコード量を減らせる場合があります。それを実現するのがKey-Value Coding(KVC)です。
具体的には、「setValue: forKey:」で値を設定し、「valueForKey:」で値を取得します。以下がコード例です。

int main (int argc, const char * argv[])
{
    @autoreleasepool {
        MyClass *obj = [[MyClass alloc]init];
        [obj setValue:@"Richard" forKey:@"name"];
        NSLog(@"%@", [obj valueForKey:@"name"]);
    }
    return 0;
}

つまり、Objective-Cでは、プロパティもしくは決められたルールに従ったsetter/getterを作成すれば、その値を「名前の文字列(ここでは@”name”)」でアクセスすることができます。何も特別なコーディングはいりません。

Key Path

オブジェクトの中にオブジェクトがあり、そのプロパティにKVCを用いてアクセスしたい場合、Key Pathというものを使います。
例として、以下のようにParentクラスがChildクラスのインスタンスを保持していて、さらにChildクラスにnameというプロパティがある場合を考えます。

@interface Child : NSObject
@property(strong) NSString *name;
@end

@implementation Child
@synthesize name;
@end

@interface Parent : NSObject
@property(strong) Child* child;
@end

@implementation Parent
@synthesize child;
@end

この構造において、ParentクラスからいきなりChildクラスのnameプロパティへアクセスする方法を提供するのがKey Pathです。具体的には、以下のようにドットでプロパティ名を区切って指定します。

[parent setValue:@"Jeremy" forKeyPath:@"child.name"];
NSLog(@"%@", [parent valueForKeyPath:@"child.name"]);

1対多関係

先ほど説明したのは、一つの値を持つプロパティです。次は、複数の値を持つプロパティ、つまり1対多の関係を持つ場合です。

NSMutableArray型のプロパティ

当然のことながら、クラスは配列型のプロパティを持つことができ、その配列を先ほど説明したvalueForKeyメソッドで取得することができます。

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
@property(strong) NSMutableArray *languages;
-(id)init;
@end

@implementation MyClass
@synthesize languages;
-(id)init
{
    languages = [NSMutableArray arrayWithObjects: @"Objective-C", @"C++", @"Java", nil];
    return [super init];
}
@end

int main (int argc, const char * argv[])
{

    @autoreleasepool {
        MyClass *obj = [[MyClass alloc]init];
        id array = [obj valueForKey:@"languages"];
        for(int i = 0; i < [array count]; ++i) {
            NSLog(@"%@", [array objectAtIndex:i]);
        }
    }
    return 0;
}

KVCにおける一対多

先ほどの例は、プロパティがNSMutableArray型ですが、そうでない場合はどうすればよいのでしょうか。例えば、独自のデータ構造を用いている場合です。KVCでは、このような場合にも対応できるように、以下の二つのメソッドを実装していれば、KVCにおける一対多の関係と認識できるようにしています。
一対多関係の多側がいくつの要素を持つかを返す:

-(NSUInteger)countOf<Key>

指定したインデックスの要素を返す:

-(id)objectIn<Key>AtIndex:(NSUInteger)index

ちょっとイメージがつきにくいと思いますので、サンプルを以下に示します。わざと、NSMutableArrayを使わず、switch文で特定のインデックスの値を返すようにしています。

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
-(NSUInteger)countOfLanguages;
-(id)objectInLanguagesAtIndex: (NSUInteger)index;
@end

@implementation MyClass
-(NSUInteger)countOfLanguages
{
    return 3;
}
-(id)objectInLanguagesAtIndex: (NSUInteger)index
{
    switch(index) {
        case 0:
            return [NSString stringWithString: @"Objective-C"];
            break;
        case 1:
            return [NSString stringWithString: @"C++"];
            break;
        case 2:
            return [NSString stringWithString: @"Java"];
            break;
    }
    return [NSString stringWithString: @"Error"];
}
@end

int main (int argc, const char * argv[])
{
    @autoreleasepool {
        MyClass *obj = [[MyClass alloc]init];
        id array = [obj mutableArrayValueForKey: @"languages"];
        for(int i = 0; i < [obj countOfLanguages]; ++i) {
            NSLog(@"%@", [array objectAtIndex:i]);
        }
    }
    return 0;
}

ここではlanguagesというプロパティに対して、mutableArrayValueForKeyを用いてアクセスしています。mutableArrayValueForKeyはNSMutableArrayと似たような機能を持つクラスを返します。ですが、実際にはNSMutableArrayではなく、特別なProxyオブジェクトが自動的に生成されて返されます。このProxyは、MyClassのcountOfやobjectInAtIndexにアクセスして、NSMutableArrayのように見せています。

Archive for the ‘Objective-C’ Category:

Objective-Cのブロック

Friday, March 30th, 2012

Objective-Cを勉強していて一番驚いたのがメモリ管理の仕組みです。ちょっと前までは、手動メモリ管理(MRC: Manual Reference Counting)、その後、GCがMac OS Xのみに入ったけど、最近になって新たにARC(Automatic Reference Counting)が導入されたとのこと。これからはARCが主流となるとのことで、少し調べてみました。

Appleの出している資料は、おそらくこのTransitioning to ARC Release Notesのみだと思われます。でも、どのような仕組みでARCが動作しているかの情報もなく、少しわかりにくい。

おそらく一番詳しいのはClangのAutomatic Reference Countingだと思います。これを読むと、内部でどのような動作をしているのかはわかります。ただ、じゃあ実際のコーディングはどうしたらいいのかという点は、わかりにくいです。

その点では、この記事が一番わかりやすかったです。なので、以下の内容は、これらの内容を参考にしながら書いたものです。

ARCによるメモリ管理

基本的な考え方

ARCになっても、管理の基本的な仕組みは、手動メモリ管理のときと同じです。つまり、各オブジェクトが参照カウンタ(retain counter)を持っていて、そのカウンタが0になるとオブジェクトが破棄されます。また、戻り値や一時的なオブジェクトのためにauto release poolが存在します。
ARCでは、新たに「強い参照」と「弱い参照」の概念が導入されます。強い参照は今までの参照と同じく、強い参照がある間はそのオブジェクトは破棄されません。対して、弱い参照は、弱い参照があっても、強い参照がなくなればそのオブジェクトは破棄されます。強い参照と弱い参照のどちらの参照なのかをコンパイラに教えるために、新たに__strong、__weakという修飾子が用意されます。
強い参照と弱い参照の二種類を持つ理由は「循環参照」を避けるためです。循環参照についてはこちらを参照してください。

ARCで用いる修飾子

変数のためのARC修飾子

強い参照

以下のように__strongを指定することで、その参照は強い参照と認識されるようになります。

__strong MyClass *obj = [[MyClass alloc]init];

デフォルトは強い参照となるので、通常は指定する必要はありません。このため、alloc/initで生成されたオブジェクトは、現在のスコープのライフタイムの間、維持されることとなります。「現在のスコープ」の意味するところは、多くの場合、変数が宣言された中括弧内を指します。

弱い参照

以下のように__weakを指定することで、その参照は弱い参照と認識されるようになります。

__weak MyClass *wobj = obj;

__weakが指定されたオブジェクトは、いつでも削除される可能性があります。弱い参照を用いる場合は、普通は、他の場所で強い参照が持たれているはずです(でないと、そのオブジェクトは破棄されてしまうので)。このオブジェクトが破棄された場合、この弱い参照にはnilが設定されます。これにより、破棄された後でも、nilに対するメソッド呼び出しは無視されるため、アプリケーションのクラッシュにはつながりません。

残念ながら、__weakをサポートしているのは、OS X 10.7とiOS 5からです。このため、OS X 10.6やiOS 4では使えません。代わりに__unsafe_unretainedを使うことができます。動作は、オブジェクトが破棄された場合にnilにならない以外は同じです。ただ、破棄された際にnilとならないので、破棄後のメソッド呼び出しはアプリケーションのクラッシュとなります。

プロパティのためのARC修飾子

強い参照

ARCがない頃は、以下のように記述していました。

@property(retain) NSObject *obj;

ARCでは、これを以下のように記述します。

@property(strong) NSObject *obj;

弱い参照

ARCがない頃は、以下のように記述していました。

@property(assign) NSObject *obj;

ARCでは、これを以下のように記述します。

@property(weak) NSObject *obj;

*) 一番わかりやすいのは、どこか一カ所で強い参照でそのオブジェクトを所有し、他の場所では、その弱い参照を持ち、そのオブジェクトにアクセスするという方法です。

ARCを使う場合のルール

1.alloc/init

オブジェクトを生成した後、retain/release/autorelease/retainCountを呼び出してはなりません。さらに、@selector(retain)や@selector(release)を用いたセレクタによる呼び出しもしてはなりません。

2.dealloc

deallocメソッドは自動的に生成されます。それと、直接deallocを呼び出してはなりません。ただ、インスタンス変数以外のリソースをリリースするためにカスタムdeallocを作成することは問題ありません。しかし、この際、[super dealloc]は呼び出してはなりません。これはコンパイラによって行われます。

3.プロパティの宣言

ARCがない頃は、@property宣言にassign/retain/copyパラメータを指定することで、メモリ管理の仕方をコンパイラに指示していました。しかし、ARCではこれらのパラメータは不要です。代わりに、weak/strongパラメータを指定し、コンパイラに指示します。

4.NSAutoReleasePoolの代わりに@autoreleasepoolを使う

5.C構造体内のオブジェクトポインタ

これは使ってはなりません。構造体の代わりにクラスを用いるべきです。

6.idとvoid*の間のキャスト

これはCore FoundationのCライブラリとFoundation KitのObjective-Cライブラリメソッドの間で発生します。ARCを用いる場合、CFオブジェクトがメモリ管理下から出たり、入ったりした場合に、ヒントをコンパイラに与えなければなりません。このヒントとしては、__bridgeや__bridge_retain、__bridge_transferなどです。さらに、Core FoundationオブジェクトのメモリマネージメントのためにはCFRetainとCFReleaseが必要となります。

7.その他

ゾーン(NSZone)によるメモリ管理はなくなったので、NSAllocateObjectやNSDeallocateObjectは使えない。

*) Objective-Cに閉じた世界で、あまり凝ったことをしていなければ1〜4を覚えておけばよいと思います。

関数の引数や戻り値

関数の引数

関数やインスタンスメソッドの引数として、オブジェクトを渡す場合は、普通は、呼び出し元側で強い参照を持っているので大きな問題となりません。
もし、特別な理由で、参照カウンタを+1してから関数やメソッドにオブジェクトを渡したい場合は、以下のように記述することができます。

void foo(__attribute((ns_consumed)) id x);
- (void) foo: (id) __attribute((ns_consumed)) x;

ただし、普通は、特に何もする必要はありません。(つまり、呼び出し元で、引数として渡すオブジェクトの強い参照を持ち、呼び出せばよい)

関数の戻り値

MRCの時代にautoreleaseという仕組みを用意したように、関数の戻り値をどのように返すかには工夫が必要となります。例えば、以下のようにインスタンス変数を返す場合は、参照カウンタがすでに+1されているので問題はありません。

@interface MyClass : NSObject
{
    NSString *name;
}
-(NSString*)getName;
@end

@implementation MyClass
-(NSString*)getName
{
    return name;
}
@end

でも、以下のように関数内で生成したオブジェクトを返す場合は、どう考えればよいのでしょうか。関数を抜けた際、参照カウンタが-1されてしまうと、参照カウンタが0になるのでオブジェクトが破棄されてしまいます。MRCでは参照カウンタを-1するタイミングをずらすためにautoreleaseを用いていました。ARCではどのようになるのでしょうか。

NSString* generateName()
{
    NSString *name = [NSString stringWithString:@"James"];
    return name;
}

ただ、この点について、どのように実装されているのかあまりはっきり書かれていません。Clangの資料では3.2.3 Unretained return valuesが該当箇所と思われます。以下は、該当部分の(だいたいの)訳です。

“メソッドや関数で、Objective-Cオブジェクト(つまり、参照カウンタを持つオブジェクト)を返すとなっているが、実際には、すでに参照カウンタが+1されている値を返すのではない場合は、オブジェクトが戻り側の境界において有効であることを保証しなければならない。
このような関数やメソッドから戻る場合、ARCはreturnステートメントを評価する時点でカウンタを+1する。そして、すべてのローカルスコープにおいてその状態を維持し、呼び出し境界において値が存在することを保証する。最悪のケースでは、autoreleaseが関与するかもしれない。しかし、呼び出し側は値が実際にautoreleaseプールにあるかどうかを仮定してはならない。
ARCは、呼び出し側ではこれ以上の作業は行わない。しかしながら、もしかしたら戻り値の生存期間を短くするために何かするかもしれない。”

結局はっきりしないので、以下のようなコードで実際にどこでオブジェクトが破棄されるのかを確認しました。

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
-(void)dealloc;
@end

@implementation MyClass
-(void)dealloc {
    NSLog(@"dealloc");
}
@end

MyClass* test()
{
    NSLog(@"enter test() scope");
    MyClass *obj1 = [[MyClass alloc]init];
    NSLog(@"leave test() scope");
    return obj1;
}

int main (int argc, const char * argv[])
{
    NSLog(@"enter main scope");
    @autoreleasepool {
        NSLog(@"enter autorelease scope");
        {
            NSLog(@"enter inner scope");
            MyClass *obj2 = test();
            NSLog(@"leave inner scope");
        }
        NSLog(@"leave autorelease scope");
    }
    NSLog(@"leave main scope");
    return 0;
}

考えられるのは、obj2がスコープから外れるタイミング、つまり「leave inner scope」と「leave autorelease scope」の間です。しかしながら、実際に実行してみると以下のような結果となります。

objc_unretained_return[1083:707] enter main scope
objc_unretained_return[1083:707] enter autorelease scope
objc_unretained_return[1083:707] enter inner scope
objc_unretained_return[1083:707] enter test() scope
objc_unretained_return[1083:707] leave test() scope
objc_unretained_return[1083:707] leave inner scope
objc_unretained_return[1083:707] leave autorelease scope
objc_unretained_return[1083:707] dealloc
objc_unretained_return[1083:707] leave main scope
Program ended with exit code: 0

つまり、autoreleaseの後となります。ここから推測されるのは、戻り値はautoreleaseプールに入るということになります。つまり、Clangの資料の後半の実装になっているようです。

*) ですので、@autoreleasepool {}は必ず記述する必要あります。

ブリッジキャスト

C/C++のオブジェクトとObjective-Cのオブジェクトの間の変換を行うのが、ブリッジキャストです。Objective-Cしか使わないという方は読飛ばしてかまいません。
Objective-Cのオブジェクトは、すべて参照カウンタを持っています。対して、C/C++のオブジェクトは持っていません。なので、単純にキャストすることはできません。

(__bridge T) op

(__bridge T) opは、opをT型へキャストします。もしTがObjective-Cオブジェクトポインタ型ならば、opはC/C++オブジェクトポインタ型でなければなりません。もし、TがC/C++オブジェクトポインタ型ならば、opはObjective-Cオブジェクトポインタ型でなければなりません。所有者の移動はないので、ARCは参照カウンタを+1しません。

T *obj2 = (__bridge T)obj1;

(__bridge_retained T) op

(__bridge_retained T) opは、Objective-Cオブジェクトポインタ型であるopを、C/C++オブジェクトポインタ型 Tへ変換します。ARCはopの参照カウンタを+1します。

(__bridge_transfer T) op

(__bridge_transfer T) opは、C/C++オブジェクトポインタ型であるopを、Objective-Cオブジェクトポインタ型 Tへ変換します。ARCはopの参照カウンタを-1します。

通常、(__bridge_retained T) opと(__bridge_transfer T) opは対で使います。__bridge_retainedで、C/C++へポインタを渡し、__bridge_transferで結果のポインタを戻すことになります。

補足: C++プログラマにとってのARC

C++ 11で正式に採択されたstd::shared_ptr<>が、Objective-Cでの強い参照(__strong)となり、std::weak_ptr<>がObjective-Cでの弱い参照(__weak)となります。関数の戻り値については、呼び出し元で受け取った際に+1されるので、ややこしい仕組みは持っていない(はず)です。Clangの資料の前半に近い実装なのだと思います。

補足: WindowsプログラマにとってのARC

MRCがCOM(AddRef/Release)、ARCがATL(CComPtr)と思ってください。