Objective-C和Sprite Kit游戏开发从入门到精通
上QQ阅读APP看书,第一时间看更新

2.12 块(block)

块的使用是Objective-C中比较有特点的功能之一,它为我们提供了一种更加灵活的代码结构。

图2-17 块的应用

下面的代码声明了一个名为factory的块,它包括两个int类型的参数,以及int类型的返回值,看上去和函数非常相似(实际上,在Swift中,就是使用函数类型取代了块的功能)。

    int(^factory)(int num1, int num2)

实际应用中,我们经常会将一个块的定义赋值到一个块变量,如下面的代码,我们分别定义了两个块,并赋值给两个块变量(factory1和factory2)。

    // 加法工厂块
    int(^factory1)(int, int) = ^int (int num1, int num2) {
        return num1 + num2;
    };
    // 减法工厂块
    int(^factory2)(int, int) = ^int (int num1, int num2) {
        return num1- num2;
    };

我们可以看到,块变量的格式初看起来可能有些奇怪,它同时包含了块变量名、返回值类型和参数类型。

在赋值运算符后面则是块的具体实现,此时不使用块的名称,但返回值和参数类型必须与块变量的定义相同,并且需要指定参数名称。

接下来,我们看一看如何使用这两个块,如下面的代码。

    #import <Foundation/Foundation.h>
    int numberFactory(int(^factory)(int, int), int num1, int num2)
    {
        return factory(num1, num2);
    }
    int main(int argc , const char * argv[])
    {
        @autoreleasepool {
            // 加法工厂
            int(^factory1)(int, int) = ^int (int num1, int num2) {
                return num1 + num2;
            };
            // 减法工厂
            int(^factory2)(int, int) = ^int (int num1, int num2) {
                return num1- num2;
            };
            // 调用工厂块
            int num1 = 10;
            int num2 = 6;
            NSLog(@"%i\n", numberFactory(factory1, num1, num2)); //16
            NSLog(@"%i\n", numberFactory(factory2, num1, num2)); //4
        }
        return 0;
    }

代码中,我们首先创建了函数numberFactory(),请注意它的第一个参数,定义为一个块类型,其格式为int类型的返回值,以及两个int类型的参数。第二和第三个参数则代入两个int类型的数值。

再看numberFactory()函数中的实现代码,我们可以看到,这个函数的功能是依靠块的执行来处理两个int数据(第二个参数和第三个参数)。

在main()函数中,调用了两次numberFactory()函数,不同之处在于它们的参数指定为不同的工厂块,这样也就实现了对两个数值的不同的操作,也就是分别执行了加法运算和减法运算。

这就是块的主要功能,我们可以使用相同的定义(块类型)实现不同的功能,也就是加什么块干什么活的含义了!

实际上,如果块只需要使用一次,我们并不需要单独定义,而是可以在使用的地方直接定义,如下面的代码就是通过numberFactory()函数进行乘法运算的。

    int result = numberFactory(
        ^int (int num1, int num2) {
            return num1 * num2;
        },
        num1, num2);
    NSLog(@"%i", result);  //60

在这里,只是显示了块的简单使用,在Foundation、Sprite Kit等框架中的开发资源中,可以看到很多关于块的使用,我们可以在实际应用中逐渐理解块的意义,并能够正确应用。

此外,如果需要在块中修改函数或方法内定义的变量,则必须在变量定义时使用__block标识,先看下面的代码。

    int main(int argc, const char * argv[])
    {
        @autoreleasepool {
            int num = 10;
            void (^addOne)(void) = ^void(void){
                num++; // 出错
            };
            addOne();
            NSLog(@"num = %i", num);
        }
        return 0;
    }

代码执行会出错,我们应该将变量num的定义修改为:

    __block int num = 10;

这样,代码就会正确执行,并显示“num = 11”。

此外,如果块没有返回值和参数,还可以更简单地定义,如前面的addOne块就可以写成:

    void (^addOne)() = ^{
        num++;
    };

关于块,最后需要说明的是,块应该定义在函数或方法内部。同时,块也可以作为参数来使用,这实际上就是实现访问者模式的重要方法,其基本原理是,在定义函数或方法时,并不能确定其中的一部分功能是如何实现的,但在调用这个函数或方法时,可能通过块参数传递具体的实现代码,我们会在后续的实践中看到在参数中传递块的实际应用。