这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战
什么是Block(快速实现直接输入inlink)
block
是一种特殊的数据类型
Block的作用
- 用于保存一段代码,可以在恰当的时间取出来调用
- 功能类似于函数和方法
Block的格式
返回值(^block变量名)(形参列表) = ^( 形参列表){
};
复制代码
-
无参数无返回值
void (^sunBlock)(); sunBlock = ^{ NSLog(@"sunBlock"); }; sunBlock(); 复制代码
-
有参数无返回值
void(^sunBlock)(int,int); sunBlock = ^(int value1,int value2){ NSLog(@"%d",value1 + value2); }; sunBlock(10,20); 复制代码
-
有参数有返回值
int (^sunBlock)(int,int); sunBlock = ^(int value1,int value2){ return value1 + value2; }; NSLog(@"%d",sunBlock(10,20)); 复制代码
typedef 和Block
利用typedef
给block
起别名,和指向函数的指针一样,block
变量的名称就是别名
typedef int (^calculateBlock)(int,int);
int main(int argc, const char * argv[]) {
calculateBlock sumBlock = ^(int value1,int value2){
return value1 + value2;
};
NSLog(@"%d",sumBlock(20,10));
calculateBlock minusBlock = ^(int value1,int value2){
return value1 - value2;
};
NSLog(@"%d",minusBlock(20,10));
}
复制代码
Block的底层实现
-
原文件:
int main(int argc, const char * argv[]) { ^{ }; return 0; } 复制代码
-
通过clang命令将OC转为C++代码来查看一下Block底层实现,clang命令使用方式为终端使用cd定位到main.m文件所在文件夹,然后利用clang -rewrite-objc main.m将OC转为C++,成功后在main.m同目录下会生成一个main.cpp文件
struct __block_impl { void *isa; //isa,指向所属类的指针,也就是block的类型 int Flags; //flags,标志变量,在实现block的内部操作时会用到 int Reserved; //Reserved,保留变量 void *FuncPtr; //block执行时调用的函数指针 }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; //__main_block_impl_0的isa指针指向了_NSConcreteStackBlock impl.Flags = flags; impl.FuncPtr = fp; //从main函数中看, __main_block_impl_0的FuncPtr指向了函数__main_block_func_0 Desc = desc; //__main_block_impl_0的Desc也指向了定义__main_block_desc_0时就创建的__main_block_desc_0_DATA,其中纪录了block结构体大小等信息。 } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { } static struct __main_block_desc_0 { size_t reserved; //保留字段 size_t Block_size; //block大小(sizeof(struct __main_block_impl_0)) } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; //以上代码在定义__main_block_desc_0结构体时,同时创建了__main_block_desc_0_DATA,并给它赋值,以供在main函数中对__main_block_impl_0进行初始化。 int main(int argc, const char * argv[]) { ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); return 0; } 复制代码
1、
__block_impl
结构体,它包含了isa
指针(包含isa指针的皆为对象),也就是说block也是一个对象
2、__main_block_impl_0
结构体,可以看出是根据所在函数(main函数)以及出现序列(第0个)进行命名的,如果是全局的blcok
,就根据变量名和出现序列进行命名。
3、__main_block_impl_0
中包含了俩个成员变量和一个构造函数,成员变量分别是__block_impl
结构体和描述信息Desc
,之后在构造函数中初始化block
的类型信息和函数指针等信息。
4、__main_block_func_0
函数,其实对应的block
的函数体,该函数接受了一个__cself
参数,其实就是对应的block
本身
5、__main_block_desc_0
结构体,其中比较有价值的信息是block
的大小
6、main
函数对block
的创建,可以看出执行block
就是调用一个以block
自身为参数的函数,这个函数对应着block
的执行体。
Block的分类
NSConcreteGlobalBlock
全局的静态block
,不会访问任何外部变量。NSConcreteStackBlock
保存在栈中的block
,当函数返回时会被销毁。NSConcreteMallocBlock
保存在堆中的block
,当引用计数为 0 时会被销毁。
NSConcreteGlobalBlock 类型的 block 的实现
void (^testGlobalBlock)() = ^{
NSLog(@"hello block");
};
int main(int argc, const char * argv[]) {
testGlobalBlock();
return 0;
}
复制代码
testGlobalBlock的isa
指向了_NSConcreteGlobalBlock
,即在全局区域创建,block
变量存储在全局数据存储区
NSConcreteStackBlock 类型的 block 的实现
int main(int argc, const char * argv[]) {
void (^testStackBlock)() = ^{
NSLog(@"hello block");
};
testStackBlock();
return 0;
}
复制代码
testStackBlock的isa
指向了_NSConcreteStackBlock
,即在栈区创建。
NSConcreteMallocBlock 类型的 block 的实现
int main(int argc, const char * argv[]) {
void (^testStackBlock)() = [^{
NSLog(@"hello block");
} copy];
testStackBlock();
return 0;
}
复制代码
NSConcreteMallocBlock
类型的 block
通常不会在源码中直接出现,其需要由_NSConcreteStackBlock
类型的block
拷贝而来(也就是说block
需要执行copy
之后才能存放到堆中)。
其内部通过函数memmove将栈中的block
的内容拷贝到了堆中,并使isa
指向了_NSConcreteMallocBlock
。
block
主要的一些学问就出在栈中block
向堆中block
的转移过程中了。
Block的应用
Block中访问局部变量
- 在
Block
中访问局部变量int main(int argc, const char * argv[]) { int testNum = 10; void(^testNumBlock)() = ^{ NSLog(@"%d",testNum); }; testNumBlock(); return 0; } 打印结果:10 复制代码
- 在声明
Block
之后,调用Block
之前对局部变量进行修改,在调用Block
时局部变量值是修改之前的旧值int main(int argc, const char * argv[]) { int testNum = 10; void(^testNumBlock)() = ^{ NSLog(@"%d",testNum); }; testNum = 20; testNumBlock(); return 0; } 打印结果:10 复制代码
- 在
Block
中不可以直接修改局部变量int main(int argc, const char * argv[]) { int testNum = 10; void(^testNumBlock)() = ^{ testNum = 20; //报错 NSLog(@"%d",testNum); }; testNumBlock(); return 0; } 复制代码
Block内访问__block修饰的局部变量
- 在局部变量前使用下划线下划线
block
修饰,在声明Block
之后、调用Block
之前对局部变量进行修改,在调用Block
时局部变量值是修改之后的新值__block int testNum = 10; void(^testNumBlock)() = ^{ NSLog(@"%d",testNum); }; testNum = 20; testNumBlock(); 打印结果:20 复制代码
- 在局部变量前使用下划线下划线
block
修饰,在Block
中可以直接修改局部变量int main(int argc, const char * argv[]) { __block int testNum = 10; void(^testNumBlock)() = ^{ testNum = 20; NSLog(@"%d",testNum); }; testNumBlock(); return 0; } 打印结果:20 复制代码
Block内访问全局变量
- 在
Block
中可以访问全局变量int testNum = 10; int main(int argc, const char * argv[]) { void(^testNumBlock)() = ^{ NSLog(@"%d",testNum); }; testNumBlock(); return 0; } 打印结果:10 复制代码
- 在声明
Block
之后、调用Block
之前对全局变量进行修改,在调用Block
时全局变量值是修改之后的新值int testNum = 10; int main(int argc, const char * argv[]) { void(^testNumBlock)() = ^{ NSLog(@"%d",testNum); }; testNum = 20; testNumBlock(); return 0; } 打印结果:20 复制代码
- 在
Block
中可以直接修改全局变量int testNum = 10; int main(int argc, const char * argv[]) { void(^testNumBlock)() = ^{ testNum = 20; NSLog(@"%d",testNum); }; testNumBlock(); return 0; } 打印结果:20 复制代码
Block内访问静态变量
- 在
Block
中可以访问静态变量int main(int argc, const char * argv[]) { static int testNum = 10; void(^testNumBlock)() = ^{ NSLog(@"%d",testNum); }; testNumBlock(); return 0; } 打印结果:10 复制代码
- 在声明
Block
之后、调用Block
之前对静态变量进行修改,在调用Block
时静态变量值是修改之后的新值int main(int argc, const char * argv[]) { static int testNum = 10; void(^testNumBlock)() = ^{ NSLog(@"%d",testNum); }; testNum = 20; testNumBlock(); return 0; } 打印结果:20 复制代码
- 在
Block
中可以直接修改静态变量int main(int argc, const char * argv[]) { static int testNum = 10; void(^testNumBlock)() = ^{ testNum = 20; NSLog(@"%d",testNum); }; testNumBlock(); return 0; } 打印结果:20 复制代码
Block作为参数传递
typedef void(^TestBlock)();
NSMutableArray *array;
void test(){
int a = 10;
TestBlock blcok = ^{
NSLog(@"%d",a);
};
[array addObject:blcok];
NSLog(@"%@",blcok);
}
int main(int argc, const char * argv[]) {
array = [[NSMutableArray alloc]init];
test();
TestBlock blockk = [array lastObject];
blockk();
NSLog(@"%@",blockk);
return 0;
}
结果:
在ARC下:
test2[2423:124143] <__NSMallocBlock__: 0x1004037f0>
test2[2423:124143] 10
test2[2423:124143] <__NSMallocBlock__: 0x1004037f0>
在非ARC下:
程序崩溃
test2[2449:125851] <__NSStackBlock__: 0x7fff5fbff6f8>
复制代码
1、在非ARC下,TestBlock的isa
指向__NSStackBlock__
,当函数退出后,相应的堆被销毁,block
也就不存在了,在经过copy
或retain
之后,对象的类型从__NSStackBlock__
变为了__NSMallocBlock__
,在函数结束后依然可以访问,在非ARC环境下,copy
或retain
了block
后一定要在使用后release
,不然会有内存泄露,而且泄露点是在系统级,在Instruments里跟不到问题触发点,比较上火。
2、ARC情况下,系统会将捕获了外部变量的block
进行了copy
。所以返回类型为__NSMallocBlock__
,在函数结束后依然可以访问
如果把blcok
中的代码不再访问变量:
TestBlock blcok = ^{
NSLog(@"demo");
};
结果:
ARC和非ARC得结果一致
test2[2484:128052] <__NSGlobalBlock__: 0x100005290>
test2[2484:128052] demo
test2[2484:128052] <__NSGlobalBlock__: 0x100005290>
复制代码
Block作为返回值
- 非ARC中
- (testBlcok) myTestBlock {
__block int val = 10;
return ^{
NSLog(@"val = %d", val);
};
}
结果:Xcode就会提示报错Returning block that lives on the local stack
复制代码
在向外传递block
的时候一定也要做到,传给外面一个在堆上的,autorelease
的对象。
- (testBlcok) myTestBlock {
__block int val = 10;
return [[^{
NSLog(@"val = %d", val);
} copy] autorelease];
}
复制代码
- ARC中
- (testBlcok) myTestBlock {
__block int val = 10;
return ^{
NSLog(@"val = %d", val);
};
}
结果:正常
复制代码
在ARC环境下,当block
作为参数返回的时候,block
也会自动被移到堆上。
Block作为属性
ARC和非ARC得声明一样
@property (strong, nonatomic) TestBlock *strongBlock;
@property (copy, nonatomic) TestBlock *copyBlock;
复制代码
Block在MRC及ARC下的内存管理
Block在MRC下的内存管理
- 默认情况下,
Block
的内存存储在栈中,不需要开发人员对其进行内存管理- (void)viewDidLoad { [super viewDidLoad]; void(^testBlock)() = ^{ NSLog(@"------"); }; testBlock(); } 结果:当testBlock变量出了作用域,testBlock的内存会被自动释放 复制代码
- 在
Block
的内存存储在栈中时,如果在Block
中引用了外面的对象,不会对所引用的对象进行任何操作- (void)viewDidLoad { [super viewDidLoad]; Student *stu = [[Student alloc]init]; void(^testBlock)() = ^{ NSLog(@"%@",stu); }; testBlock(); [stu release]; } 结果:Student可以正常释放 复制代码
- 如果对
Block
进行一次copy
操作,那么Block
的内存会被移动到堆中,这时需要对其进行release
操作来管理内存- (void)viewDidLoad { [super viewDidLoad]; void(^testBlock)() = ^{ NSLog(@"testBlock"); }; testBlock(); Block_copy(testBlock); Block_release(testBlock); } 结果:Block正常释放 复制代码
- 如果对
Block
进行一次copy
操作,那么Block
的内存会被移动到堆中,在Block
的内存存储在堆中时,如果在Block
中引用了外面的对象,会对所引用的对象进行一次retain
操作,即使在Block
自身调用了release
操作之后,Block
也不会对所引用的对象进行一次release
操作,这时会造成内存泄漏- (void)viewDidLoad { [super viewDidLoad]; Student *stu = [[Student alloc]init]; void(^testBlock)() = ^{ NSLog(@"%@",stu); }; testBlock(); Block_copy(testBlock); Block_release(testBlock); [stu release]; } 结果:Student无法正常被释放,因为其在Block中被进行了一次retain操作 复制代码
- 如果对
Block
进行一次copy
操作,那么Block
的内存会被移动到堆中,在Block
的内存存储在堆中时,如果在Block
中引用了外面的对象,会对所引用的对象进行一次retain
操作,为了不对所引用的对象进行一次retain
操作,可以在对象的前面使用__block
来修饰- (void)viewDidLoad { [super viewDidLoad]; __block Student *stu = [[Student alloc]init]; void(^testBlock)() = ^{ NSLog(@"%@",stu); }; testBlock(); Block_copy(testBlock); Block_release(testBlock); [stu release]; } 结果:Student可以正常释放 复制代码
- 如果对象内部有一个
Block
属性,而在Block
内部又访问了该对象,那么会造成循环引用 - 第一种情况
@interface Student : NSObject @property (nonatomic,copy) void(^testBlock)(); @end ---------------------------------------------- @implementation Student -(void)dealloc{ NSLog(@"%s",__func__); Block_release(_testBlock); [super dealloc]; } @end ---------------------------------------------- -(void)viewDidLoad { [super viewDidLoad]; Student *stu = [[Student alloc]init]; stu.testBlock = ^{ NSLog(@"%@",stu); }; stu.testBlock(); [stu release]; } 结果:因为testBlock作为Student的属性,采用copy修饰符修饰(这样才能保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Student对象进行一次retain操作,导致循环引用无法释放 复制代码
- 第二种情况
@interface Student : NSObject @property (nonatomic,copy) void(^testBlock)(); -(void)resetBlock; @end ---------------------------------------------- @implementation Student -(void)resetBlock{ self.testBlock = ^{ NSLog(@"%@",self); }; } -(void)dealloc{ NSLog(@"%s",__func__); Block_release(_testBlock); [super dealloc]; } @end ---------------------------------------------- -(void)viewDidLoad { [super viewDidLoad]; Student *stu = [[Student alloc]init]; [stu resetBlock]; [stu release]; } 结果:Student对象在这里无法正常释放,虽然表面看起来一个alloc对应一个release符合内存管理规则,但是实际在resetBlock方法实现中,Block内部对self进行了一次retain操作,导致循环引用无法释放 复制代码
- 如果对象内部有一个
Block
属性,而在Block
内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是在对象的前面使用下划线下划线block
来修饰,以避免Block
对对象进行retain
操作- 第一种情况
@interface Student : NSObject @property (nonatomic,copy) void(^testBlock)(); @end ---------------------------------------------- @implementation Student -(void)dealloc{ NSLog(@"%s",__func__); Block_release(_testBlock); [super dealloc]; } @end ---------------------------------------------- - (void)viewDidLoad { [super viewDidLoad]; __block Student *stu = [[Student alloc]init]; stu.testBlock = ^{ NSLog(@"%@",stu); }; stu.testBlock(); [stu release]; } 结果:Student可以正常释放 复制代码
- 第二种情况
@interface Student : NSObject @property (nonatomic,copy) void(^testBlock)(); -(void)resetBlock; @end ---------------------------------------------- @implementation Student -(void)resetBlock{ // 这里为了通用一点,可以使用__block typeof(self) stu = self; __block Student *stu = self; self.testBlock = ^{ NSLog(@"%@",stu); }; } -(void)dealloc{ NSLog(@"%s",__func__); Block_release(_testBlock); [super dealloc]; } @end ---------------------------------------------- - (void)viewDidLoad { [super viewDidLoad]; Student *stu = [[Student alloc]init]; [stu resetBlock]; [stu release]; } 结果:Student可以正常释放 复制代码
- 第一种情况
Block在ARC下的内存管理
- 在ARC默认情况下,
Block
的内存存储在堆中,ARC会自动进行内存管理,程序员只需要避免循环引用即可- (void)viewDidLoad { [super viewDidLoad]; void(^testBlock)() = ^{ NSLog(@"testBlock"); }; testBlock(); } 结果:当Block变量出了作用域,Block的内存会被自动释放 复制代码
- 在
Block
的内存存储在堆中时,如果在Block
中引用了外面的对象,会对所引用的对象进行强引用,但是在Block
被释放时会自动去掉对该对象的强引用,所以不会造成内存泄漏- (void)viewDidLoad { [super viewDidLoad]; Student *stu = [[Student alloc]init]; void(^testBlock)() = ^{ NSLog(@"%@",stu); }; testBlock(); } 结果:Student可以正常释放 复制代码
- 如果对象内部有一个
Block
属性,而在Block
内部又访问了该对象,那么会造成循环引用- 第一种情况
@interface Student : NSObject @property (nonatomic,copy) void(^testBlock)(); @end ---------------------------------------------- @implementation Student -(void)dealloc{ NSLog(@"%s",__func__); } @end ---------------------------------------------- -(void)viewDidLoad { [super viewDidLoad]; Student *stu = [[Student alloc]init]; stu.testBlock = ^{ NSLog(@"%@",stu); }; stu.testBlock(); } 结果:因为testBlock作为Student的属性,采用copy修饰符修饰(这样才能保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Person对象进行一次强引用,导致循环引用无法释放 复制代码
- 第二种情况
@interface Student : NSObject @property (nonatomic,copy) void(^testBlock)(); - (void)resetBlock; @end ---------------------------------------------- @implementation Student - (void)resetBlock{ self.testBlock = ^{ NSLog(@"------%@", self); }; } -(void)dealloc{ NSLog(@"%s",__func__); } @end ---------------------------------------------- - (void)viewDidLoad { [super viewDidLoad]; Student *stu = [[Student alloc]init]; [stu resetBlock]; } 结果:Student对象在这里无法正常释放,在testBlock方法实现中,Block内部对self进行了一次强引用,导致循环引用无法释放 复制代码
- 如果对象内部有一个
Block
属性,而在Block
内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是使用一个弱引用的指针指向该对象,然后在Block
内部使用该弱引用指针来进行操作,这样避免了Block
对对象进行强引用 - 第一种情况
@interface Student : NSObject @property (nonatomic,copy) void(^testBlock)(); @end ---------------------------------------------- @implementation Student -(void)dealloc{ NSLog(@"%s",__func__); } @end ---------------------------------------------- - (void)viewDidLoad { [super viewDidLoad]; Student *stu = [[Student alloc]init]; __weak typeof(stu) weakS = stu; stu.testBlock = ^{ NSLog(@"------%@", weakS); }; stu.testBlock(); // Student对象在这里可以正常被释放 } 复制代码
- 第二种情况
@interface Student : NSObject @property (nonatomic,copy) void(^testBlock)(); -(void)resetBlock; @end ---------------------------------------------- @implementation Student -(void)resetBlock{ //这里为了通用一点,可以使用__weak typeof(self) weakP = self; __weak Student *stu = self; self.testBlock = ^{ NSLog(@"------%@", self); }; } void)dealloc{ NSLog(@"%s",__func__); } @end ---------------------------------------------- -(void)viewDidLoad { [super viewDidLoad]; Student *stu = [[Student alloc]init]; [stu resetBlock]; } 结果:Student可以正常释放 复制代码
注: 关于下划线下划线block
关键字在MRC和ARC下的不同
__block
在MRC下有两个作用
- 允许在
Block
中访问和修改局部变量 - 禁止
Block
对所引用的对象进行隐式retain
操作
__block
在ARC下只有一个作用
- 允许在
Block
中访问和修改局部变量