在Objective-C中比较块与函数

8

由于我正在学习Objective-C,我的理解还比较新而且不完整。块(block)的概念非常类似于函数(function),它们甚至看起来几乎相同:

函数名为 'multiply'

#import <Foundation/Foundation.h>

int multiply (int x, int y)
{

    return x * y;

}

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

        int result = multiply(7, 4); // Result is 28.
        NSLog(@"this is the result %u",result);

        }
}

名为“Multiply”的块

#import <Foundation/Foundation.h>

int (^Multiply)(int, int) = ^(int num1, int num2) {

    return num1 * num2;
};

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

    @autoreleasepool {

        int result = Multiply(7, 4); // Result is 28.
        NSLog(@"this is the result %u",result);
    }
}

我在网上发现了各种关于块的声明,如下: “块被实现为Objective-C对象,但它们可以放在堆栈上,所以它们不一定需要被malloc(如果你保留一个块的引用,它将被复制到堆上)。" Ray Wenderlich说道: “块是一流函数”
我完全不知道这些话的意思。我的例子展示了块和函数完成相同的事情。有人能举个例子说明块可以做些函数不能做的事情吗?还是反过来呢?或者这是更微妙的东西,比如变量“result”在内存中的处理方式?或者哪个更快/更安全?它们中的任何一个都可以在类定义中用作方法吗?
谢谢。

块是对象。任何对象能做的事情,块也可以做。包括像在运行时将对象关联到它们或将其添加到集合中这样的事情。 - CrimsonChris
2
一个块是一种匿名函数,在Objective-C中可以被视为一个对象。https://developer.apple.com/library/IOs/featuredarticles/Short_Practical_Guide_Blocks/index.html - quellish
3
更重要的是,闭包可以捕获值(即它可以具有稳定性),但函数则无法。 - Bryan Chen
@quellish - 显然你找到了我所引用的苹果文档的例子。但是它就像一个循环引用,因为它没有提到它们与函数的区别。 - aquagremlin
@Bryan - 感谢您的陈述,但我不理解什么是“捕获值”,甚至不知道“stableful”是什么意思。很抱歉我是自学的,显然缺乏对编译器操作的基本理解。 - aquagremlin
显示剩余4条评论
2个回答

10

块(Blocks)是 Objective-C 对象,而函数则不是。实际上,这意味着您可以像这样从一段代码传递一个块到另一段:

NSArray *names = @[@"Bob", @"Alice"];
[names enumerateObjectsUsingBlock:^(id name, NSUInteger idx, BOOL *stop) {
    NSLog(@"Hello, %@", name);
}];

在C语言中,您可以通过传递函数指针来实现类似的效果。然而,使用块和这样做之间的主要区别在于,块可以捕获值。例如,在上面的示例中,如果我们想要使用变量greeting:
NSString *greeting = @"Hello";
NSArray *names = @[@"Bob", @"Alice"];
[names enumerateObjectsUsingBlock:^(id name, NSUInteger idx, BOOL *stop) {
    NSLog(@"%@, %@", greeting, name);
}];

在这个例子中,编译器可以看到该块取决于本地变量“greeting”,并且将“捕获”“greeting”的值,并将其与块一起存储(在这种情况下,这意味着保留和存储指向NSString的指针)。无论块最终在哪里使用(在这种情况下,在[NSArray-enumerateObjectsUsingBlock:]的实现中),它都可以访问当块声明时greetings变量的值。这使您可以在块的范围内使用任何本地变量,而无需担心将它们传递到块中。
要在C中使用函数指针执行相同的操作,需要将“greeting”作为变量传入。但是,这是不可能的,因为调用者(在这种情况下,NSArray)不能知道(特别是在编译时)需要传递给函数的确切参数。即使它知道,您也需要以某种方式将“greeting”的值与NSArray一起传递,以及您想要使用的每个其他本地变量,这将很快变得混乱:
void greet(NSString *greeting, NSString *name) {
    NSLog(@"%@, %@", greeting, name);
}

// NSArray couldn't actually implement this
NSString *greeting = @"Hello";
NSArray *names = @[@"Bob", @"Alice"];
[names enumerateObjectsUsingFunction:greet withGreeting:greeting];

谢谢igul222。我太老了,还记得BASIC和PL1编程语言。在那些语言中,主循环中的变量通常可以通过子程序、函数或过程调用(与函数相同,但不传递任何内容)进行访问。作用域规则至关重要,您必须记住您所在的深度,以知道哪些变量在您的上方(可访问),哪些在您的下方(不可访问)。 - aquagremlin
那么块可以访问任何变量?是的吗?顺便说一下,我尝试在我的注释中添加换行符,但是在行末添加两个空格什么也没发生。我该如何在这些注释中创建新段落? - aquagremlin

8

块是闭包——它们可以从周围范围中捕获本地变量。这就是块(以及其他现代语言中的匿名函数)与C语言中的函数之间的重大区别。

下面是一个高阶函数makeAdder的示例,它创建并返回一个“adder”函数,该函数将某个基数加到其参数上。这个基数由makeAdder的参数设置。因此,makeAdder可以返回具有不同行为的不同“adders”:

typedef int (^IntFunc)(int);

IntFunc makeAdder(int x) {
    return ^(int y) { return x + y; }
}

IntFunc adder3 = makeAdder(3);
IntFund adder5 = makeAdder(5);

adder3(4); // returns 7
adder5(4); // returns 9
adder3(2); // returns 5

这在C语言中是不可能使用函数指针实现的,因为每个函数指针必须指向代码中的实际函数,这些函数数量是有限的,在编译时已经确定,每个函数的行为也在编译时被固定下来。因此,像makeAdder这样根据运行时值创建几乎无限数量的“adders”的能力是不可能实现的。相反,您需要创建一个结构体来保存状态。
与您的示例中不捕获周围作用域的局部变量的块不同,它与普通函数几乎没有什么不同,除了类型。

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