从NSArray中执行块?

37

我在思考,既然可以将块视为对象,如果我创建了两个块并将它们添加到NSArray中,是否有一种方法可以从该数组执行它们?

int (^Block_001)(void) = ^{ return 101; };
int (^Block_002)(void) = ^{ return 202; };
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];

编辑:为了更好的表述,根据 @davedelong 的出色回答进行更新。

int (^Block_001)(void) = [^{ return 101; } copy];
int (^Block_002)(void) = [^{ return 202; } copy];
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];

[Block_001 release];
[Block_002 release];

2
很棒的一组答案……我看到标题就希望能轻松获得一些声望分。大家已经非常全面地覆盖了它。 :) - bbum
只是一个小提示,如果你不复制/释放该块,它将在堆栈中。所以如果堆栈被销毁了,应用程序会崩溃,对吧? - fzaziz
3个回答

60

@KennyTM和@David是正确的,但你的代码可能有问题。原因如下:

当使用对象创建一个NSArray时,它会retain这些对象。对于块对象,它使用Block_retain函数。这意味着数组已经保留了你创建的块对象,但它们存储在栈上(块对象是Objective-C中可以在栈上创建的非常罕见的例子,而不需要进行荒谬的技巧)。这意味着,一旦该方法退出,你的数组现在指向垃圾数据,因为它所指向的块对象已经不存在。要正确处理这个问题,应该这样做:

int (^Block_001)(void) = [^{ return 101; } copy];
int (^Block_002)(void) = [^{ return 202; } copy];
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];

[Block_001 release];
[Block_002 release];

通过在块上调用copy,您明确地将块从堆栈移动到堆上,在那里它可以在方法/函数退出后安全地保留。然后,在将块添加到数组之后,您必须平衡您的copy(由于NARC规则),需要随后调用release


嗨,Dave,是的,我明白了,我不知道块是在堆栈上创建的(今天才开始研究块)。非常感谢。 - fuzzygoat
嗨,Dave,我发现了这个问题和David Gelhar的被接受的答案,所以我实现了它,而且它运行得非常好,没有崩溃。继续阅读,你的答案很有道理,但并没有解释为什么David的代码没有立即崩溃。你有任何线索吗? - Tieme
1
@Tieme David的代码不会立即崩溃,因为当您执行存储在数组中的块时,仍处于创建该块的同一词法作用域中。如果将该数组作为方法的返回值返回,然后尝试在调用函数中执行块,则会崩溃。虽然如果您使用ARC,它可能不会崩溃,因为编译器会为您插入必要的复制调用。 - Dave DeLong
好的,我明白了,我会测试一下。但是尽管编译器可能会插入复制,你也不应该像“release”和“retain”那样省略它,对吧? - Tieme

29

当然,你可以像调用其他块一样使用 () 调用它,但是你需要对从 NSArray 检索到的值进行类型转换。这里有一个示例(添加了一个 typedef,否则我的头会疼):

typedef int (^IntBlock)(void);
IntBlock Block_001 = ^{ return 101; };
IntBlock Block_002 = ^{ return 202; };
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];
int x = ((IntBlock)[array objectAtIndex:0]) (); // now x == 101

好的,对于函数指针我不太熟悉,我的理解是这样的:typedef 定义了 "IntBlock",它是一个指向返回 int 类型且不带参数的块的指针。我想我理解了,看到另一种方式(我很确定我会弄错)后,我很感激您决定使用 typedef :) 非常感谢。 - fuzzygoat
1
你不需要进行强制类型转换。直接使用int (^block)() = [array objectAtIndex:0]即可正常工作。毕竟,指针就是指针,至少在像C及其衍生语言这样的低级语言中是这样的。 - aroth
@aroth 当然,您也可以使用分配给适当类型的变量来执行从-objectAtIndex返回的通用id类型的“转换”。您不能执行int y = [array objectAtIndex:0]();(编译器会抱怨“调用类型id不是函数或函数指针”)。一种方法(强制转换)或另一种方法(分配给块指针),您需要告诉编译器“我知道我从数组中取出的东西是一个块,所以当我说()时,请调用它!” - David Gelhar

7
当然可以。
int (^x)(void) = [array objectAtIndex:0];
printf("%d\n", x()); // prints 101.

我确实考虑过这个问题,非常有趣。你能否为我澄清一下,我所说的“int (^x)(void)”是指一个块的指针吗?只是想确保我掌握了正确的术语。 - fuzzygoat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接