一、 block语法格式,如下
//return type (^BlockName)(list of arguments) = ^return type (list of arguments){do something;};
根据上面的英文应该就能理解各部分是什么了,接下来是一个简单的例子,可以帮助理解,这里引用了 Sindri的小巢(简书作者)的文章
原文链接://www.greatytc.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了,就不会造成无意识地强引用循环。
在这种情况下,重要的是要理解编译器是如何思考的,这样才能避免隐藏的强引用循环。