[IOS5]FOR (;;) を書くのをやめるいくつかの方法と、BLOCKS+ARCの考察

for (int i=0; i
上のコードは、バグも起こりやすいし、ループの際に毎回countメソッド呼ばれるし、書くのが面倒ですよね。それで、Objective-Cにおいてこの書き方の代わりに使える書き方を紹介します。

状況として、array の各要素を検索して値を見つけて、クラスの中の値を変えるという状況を考えてみましょう。
これが、for (;;) を使う例です。

@implementation DetailViewController{
NSMutableArray* array;
int myIndex;
}

-(void) testFunc {

array = [NSMutableArray array];
[array addObject:[NSNumber numberWithInt:1]];
[array addObject:[NSNumber numberWithInt:2]];
[array addObject:[NSNumber numberWithInt:3]];
[array addObject:[NSNumber numberWithInt:4]];

int myvalue = 3;

for (int i=0; i

次の方法はfast enumerationを使う方法です。 for(ID a in array)という書き方ですね。僕はこれを多用するのですが、全部の要素をとることが出来るものの、iに相当するインデックスを求めるためには、変数を作って、++してあげる必要がありますね。


@implementation DetailViewController{
NSMutableArray* array;
int myIndex;
}

-(void) testFunc {

array = [NSMutableArray array];
[array addObject:[NSNumber numberWithInt:1]];
[array addObject:[NSNumber numberWithInt:2]];
[array addObject:[NSNumber numberWithInt:3]];
[array addObject:[NSNumber numberWithInt:4]];

int myvalue = 3;

int count = 0;
for (NSNumber*num in array) {
NSLog(@"%d - %d", [num intValue],count);
if ([num intValue]==2) {
continue;
}
if ([num intValue]==myvalue) {
myIndex = count;
break;
}
count++;
}
}

最後に、iOS5で追加された、NSArrayのenumerateObjectsUsingBlockを使う方法です。この方法は、for(in)の方法と同じ様に配列の全要素を返すのと同時に、breakや、要素のIDも与えてくれます。


@implementation DetailViewController{
NSMutableArray* array;
int myIndex;
}

-(void) testFunc {

array = [NSMutableArray array];
[array addObject:[NSNumber numberWithInt:1]];
[array addObject:[NSNumber numberWithInt:2]];
[array addObject:[NSNumber numberWithInt:3]];
[array addObject:[NSNumber numberWithInt:4]];

int myvalue = 3;

[array enumerateObjectsUsingBlock:^(NSNumber* num, NSUInteger idx, BOOL *stop) {
NSLog(@"%d - %d", [num intValue],idx);
if ([num intValue]==2) {
return;
}
if ([num intValue]==myvalue) {
myIndex = idx;
*stop = YES;
return;
}
}];
}

このメソッドには、オプションを渡せる、enumerateObjectsWithOptions:usingBlock:メソッドもあり、そちらは、逆順や非同期のオプションもありますので、いろいろ便利に使えそうです。

スピードに関しては、for(in) と、enumerateObjectsUsingBlockは、同等か、enumerateObjectsUsingBlockの方が早いようですので、安心して使えますね。

このサンプルで、あえてクラス内の ivar を、修正するサンプルを block 内で変更してみました。これをInstrumentでテストしてみたところ、リークはしていませんでした。blocksの構文的に、この場合は、Blockコピーは行われず、コピーの際に、ivarにアクセスすると、selfがキャプチャされて、リークするので、上の書き方で問題ないと思うのですが、もし間違ってたらご指摘お願いします。。。

基本的に、アップルのガイドに、Blocksの正しい使い方が書かれているので、ここBlocks Programming Topics: Introduction をみると、

In a manually reference-counted environment, local variables used within the block are retained when the block is copied. Use of instance variables within the block will cause the object itself to be retained. If you wish to override this behavior for a particular object variable, you can mark it with the __block storage type modifier.

If you are using ARC, object variables are retained and released automatically as the block is copied and later released.

Note: In a garbage-collected environment, if you apply both __weak and __block modifiers to a variable, then the block will not ensure that it is kept alive.
If you use a block within the implementation of a method, the rules for memory management of object instance variables are more subtle:

If you access an instance variable by reference, self is retained;
If you access an instance variable by value, the variable is retained.

ここを見て分かるのは、ARCの際は、BlockがCopyされる状況に応じて、自動的にRetainされるとあって、以下のサンプルコードが有り、asyncで、queueに追加する状況が出ています。

dispatch_async(queue, ^{
// instanceVariable is used by reference, self is retained
doSomethingWithObject(instanceVariable);
});

id localVariable = instanceVariable;
dispatch_async(queue, ^{
// localVariable is used by value, localVariable is retained (not self)
doSomethingWithObject(localVariable);
});

この状況では、自動的にqueueに追加する際に、ブロックがコピーされるので、selfのivarを扱うのが危険なんでしょう。おそらく、enumerateObjectsUsingBlockも、オプションのconcurrencyモードを使うと、asyncになるので、ブロックがコピーされるでしょう。そのことを考えると、以下で述べる、
__weak ClassName wself=self;
を常に使うのがよいでしょうね。

__weak を使って安全にblockにアクセスする場合はこちら。


@implementation DetailViewController{
NSMutableArray* array;
int myIndex;
}

-(void) testFunc {

array = [NSMutableArray array];
[array addObject:[NSNumber numberWithInt:1]];
[array addObject:[NSNumber numberWithInt:2]];
[array addObject:[NSNumber numberWithInt:3]];
[array addObject:[NSNumber numberWithInt:4]];

int myvalue = 3;

__weak DetailViewController* bself = self;
[array enumerateObjectsUsingBlock:^(NSNumber* num, NSUInteger idx, BOOL *stop) {
NSLog(@"%d - %d", [num intValue],idx);
if ([num intValue]==2) {
return;
}
if ([num intValue]==myvalue) {
bself->myIndex = idx;
*stop = YES;
return;
}
}];
}

以上です。enumerateObjectsUsingBlockを使いましょうというエントリでしたが、サンプルなど含めると長くなってしまった。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください