您好,欢迎访问代码之道!登录后台查看权限
  • 欢迎大神光临
  • 有朋自远方来 不亦悦乎

Objective-C教程之Blocks

ObjC教程 老刘 2018-08-27 997 次浏览 0个评论

Blocks

Blocks是Objective-C/Objective-C++在C/C++语言基础上添加的语言特性。包括两部分,Blocks字面量和Blocks变量。Blocks字面量看着就是一段可执行代码块, 运行时创建Objective-C对象, 类似匿名的函数,但是可以穿插定义在其他函数内,参数列表中,而且可以捕获周边的变量。Blocks变量好比函数指针,用来引用代码块,但是因为Blocks 是对象,除了存储可以执行代码地址,显然还要存储其他更多信息。Blocks具有函数和Objective-C对象双重特征。

Hello Blocks

Blocks和函数一样,拥有一段可执行代码,最简单的Blocks字面量例子可以定义如下

^() {
    NSLog(@"Hello Blocks!");
}

下面是一个完整的小程序,在主函数中定义了一个Blocks。

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        ^() {
            NSLog(@"Hello Blocks");
        };
    }
    return 0;
}

上面程序运行不会有任何输出,定义一个Blocks好比定义了一个函数,没有人call她,她是不会执行的。

她是匿名的,我们可以直接在定义她的时候就直接运行,否则后面我们没有办法执行她。

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        ^() {
            NSLog(@"Hello Blocks");
        }();
    }
    return 0;
}

/*
程序输出:
2018-08-27 10:43:30.984 demo1[1083:36918] Hello Blocks
*/

Blocks变量

Blocks字面量是匿名的,就好比一个@""字面量,我们要引用她,就需把她赋值给变量才行,然后通过变量使用她。除非我们像上面例子那样, 定义一个Blocks,马上就执行,并且只是用一次。

下面这个小程序将声明一个Blocks变量sayHello,然后将一段Block赋值给她,之后像调用函数一样,调用了sayHello两次。

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        void (^sayHello)();
        sayHello = ^() {
            NSLog(@"Hello Blocks");
        };
        sayHello();
        sayHello();
    }
    return 0;
}

/*
程序输出:
2018-08-27 10:56:35.129 demo2[1148:40850] Hello Blocks
2018-08-27 10:56:35.129 demo2[1148:40850] Hello Blocks
*/

如果逻辑允许,也可以直接在声明一个Blocks变量的时候就将她初始化。就好比我们经常定义一个NSString *变量,有时候是在程序运行到某处的时候 才给她赋值,有时候在声明的同时就将其初始化了。

下面是一个声明了Blocks变量并直接初始化她的例子。

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        void (^sayHello)() = ^() {
            NSLog(@"Hello Blocks");
        };
        sayHello();
        sayHello();
    }
    return 0;
}

/*
程序输出:
2018-08-27 10:56:35.129 demo3[1148:40850] Hello Blocks
2018-08-27 10:56:35.129 demo3[1148:40850] Hello Blocks
*/

Blocks的返回值和参数列表

总结一下Blocks变量声明语法和Blocks字面量语法。

Blocks变量声明语法如下

(返回值类型)(^Blocks变量名)(参数列表)

Blocks字面量语法如下

^返回值类型(参数列表) {
 // 实现代码
}

因为Blocks默认返回id类型,因此很多时候返回值类型可以省略不写。

下面是一个加法的例子,定义的的Blocks拥有两个int类型参数和int类型返回值。

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        int (^add)(int, int) = ^int(int a, int b) {
            return a + b;
        };
        NSLog(@"result=%d", add(1, 2));
    }
    return 0;
}

/*
程序输出:
2018-08-27 11:27:03.100 demo4[1320:50223] result=3
*/

捕获变量

Blocks可以通过参数列表和返回值和外部进行数据交换,显然这还是函数的老掉牙的方式了,下面我们看看Blocks特有的数据交换方式:变量捕获。 Blocks可以得到外围作用域变量的一份拷贝,也可以通过对变量添加__block作用范围说明关键字,声明该变量和Blocks共享存储空间,从而让Blocks不但能访问变量的值,而且支持直接赋值给变量。

下面依然是一个加法的例子,不过Blocks是通过变量捕获得到加数和被加数的,并对__block的外围变量直接赋值进行结果的输出。

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        __block int result;
        int a = 1, b = 2;
        void (^add)() = ^() {
            result =  a + b;
        };
        add();
        NSLog(@"result=%d", result);
    }
    return 0;
}

/*
程序输出:
2018-08-27 11:27:03.100 demo5[1320:50223] result=3
*/

再次强调,如果变量没有__block描述,Blocks得到只是变量的一份拷贝,也就是在定义Blocks之后,给外围变量赋值将不会影响block得到的值。 因为在定义Blocks的地方已经执行了拷贝操作。

下面这个例子说明了这个问题,即使执行add()之前便已经修改了a的值,但是计算结果还是3,因为 block内部得到的a还是block定义前面的的值,也就是1。

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        __block int result;
        int a = 1, b = 2;
        void (^add)() = ^() {
            result =  a + b;
        };
        a = 3;
        add();
        NSLog(@"result=%d", result);
    }
    return 0;
}

/*
程序输出:
2018-08-27 13:47:43.447 demo6[1724:75394] result=3
*/

当然如果变量是C指针或者Objective-C类对象指针(当然也是指针),即使得到仅仅是指针的一份拷贝,但是依然能对指针指向的内容进行修改。

下面是一个例子,block内的result_p和a_p实际上是外部result_p和a_p的一份指针拷贝, 但是block可以通过拷贝的result_p修改result的值,并且通过拷贝的a_p访问外部a存储的值,所以外部a被赋值,会影响计算结果。

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        int result;
        int *result_p = &result;
        int a = 1, b = 2;
        int *a_p = &a;
        void (^add)() = ^() {
            *result_p =  *a_p + b;
        };
        a = 3;
        add();
        NSLog(@"result=%d", result);
    }
    return 0;
}

/*
程序输出:
2018-08-27 14:46:27.702 demo7[1930:90966] result=5
*/

另外Blocks是不可以捕获C语言数组的,比如下面的block将无法通过编译

int a[] = {1, 2, 3};
^() {
    NSLog(@"a[0]=%d", a[0]);
}();

编译器一般会给出下面抱怨

objectivec
#import <Foundation/Foundation.h>


int main(int argc, const char *argv[]) {
    @autoreleasepool {
        int a[] = {1, 2, 3};
        int *a_p = a;
        ^() {
            NSLog(@"a[0]=%d", a_p[0]);
        }();
    }
    return 0;
}
/*
程序输出:
2018-08-27 17:43:57.033 demo10[3056:142058] a[0]=1
*/

Blocks作为函数参数

Blocks可以作为函数参数,可以充当delegate/回调函数的角色。

下面是一个例子,foreachIntArray决定如何从头到尾访问数组,而如何处理每个数组的元素则是由具体的block决定。 比如下面第一次foreachIntArray调用,是对每个元素进行了打印,而第二次调用是对所有元素进行求和,这如果换成函数指针实现foreachIntArray, 那么我们必须提前准备好两个函数。

#import <Foundation/Foundation.h>


void foreachIntArray(int *array, int size, void (^handler)(int *array, int index)) {
    for(int i=0; i<size; i++) handler(array, i);
}

int main(int argc, const char *argv[]) {
    @autoreleasepool {
       int a[3] = {1, 2, 3};
       foreachIntArray(a, 3, ^(int *array, int index) {
            NSLog(@"%d-->%d", index, array[index]);
       });
       __block int sum = 0;
       foreachIntArray(a, 3, ^(int *array, int index) {
            sum += array[index];
       });
        NSLog(@"sum=%d", sum);
    }
    return 0;
}

/*
程序输出:
2018-08-27 16:11:14.862 demo8[2695:116638] 0-->1
2018-08-27 16:11:14.862 demo8[2695:116638] 1-->2
2018-08-27 16:11:14.862 demo8[2695:116638] 2-->3
2018-08-27 16:11:14.862 demo8[2695:116638] sum=6
*/

Blocks作为函数返回值

函数返回值同样可以是Blocks类型。语法如下

block返回值类型 (^函数名(函数参数列表))(block参数列表) {
    // 函数体
}

看着语法有点陌生,我们还是看一个例子,该例子会根据getCalc的参数type的值不同而返回不同逻辑的block。

#import <Foundation/Foundation.h>


int (^getCalc(int type))(int, int) {
    if(type == 0) {
        return ^int(int a, int b) {return a + b;};
    } else if(type == 1){
        return ^int(int a, int b) {return a - b;};
    } else if(type == 2){
        return ^int(int a, int b) {return a * b;};
    } else {
        return ^int(int a, int b) {return a / b;};
    }
}

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        for(int i=0; i<4; i++)
            NSLog(@"result=%d", getCalc(i)(1, 2));
    }
    return 0;
}

/*
程序输出:
2018-08-27 17:15:19.742 demo9[2980:134408] result=3
2018-08-27 17:15:19.742 demo9[2980:134408] result=-1
2018-08-27 17:15:19.742 demo9[2980:134408] result=2
2018-08-27 17:15:19.742 demo9[2980:134408] result=0
*/

Blocks类型typedef

Blocks类型语法不容易记住,如果我们喜欢,可以对Blocks类型typedef一个好听的名称。

定义Blocks类型语法如下

typedef 返回值类型 (^Blocks类型名称)(参数列表);

下面我们改造一下上一个例子,看着会顺眼很多。

#import <Foundation/Foundation.h>

typedef int (^calc_t)(int, int);

calc_t getCalc(int type) {
    if(type == 0) {
        return ^int(int a, int b) {return a + b;};
    } else if(type == 1){
        return ^int(int a, int b) {return a - b;};
    } else if(type == 2){
        return ^int(int a, int b) {return a * b;};
    } else {
        return ^int(int a, int b) {return a / b;};
    }
}

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        for(int i=0; i<4; i++)
        NSLog(@"result=%d", getCalc(i)(1, 2));
    }
    return 0;
}

/*
程序输出:
2018-08-27 18:12:08.774 demo9.1[3103:149915] result=3
2018-08-27 18:12:08.775 demo9.1[3103:149915] result=-1
2018-08-27 18:12:08.775 demo9.1[3103:149915] result=2
2018-08-27 18:12:08.775 demo9.1[3103:149915] result=0
*/

已有 997 位网友参与,快来吐槽:

发表评论