Block用法总结

一、 block语法格式,如下

//return type (^BlockName)(list of arguments) = ^return type (list of arguments){do something;};

根据上面的英文应该就能理解各部分是什么了,接下来是一个简单的例子,可以帮助理解,这里引用了  Sindri的小巢(简书作者)的文章

原文链接:http://www.jianshu.com/p/29d70274374b

int (^sumOfNumbers) (int a, int b) = ^int(int a, int b) {

return a + b;

};

//Block的调用

int result = sumOfNumbers(100, 200); //无参时,也要带上小括号

NSLog(@"result = %d", result);

1.等号左侧声明一个名为sumOfNumbers的代码块,名称前用^符号表示后面的字符串是block的名称;

2.最左侧的int表示这个block的返回值;

3.括号中间表示这个block的参数列表,这里接收两个int类型的参数。

4. 而在等号右侧表示这个block的实现,其中返回值类型是可以省略的,编译器会根据上下文自动补充返回值类型。

5.使用^符号衔接着一个参数列表,使用括号包起来,告诉编译器这是一个block,然后使用大括号将block的代码封装起来。

二、 block的直接使用(匿名Block对象)

NSArray *array = @[@"3", @"1", @"2"];

array = [array sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {

return[obj1 compare: obj2];

}];

NSLog(@"array = %@", array);

其实理解起来也很简单,匿名的Block对象/是可以传递给/方法的Block对象/的,而不需要先赋值给变量。

让我们先看看匿名的整数。有三种方法可以将整数传递给方法:

方法1: 声明、赋值和使用完全分开

int i;

i = 5;

NSNumber *num = [NSNumber numberWithInt:i];

方法2: 在一行中声明赋值使用

int i = 5;

NSNumber *num = [NSNumber numberWithInt:i];

方法3:跳过变量声明步骤

NSNumber *num = [NSNumber numberWithInt:5];

如果采用第三种方法,就是匿名地传递一个整数。因为它没有名字,所以说它是匿名的。

而将Block对象传递给方法的办法和传递整数相同。分别用三行代码来声明Block对象,然后赋值,最后使用。但是匿名传递Block对象更加常用。

三、类型重定义(block重命名)

Block对象的语法可能会比较复杂。通过使用第11章介绍过的typedef关键字,可以将某个Block对象类型定义为一个新类型,以方便使用。需要注意的是,不能在方法的实现代码中使用typedef。也就是说,应该在实现文件的顶部,或者头文件内使用typedef。在main.m中,添加以下代码:

#import

typedef int (^SumBlock)(int a, int b);

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

{

这段代码中的typedef语句看上去与Block变量声明很像,但是,这里定义的是一个新的类型,而不是变量。跟在^字符后面的是类型名称。创建这个新类型后,就能简化相应Block对象的声明。

现在使用新的类型声明SumBlock:

注意,这里的Block类型只是声明了Block对象的实参和返回类型,并没有实现真正的Block对象。

SumBlock sumBlock = ^(int a, int b){

return a + b;

};

int result = sumBlock(100, 200);

NSLog(@"result = %d", result);

四、外部变量 —— block对栈区变量做只读拷贝操作,使用的是对变量的copy,而不是变量本身

Block对象通常会(在其代码中)使用外部创建的其他变量(基本类型的变量,或者是指向其他对象的指针)。这些外部创建的变量叫做外部变量(external variables)。当执行Block对象时,为了确保其下的外部变量能够始终存在,相应的Block对象会捕获(captured)这些变量。对基本类型的变量,捕获意味着程序会拷贝变量的值,并用Block对象内的局部变量保存。对指针类型的变量,Block对象会使用强引用。这意味着凡是Block对象用到的对象,都会被保留。所以在相应的Block对象被释放前,这些对象一定不会被释放(这也是Block对象和函数之间的差别,函数无法做到这点)。

使用外部变量

int a = 200, b = 100;

int (^minusBlock) (void) = ^(void){return a - b;};

NSLog(@"minus1 = %d", minusBlock());

//结果为100

a = 500, b = 200;

NSLog(@"minus2 = %d", minusBlock());

//结果还是100

只需要在定义外部变量的时候,使用 __block 或者 static 修饰。

__block int a = 200, b = 100;//或者 static

int (^minusBlock) (void) = ^(void){return a - b;};

NSLog(@"minus1 = %d", minusBlock());

//结果为100

a = 500, b = 200;

NSLog(@"minus2 = %d", minusBlock());

//结果是300

修改外部变量

在Block对象中,被捕获的变量是常数,程序无法修改变量所保存的值。如果需要在Block对象内修改某个外部变量,则可以在声明相应的外部变量时,在前面加上__block关键字,否则,如果在block内部改变外部变量的值程序就会报错

这样写是没问题的

void(^oneBlock)(void) = ^(void){

int n = 1;

n = 2;

NSLog(@"n = %d", n);

};

oneBlock();//结果是2

下面的写法会报错

int n = 1;

//定义Block

void(^oneBlock)(void) = ^(void){

n = 2;//报错!!!!!!

NSLog(@"n = %d", n);

};

oneBlock();

同样可以使用__block修饰外部变量来解决这个问题(使用static修饰或定义成实例变量的方法解决)

__block int n = 1;

//定义Block

void(^oneBlock)(void) = ^(void){

n = 2;

NSLog(@"n = %d", n);

};

oneBlock();//结果是2

在block对象中使用self

如果需要写一个使用self的Block对象,就必须要多做几步工作来避免循环引用问题。下面举个简单的例子

self.nameLabel.text = @"xiaoming";

void(^strBlock)(void) = ^(void){

NSString*name = self.nameLabel.text;

NSLog(@"%@", name);

};

strBlock();

Block中使用了self,这个Block对象会捕获self,Block必须等到所引用的对象全部释放后才会释放,然而,self又要到程序运行结束才释放,这样Block就不可能得到释放,就陷入强引用循环了。

为了打破这个强引用循环,可以先在Block对象外声明一个__block指针(ARC下使用__weak),然后将这个指针指向Block对象使用的self,最后在Block对象中使用这个新的指针:

__weak RootViewController *weakSelf = self; // 一个弱引用指针

void(^strBlock)(void) = ^(void){

NSString*name =weakSelf.nameLabel.text;

NSLog(@"%@", name);

};

如果想要了解的更深入,可以继续看完下面的内容,举例有所变化,但应该可以理解。

=======================================

现在这个Block对象对BNREMployee实例是弱引用,强引用循环打破了。

然而,由于是弱引用,所以self指向的对象在Block执行的时候可能会被释放。

为了避免这种情况的发生,可以在Block对象中创建一个对self的局部强引用:

__weak BNREmployee *weakSelf = self; // 弱引用

myBlock = ^{

BNREmployee *innerSelf = weakSelf; // 局部强引用

NSLog(@"Employee: %@", innerSelf);

};

通过创建innerSelf强引用,就可以在Block和BNREmployee实例中再次创建一个强引用循环。但是,由于innerSelf引用是针对Block内部的,所以只有在Block执行的时候它才会执行,而Block结束之后就会自动消失。

每次写Block对象的时候都引用self会是一个很好的练习。

在Block对象中无意使用self,而是使用了实例变量的情况

如果直接在Block对象中使用实例变量,那么block会捕获self,而不会捕获实例变量。这是实例变量的一个鲜为人知的特点。例如,以下这段代码直接存取一个实例变量:

__weak BNREmployee *weakSelf = self;

myBlock = ^{

BNREmployee *innerSelf = weakSelf; // 局部强引用

NSLog(@"Employee: %@", innerSelf);

NSLog(@"Employee ID: %d", _employeeID);

};

编译器是这么解读这段代码的:

__weak BNREmployee *weakSelf = self;

myBlock = ^{

BNREmployee *innerSelf = weakSelf; // 局部强引用

NSLog(@"Employee: %@", innerSelf);

NSLog(@"Employee ID: %d", self->_employeeID);

};

->语法看上去是不是很熟悉?这个语法实际是用来后去堆上的成员结构的。从最底层来说,对象实际就是结构。

由于编译器将_employeeID看成是self->_employeeID,self就被Block对象无意地捕获了。这样又会造成之前使用weakSelf和innerSelf避免的强引用循环。

怎样解决呢?不要直接存取实例变量。使用存取方法!

__weak BNREmployee *weakSelf = self;

myBlock = ^{

BNREmployee *innerSelf = weakSelf; // 局部强引用

NSLog(@"Employee: %@", innerSelf);

NSLog(@"Employee ID: %d", innerSelf.employeeID);

};

现在没有直接地使用self了,就不会造成无意识地强引用循环。

在这种情况下,重要的是要理解编译器是如何思考的,这样才能避免隐藏的强引用循环。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。