目录
- 简介
----声明/定义一个Block
变量
----使用typedef
定义一个Block
- Block基本使用
----Block
变量的赋值格式
----Block
作为属性
----Block
作为OC函数参数
----Block
作为OC函数参数拓展
----当Block
作为返回值- Block在C/C++(底层)的数据结构
----观察Block
的class等信息
----观察__main_block_func_0
结构体
----观察Block
代码具体执行区- Block的类型
----区分创建的Block
是那种类型?
----三种类型内存分布
----为什么Block
要调用copy把栈Block复制到堆区呢?
----在ARC
环境下,编译器会根据情况自动将栈上的Block
复制到堆上的几种情况
----最后总结
简介
Block
是OC
中一种比较特殊的对象,和其他OC对象一样使用,只是比较特殊。
Block
用来封装一段函数/代码块
和函数的调用环境,等到需要的时候再去调用。
封装:Block
内部会把Block
的参数,返回值,执行体封装成一个函数,并且存储该函数的内存地址。
调用环境:Block
内部会捕获变量,并且存储这些捕获的变量。
Block定义
-
声明/定义一个Block变量
返回值类型 (^方法名)(参数列表) = ^(参数列表){ 代码块 };
void (^myBlock1)(void); //无返回值,无参数
void (^myBlock2)(NSObject, int); //无返回值,有参数
void(^myBlock3)(NSString *, NSString *); // 形参变量名称可以省略,只留有变量类型即可
NSString* (^myBlock4)(NSString *x,NSString *y); //有返回值和参数,并且在参数类型后面加入了参数名(仅为可读性)
-
使用typedef定义一个
Block
在实际使用Block的过程中,我们可能需要重复第声明多个相同返回值相同参数列表的Block变量,如果总是重复地编写一长串代码来声明变量会非常繁琐,所以我们可以使用typedef来定义Block类型。
typedef 返回类型 (^方法名)(参数列表)
typedef void(^ calculate)(int,int);
calculate minusBlock = ^(int value1,int value1){
NSLog(@"I am a block");
return value1 - value2;
};
calculate sumBlock = ^(int value1,int value1){
NSLog(@"I am a block");
return value1 + value2;
};
Block的基本使用
-
Block变量的赋值格式为:
Block变量 = ^(参数列表){函数体};
myBlock4 = ^(NSString *x, NSString *y){
NSLog(@“x:%, y:%@“, x, y);
};
其中右边的返回值类型和参数类型为空的时候可以省略不写
void (^block)(void) = ^{};
备注:Block变量的赋值格式可以是:
Block变量 = ^返回值类型(参数列表){函数体};
不过通常情况下都将返回值类型省略
,因为编译器可以从存储代码块的变量中确定返回值的类型。
// Block最简单的形式
void (^ablock)(void); // 定义一个Block
ablock = ^(void){NSLog(@"I am a block");}; // 包一个代码块
ablock(); // 在我们想要的时候进行调用
// 带参数的Block
void (^ sumBlock)(int,int) = ^(int value1,int value1){
NSLog(@"两数之和为:%d",value1 + value2);
};
sumBlock();
// 带参数和返回值的Block
int (^ sumBlock)(int,int);
sumBlock = ^(int value1,int value1){
NSLog(@"I am a block");
return value1 + value2;
};
sumBlock();
-
当Block作为属性时:
@property(nonatomic, copy) void (^NormalBlock)(void);
或者
typedef void (^NormalBlock)(void);
@property(nonatomic, copy) NormalBlock block;
这种用法最常见的就是平时我们在cell中的响应事件的处理,有时使用block来回调到VC去处理会更加方便
@interface TableViewCell : UITableViewCell
@property (copy,nonatomic) void (^aBlock)(void); // 无参数
@property (copy,nonatomic) void (^bBlock)(NSString * , NSDictionary *); // 有参数
@end
@implementation TableViewCell
- (void)clickAction1{
if(self. aBlock){
self. aBlock();
}
}
- (void)clickAction2{
NSString * keyStr = @"YES";
NSDictionary * dict = @{@"uid" : @"100008" };
if(self. bBlock){
self. bBlock(keyStr,dict);
}
}
@end
@interface ViewController : UIViewController
@end
@implementation ViewController
[self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass( [EP_Files_ProListCell class] ) bundle:nil] forCellReuseIdentifier:[EP_Files_ProListCell cellID]];
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
EP_Files_ProListCell * cell = [tableView dequeueReusableCellWithIdentifier:[EP_Files_ProListCell cellID]];
cell. aBlock = ^{
//do anything
};
cell. bBlock = ^(NSString *keyStr, NSDictionary *dicT){
//do anything
};
return cell;
}
@end
-
Block作为OC函数参数
既然
Block
是iOS
中一种比较特殊的数据类型,那就代表Block
可以作为函数参数进行传递。
// 1、使用typedef定义Block类型
typedef int(^MyBlock)(int, int);
// 2、定义一个形参为Block的OC函数
-(void)useBlockForOC:(MyBlock)aBlock{
NSLog(@"result = %d", aBlock(300,200));
}
// 3、声明并赋值定义一个Block变量
MyBlock addBlock = ^(int x, int y){
return x+y;
};
// 4、以Block作为函数参数,把Block像对象一样传递
[self useBlockForOC:addBlock];
// 5、将第3点和第4点合并一起,以内联定义的Block作为函数参数
[self useBlockForOC:^(int x, int y){
return x+y;
}];
-
Block作为OC函数参数拓展
有时候我们需要从一个方法中返回一个值时,但刚好需要经过
GCD
延时处理后赋值才返回,这种场景用return
时不行的,因为GCD
中的block
返回值类型为空,那么这时候可以用block
来回调返回值。
typedef void (^aBlock)(NSString *value);
- (void)test{
[self doSomeThingWithBlock:^(NSString *value) {
NSLog(@"%@",value);
}];
}
- (void)doSomeThingWithBlock:(aBlock)block{
NSString *value = @"1";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
value = @"2";
block(value);
});
}
-
当Block作为返回值时
我们经常使用的Masonry
框架内部实现就大量用到block
返回值来实现链式调用的语法
[_iconImg mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.bottom.right.mas_equalTo(0);
}];
Block的底层数据结构
-
观察Block的class等信息
NSLog(@"block.class = %@",[block class]);
NSLog(@"block.class.superclass = %@",[[block class] superclass]);
NSLog(@"block.class.superclass.superclass = %@",[[[block class] superclass] superclass]);
block.class = __NSMallocBlock__
block.class.superclass = NSBlock
block.class.superclass.superclass = NSObject
我们可以发现:
- 它继承自
NSBlock
,而NSBlock
又继承自NSObject
。所有的OC对象都继承自NSObject
,所以Block
也是一个OC对象,下面会发现它里面也有isa
指针,更加证明了这一点。
-
转化成C/C++代码来观察
在main
函数中声明一个block
对象
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1、定义一个block
void (^aBlock)(void);
// 2、把block指向一个代码块
aBlock = ^{
NSLog(@"hello world");
};
// 3、调用执行block
aBlock();
}
return 0;
}
转化成C/C++代码,来查看底层实现。
struct __block_impl {
void *isa;
int Flags;
int 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;
impl.Flags = flags;
impl.FuncPtr = fp; // 通过指针传递
Desc = desc;
}
};
// Block的具体代码执行
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_69540b_mi_0);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 1、定义一个block
void (*aBlock)(void);
// 2、把block指向一个代码块,调用__main_block_impl_0结构体内部的构造方法
aBlock = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA)
);
// 3、调用执行block
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
-
观察__main_block_func_0结构体
从上面看出我们定义的aBlock
最终指向给了__main_block_func_0
这个结构体,所以Block
本质是一个的结构体对象。
根据OC对象的知识,通过层层剥离,其实__main_block_func_0
这个结构体也可以简化成以下组成:
// Block结构体
struct __main_block_impl_0 {
// struct __block_impl impl;
void *isa; //说明block是一个oc对象
int Flags;
int Reserved;
void *FuncPtr; //所封装的函数的地址
// struct __main_block_desc_0* Desc;
size_t reserved;
size_t Block_size; //block的大小
// 构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; // 通过指针传递
Desc = desc;
}
};
我们可以发现Block本质是一个C++结构体:
isa
指针,指向Block
所属的类。证明Block本质
上是一个OC对象
。__block_impl impl
的内存地址就是__main_block_impl_0
的内存首地址。*FuncPtr
指针,指向该Block
的执行函数。__main_block_desc_0
保存了Block
描述信息。- 还有
Block
的构造函数。- 还有
Block
捕获的变量(这里暂未展示)。
-
观察block代码具体执行区
^{
NSLog(@"hello world");
};
大括号里面的代码就是block的具体执行,在Block的初始化方法中:
// 2、把block指向一个代码块,调用__main_block_impl_0结构体内部的构造方法
aBlock = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0, // 构造方法第一个参数,一个指针
&__main_block_desc_0_DATA)
);
截取部分相关方法:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 最终的指针接收
};
// 内部构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; // 通过指针传递
Desc = desc;
}
// Block的代码具体执行区
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_69540b_mi_0);
}
我们可以发现:
__main_block_func_0
封装的是Block内部具体执行
函数。- 通过
__main_block_impl_0
内部构造方法将__main_block_func_0
的地址传给参数参数*fp
。- 在构造方法中
*fp
最终传递给了__block_impl
结构体中的FuncPtr 方法
。- 又通过上面得知
__block_impl impl
的内存地址就是__main_block_impl_0
的内存首地址。
总结:其实相当于通过指针传递的方式
把整段代码执行
传递进入了block
中。Block
是一个封装了函数调用以及函数调用环境的OC对象
。
Block的类型
通过调用class方法查看其类型以及继承链发现:block
有三种类型。
(__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject)
(__NSStackBlock__ : __NSStackBlock : NSBlock : NSObject)
(__NSMallocBlock__ : __NSMallocBlock : NSBlock : NSObject)
-
如何区分创建的Block是那种类型?
如果创建了一个block
,但是没有访问auto
变量,block
是__NSGlobalBlock__
类型:
void (^block1)(void) = ^ {
};
如果创建了一个block,访问了auto
变量,block
是__NSStackBlock__
类型:
int age = 10;
void (^block2)(void) = ^{
NSLog(@"age = %d",age);
};
需要在
MRC
环境下验证,在ARC
环境下,编译器可能会将栈上的block
复制到堆上
如果一个__NSStackBlock__
类型block
,调用了copy
方法,block变成__NSMallocBlock__
类型:
int age = 10;
void (^block3)(void) = [^{
NSLog(@"age = %d",age);
} copy];
总结如下图
-
Block三种类型内存分布
-
各个类型Block调用copy的结果:
-
为什么Block要调用copy把栈Block复制到堆区呢?
Block
刚被创建出来时候,若不是GlobalBlock
就是StackBlock
,栈内存是系统自动管理,超过作用域就会释放。
如果不把栈Block
复制到堆区,很有可能调用栈Block
的时候它已经被销毁,就会导致数据错乱。
-
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上比如以下情况:
1、block作为函数返回值时。
2、将block赋值给__strong指针时。
3、block作为Cocoa API中方法名含有usingBlock的方法参数时。
4、block作为GCD API的方法参数时。
5、block访问了auto变量。
-
Block三种类型的最后总结
block
分类三种类型,通过是否访问auto
变量和调用copy
决定.
不同类型的block
调用copy
效果不同。
平时使用block
时一般会用copy
修饰。因为放在栈区的block
的内存由系统管理,调用block
时可能出现内部存储的数据错乱问题,所以需要拷贝到堆区进行使用。
ARC
下block
属性的建议写法:
@property (copy, nonatomic) void (^block)(void);
@property (strong, nonatomic) void (^block)(void);
MRC
下block
属性的建议写法:
@property (copy, nonatomic) void (^block)(void);
网友评论