スクリプト言語間における「lexical closure」の違い – Blocksの場合
元ネタ:
スクリプト言語でもclosureでもないけど、一応やっておかないとね。
Objective-C with Blocks
#import <Foundation/Foundation.h> #import <objc/runtime.h> // 注: スタックの持ち方に問題のある書き方 int main(int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSMutableArray *closures = [NSMutableArray array]; for (int i = 0; i < 5; i++) { NSString *localvar = [NSString stringWithFormat:@"foo%d", i]; [closures addObject:^{return localvar;}]; } for (int i = 0, c = [closures count]; i < c; i++) { NSLog(@"%@", ((id (^)())[closures objectAtIndex:i])()); } [pool drain]; return 0; }
2010-11-04 03:37:37.636 BlocksSample[49954:a0f] foo4 2010-11-04 03:37:37.638 BlocksSample[49954:a0f] foo4 2010-11-04 03:37:37.639 BlocksSample[49954:a0f] foo4 2010-11-04 03:37:37.639 BlocksSample[49954:a0f] foo4 2010-11-04 03:37:37.640 BlocksSample[49954:a0f] foo4
がっかりだ……。これはlocalvarが使い回されるせいではなく、Blocksがスタック上の同じアドレスに作られるせいだと思われる。
次のようにコピーすれば解消する。
#import <Foundation/Foundation.h> #import <objc/runtime.h> // 修正版 int main(int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSMutableArray *closures = [NSMutableArray array]; for (int i = 0; i < 5; i++) { NSString *localvar = [NSString stringWithFormat:@"foo%d", i]; [closures addObject:[[^{return localvar;} copy] autorelease]]; } for (int i = 0, c = [closures count]; i < c; i++) { NSLog(@"%@", ((id (^)())[closures objectAtIndex:i])()); } [pool drain]; return 0; }
2010-11-04 03:49:22.292 BlocksSample[50207:a0f] foo0 2010-11-04 03:49:22.299 BlocksSample[50207:a0f] foo1 2010-11-04 03:49:22.304 BlocksSample[50207:a0f] foo2 2010-11-04 03:49:22.305 BlocksSample[50207:a0f] foo3 2010-11-04 03:49:22.307 BlocksSample[50207:a0f] foo4
というかスタックへの参照をうかつに残すとクラッシュするので、コピーするのが正解のようではある。
だったらもう少し使い易いシンタックスシュガーが欲しい気もする。