1、定义
【1】首先我们看下block的定义。
在Block_private.h
文件中:
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
unsigned long int reserved;
unsigned long int size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout;
};
struct Block_layout {
void *isa;
volatile int flags; // contains ref count
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
在上面Block_layout
中,包含一个isa
的指针。
所以block
可以理解为: block
在本质上是一个指向结构体的指针。
【2】脱字符(^)是块的语法标记。
按照我们熟悉的参数语法规约所定义的返回值以及块的主体(也就是可以执行的代码)。下图是如何把块变量赋值给一个变量的语法讲解:
2、写法
下面写几个例子,稍微展示下block的写法。 扔个砖头,仅此而已。
例子1:
在文件顶部定义block
,然后在.m文件中引用。
typedef void(^idBlock_int)(int);
idBlock_int name = ^(int NumVersion){
NSLog(@"%d",NumVersion);
};
例子2:
定义一个名称为VoidBlock_int
的block作为参数使用。
@interface ViewController ()
@property (nonatomic,copy) void(^VoidBlock_int)(int);
@end
self.VoidBlock_int = ^(int number) {
NSLog(@"%d",number);
};
例子3:
在.m文件中定义一个block
。然后调用。
int(^intBlock_int)(int) = ^(int NSNumber){
return (NSNumber + 100);
};
int result = intBlock_int(10);
NSLog(@"result is %d",result); //result is 110
3、Block真面目(三打白骨精)
1、简单调用block
我们创建一个Block.m
文件,然后自定义一个block
。
#import "Block.h"
@implementation Block
- (void)creat{
void (^strBlock)() = ^(){
printf("hello world");
};
strBlock();
}
@end
然后我们使用clang命令查看一下:
clang -rewrite-objc Block.m
我们会看到下面的代码:
struct __Block__creat_block_impl_0 {
struct __block_impl impl;
struct __Block__creat_block_desc_0* Desc;
__Block__creat_block_impl_0(void *fp, struct __Block__creat_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Block__creat_block_func_0(struct __Block__creat_block_impl_0 *__cself) {
printf("hello world");
}
static struct __Block__creat_block_desc_0 {
size_t reserved;
size_t Block_size;
} __Block__creat_block_desc_0_DATA = { 0, sizeof(struct __Block__creat_block_impl_0)};
static void _I_Block_creat(Block * self, SEL _cmd) {
void (*strBlock)() = ((void (*)())&__Block__creat_block_impl_0((void *)__Block__creat_block_func_0, &__Block__creat_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)strBlock)->FuncPtr)((__block_impl *)strBlock);
}
通过这个,我们发现了什么呢?
定义完block
之后,其实是创建了一个函数,在创建结构体的时候把函数的指针一起传给了block
,所以之后可以拿出来调用。
2、看值捕获的问题
#import "Block.h"
@implementation Block
- (void)creat{
int a = 10;
void (^strBlock)() = ^(){
printf("a = %d",a);
};
strBlock();
}
@end
然后我们使用clang命令查看一下:
clang -rewrite-objc Block.m
我们会看到下面的代码:
在其中我们可以发现,
定义block
的时候,变量a
的值就传递到了block
结构体中,仅仅是值传递,
所以在block中
修改a
是不会影响到外面的a
变量的。
3、添加__block前缀
#import "Block.h"
@implementation Block
- (void)creat{
__block int a = 10;
void (^strBlock)() = ^(){
a++;
printf("a = %d",a);
};
strBlock();
}
@end
然后我们使用clang命令查看一下:
clang -rewrite-objc Block.m
我们会看到下面的代码:
在其中,我们可以发现:
并不是直接传递a
的值了,而是把a的地址传过去了,
所以在block
内部便可以修改到外面的变量了。
3、总结
1、Block常规存储地址
【1】 在Block中,如果只使用全局或静态变量 或者 不使用外部变量。
那么Block块的代码会存储在全局区。
【2】在Block中,如果使用了外部变量。
在ARC中,Block块的代码会存储在堆区;
在MRC中,Block块的代码会存储在栈区。
2、Block默认情况下不能修改外部变量, 只能读取外部变量。
【在ARC中】
外部变量存在堆中。这个变量在block块内与block块外地址相同;
外部变量存在栈区。这个变量会被copy盗block代码块所分配的堆中。
【在MRC中】
外部变量存在堆中。这个变量在block块内和block块外地址相同;
外部变量在栈区,这个变量会被copy到block代码块所分配的栈中。
3、如果需要修改外部变量,需要在外部变量前面声明_block
【在ARC中】
外部变量存在堆中。这个变量在block块内与block块外地址相同;
外部变量存在栈中。这个变量会被转移到堆区,不是复制,是转移。
【在MRC中】
外部变量存在堆中。这个变量在block块内与block块外地址相同;
外部变量在栈中。这个变量在block块内与block块外地址相同。
4、使用block应该避免循环引用
使用block代码块应注意内部循坏引用, 导致循环引用应在block定义前加上__weak声明:
__weak typeof(<#obj#>) weak<#obj#> = <#obj#>;
例如:
__weak typeof(self)weakSelf = self;