Block:带有自动变量的匿名函数,它是C语言的拓展功能,之因此是扩展,是由于C语言不容许存在这样的匿名函数编程
匿名函数是指不带函数名称的函数函数
这是由于Block拥有捕获外部变量的功能,在Block中访问一个外部的局部变量,Block会持有它的临时状态,自动捕获变量值,外部局部变量的变化不会影响它的状态ui
int val = 10;
void (^blk)(void) = ^{
printf("val=%d\n", val);
};
val = 2;
blk(); // 这里输出的值是10,而不是2,由于block在实现时就会对它所在方法中定义的栈变量进行一次只读拷贝
复制代码
__block
修饰__block int val = 10;
void (^blk)(void) = ^{
printf("val=%d\n", val);
};
val = 2;
blk(); // 这里输出的值是2
复制代码
UIViewController 须要监听TableView中Cell的某个按钮的点击事件,既能够经过Delegate回调,也能够利用Block回调 Block回调的思路: 声明一个Block属性,注意这里要用copy。 利用Block属性进行回调atom
以
[UIView animateWithDuration:animations:]
为例,animations是一个block对象,利用block实现调用者与UIView之间的数据传递spa
链式编程思想:将block做为方法的返回值,且返回值的类型为调用者自己,并将该方法以setter的形式返回,从而实现连续调用设计
// CaculateMaker.h
// ChainBlockTestApp
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CaculateMaker : NSObject
@property (nonatomic, assign) CGFloat result;
/* * 返回类型 CaculateMaker * 传入参数 CGFloat num */
- (CaculateMaker *(^)(CGFloat num))add;
@end
// CaculateMaker.m
// ChainBlockTestApp
#import "CaculateMaker.h"
@implementation CaculateMaker
- (CaculateMaker *(^)(CGFloat num))add;{
return ^CaculateMaker *(CGFloat num){
_result += num;
return self;
};
}
@end
// 使用
CaculateMaker *maker = [[CaculateMaker alloc] init];
maker.add(20).add(30);
复制代码
由于在MRC状况下若是Block属性不使用copy修饰,在使用中会出现崩溃,在ARC状况下,Block属性使用strong修饰会被默认进行copy,因此ARC状况下,Block属性能够使用strong或copy修饰,否则会出现崩溃。
**
为什么会有这种现象出现?指针
Block在内存中的位置分为三种类型:code
这三种类型对应如下三种状况:对象
当Block类型是__NSStackBlock__时,一旦超出了变量做用域,栈上的Block以及__block变量就会被销毁,从而致使调用Block回调时崩溃。所以,Block属性须要用copy修饰来避免这种状况。生命周期
- (void)click:(id)sender {
TestClass *test = [[TestClass alloc] init];
__block int a = 1;
// 弱引用,block类型是__NSStackBlock__ 当TestClass执行回调时必崩 EXC_BAD_ACCESS
test.weakBlock = ^() {
NSLog(@"ok");
a = 2;
};
// block类型是__NSStackBlock__ 当TestClass执行回调时必崩 EXC_BAD_ACCESS
test.assignBlock = ^() {
NSLog(@"ok");
a = 3;
};
// block类型是__MallocBlock__ 正常执行
test.copyBlock = ^() {
NSLog(@"ok");
a = 4;
};
// block类型是__MallocBlock__ 正常执行
test.strongBlock = ^() {
NSLog(@"ok");
a = 5;
};
NSLog(@"copy property: %@", test.copyBlock);
NSLog(@"assign property: %@", test.assignBlock);
NSLog(@"weak property: %@", test.weakBlock);
NSLog(@"strong property: %@", test.strongBlock);
[test start];
}
复制代码
首先,须要弄明白一个概念,static声明的静态局部变量是能够在block内进行修改的,为什么会有这种区别呢?
由于,静态局部变量存在于应用程序的整个生命周期,而非静态局部变量仅存在于一个局部上下文中,绝大多数状况下,block都是延后执行的,这就有可能出现非静态局部变量被回收的状况,为了不这个问题,苹果在设计block时,block中非静态局部变量是值传递,这也解释了为什么block会持有变量的临时状态,后续再修改,block中的变量值也再也不改变。
加上__block修饰的局部变量,被block捕获时,就再也不是传递局部变量的值了,而是变成了一个结构体实例。好比:
定义一个 __block int a = 10;
会变成 __Block_byref_a_0 *a;
__Block_byref_a_0
的结构体以下所示:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding; // forwarding指针
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
int a; // 原变量同类型变量
};
复制代码
结构体中有一个forwarding指针,此指针指向转换后变量自己,结构体中也有一个原变量同样类型的变量。此后代码中涉及到原变量的地方,都会转换成新变量->forwarding->原变量同类型变量
若是在block中直接修改变量的值,实质的过程是新变量->__forwarding->原变量同类型变量,最终修改的实际上是结构体中原变量同类型变量,很明显这个结构体内的变量已经不属于block的外部变量了,因此能在block内修改。
这个新变量也是非静态局部变量,因此若是没有copy,block执行时,新变量有可能已经被栈回收
总结:
block修饰的变量转换成告终构体,结构体内有一个forwarding指针和一个与原变量相同类型的成员变量,forwarding指针指向结构体内的成员变量。不管在block内外,都是经过forwarding来访问的。
咱们先看一段block致使循环引用的代码:
TestClass *test = [[TestClass alloc] init];
test.copyBlock = ^() {
NSLog(@"ok: %d", test.result);
};
复制代码
当咱们写完这段代码后,Xcode就会提醒咱们,这段代码存在循环引用,事实上也确实存在循环引用。接下来咱们就来分析一下为何会产生循环引用。
test的属性block强引用了SecondViewController中的block,SecondViewController中的block又强引用了test的属性result,从而致使了循环引用。
并不是全部的block都存在循环引用,下面列举一些常见的block使用的示例:
// self-->requestModel-->block-->self
[self.requestModel requestData:^(NSData *data) {
self.name = @"leafly";
}];
// 虽然存在引用环,可是经过主动释放requestModel打破了循环
[self.requestModel requestData:^(NSData *data) {
self.name = @"leafly";
self.requestModel = nil;
}];
// t-->block-->self 不存在循环引用
Test *t = [[Test alloc] init];
[t requestData:^(NSData *data) {
self.name = @"leafly";
}];
// AFNetworking-->block-->self 不存在循环引用
[AFNetworking requestData:^(NSData *data) {
self.name = @"lealfy";
}];
复制代码