什么是block?

用一句话概括就是–“带有自动变量的匿名函数”

自动变量

变量有5种,局部变量、函数参数、静态变量、全局变量、全局静态变量。
自动变量=局部变量+函数参数

匿名函数

匿名函数就是没有名称的函数
带有自动变量的匿名函数在很多语言中被称之为闭包或者lambda表达式,如JS中常见的

()=>{

}

block的语法

block与c语言中的函数指针比较相似,在c语言中函数指针的形式如下

// 返回值类型+函数名+参数列表+表达式
int testFunc(int) {}
int main() {
// 声明一个函数指针
int (*testFuncPtr)(int);
// 为该函数指针赋值
testFuncPtr = &testFunc;
}

再看下OC中的block

{
// 声明一个block变量blk
int (^blk)(int);
// 定义该变量
// ^+返回值类型+参数列表+表达式
blk = ^int(int val){
}
}

可以看到,block变量的声明与函数指针变量的声明非常相似,都是返回值+变量名+参数,只是将$\pmb{*}$替换成了^(脱字符号);
对于定义,block相比于c函数定义仅仅少了函数名,以及在前面增加了^符号
声明block类型、block作为属性、函数参数、函数调用参数可以看这里:http://fuckingblocksyntax.com/

block源码分析

通过编译器命令,我们可以将block转换为c语言源代码

clang -rewrite-objc 源代码文件名

在oc工程的main函数中写一段block代码,分别定义六种类型的变量:全局、静态全局、oc对象、局部、静态局部、局部指针

int glbVal = 0; // 全局变量
static int staticGlbVal = 0; // 静态全局变量
int main(int argc, const char * argv[]) {
NSObject *obj = [[NSObject alloc] init]; // oc对象
int stackVal = 0; // 局部变量
static int staticStackVal = 0; // 静态局部变量
int *stackValPtr = &stackVal; // 局部指针变量
void(^blk)(void) = ^{
NSLog(@"glbVal:%d staticGlbVal:%d stackVal:%d staticStackVal:%d obj:%p stackValPtr:%p",glbVal,staticGlbVal,stackVal,staticStackVal,obj,stackValPtr);
};
return 0;
}

我们将其转换为c语言代码:

int glbVal = 0; // 全局变量
static int staticGlbVal = 0; // 静态全局变量

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int stackVal;
int *staticStackVal;
NSObject *obj;
int *stackValPtr;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _stackVal, int *_staticStackVal, NSObject *_obj, int *_stackValPtr, int flags=0) : stackVal(_stackVal), staticStackVal(_staticStackVal), obj(_obj), stackValPtr(_stackValPtr) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int stackVal = __cself->stackVal; // bound by copy
int *staticStackVal = __cself->staticStackVal; // bound by copy
NSObject *obj = __cself->obj; // bound by copy
int *stackValPtr = __cself->stackValPtr; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders_k__t3kntz3113q9w6x7v3vy70j40000gn_T_main_c1a283_mi_0,glbVal,staticGlbVal,stackVal,(*staticStackVal),obj,stackValPtr);
}
// block拷贝
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
// block销毁
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// block描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, const char * argv[]) {
// oc对象
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));

// 局部变量
int stackVal = 0;

// 静态局部变量
static int staticStackVal = 0;

// 局部指针变量
int *stackValPtr = &stackVal;

// 名称为blk的函数指针变量
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, stackVal, &staticStackVal, obj, stackValPtr, 570425344));
return 0;
}

blk变量

先从转换后的源码开始,oc中的blk为block类型变量,转换成c语言代码后,blk变成了函数指针类型变量。__main_block_impl_0在源码中是一个结构体,这里为结构体的同名构造函数调用,调用结果是生成一个结构体->取该结构体地址->转换为函数指针类型->赋值给blk变量。

__main_block_impl_0,block的结构体&捕获外部变量值

看下__main_block_impl_0结构体,成员变量为impl结构体变量Desc结构体指针stackVal变量staticStackVal指针obj对象指针,我们再将impl的类型struct __block_impl展开,**__block_impl的成员变量为:isa指针Flags变量Reserve变量、FuncPtr**函数指针。根据__main_block_impl_0构造函数我们可以看到这些成员变量和构造函数参数有如下对应关系:

  • impl.FuncPtr = fp // 传地址
  • Desc = desc // 传地址
  • stackVal = __stackVal // 传值
  • staticStackVal = _staticStackVal // 传地址
  • obj = _obj // 传指针值
  • stackValPtr = __stackValPtr // 传指针值

__main_block_impl_0结构体就是block的原型,block持有的外部变量规则为:

  • 局部变量为值传递
  • 静态局部变量为指针传递
  • 静态局部指针为值传递(传指针值)
  • oc对象指针也为值传递(传对象指针值)
  • 全局变量与静态全局变量为直接访问
    换句话来说,block引用的外部变量值,除了静态局部变量为传地址,全局/静态全局为直接访问,其它都是传值,指针传指针的值,变量传变量的值

__main_block_func_0,block代码块的实现

对于block中执行的打印外部变量值的代码,都实现在__main_block_func_0函数中,__main_block_func_0作为__main_block_impl_0的构造函数参数传递给结构体,同时__main_block_func_0也接收struct __main_block_impl_0 *__cself作为参数,__cself其实就是持有__main_block_func_0函数的__main_block_impl_0结构体自身的指针,类似于C++中的this指针,从自身的成员变量中取值并打印,bound by copy即拷贝

__main_block_desc_0

根据名称规则可以猜到,该结构体的作用是描述block,含有以下成员

  • reserved // 保留区域大小(版本升级的保留区?)
  • Block_size // block大小,即__main_block_impl_0的大小
  • copy // block的拷贝函数
  • dispose // block的销毁函数

block的几种类型

NSConcretGlobalBlock

全局block,存储域为数据区,以下两种情况下存在

  • block变量定义在全局区时候
  • block没有引用外部局部变量(非静态局部变量)时
    void (^blk)(void) = ^{};  // global_block
    int main(){
    static int static_val = 0;
    void (^blk1)(void) = ^{ // global_block
    };
    void (^blk2)(void) = ^{ // global_block
    NSLog(@"%d",static_val);
    };
    }

NSConcreteStackBlock

栈block,存储域为栈区,目前已经很少有栈block,以前定义在函数栈中的引入外部局部变量的block为栈block,但是目前栈block会被自动拷贝到堆上,变为堆block,需要在block变量的前面加上__weak关键字,打印后才为栈block

int main() {
int val = 0;
void(^__weak blk)(void) = ^{ // stack_block
NSLog(@"%d",blk);
}
void(^blk1)(void) = ^{ // malloc_block
NSLog(@"%d",blk);
}
}

NSConcreteMallocBlock

堆block,存储域为堆区,目前的oc代码中,若不是global_block,则它一定是malloc_block,stack_block会被系统自动拷贝到堆上;可以手动将栈block,执行copy,生成堆block

int main(){
int val = 0;
void(^__weak blk)(void) = ^{ // stack_block
NSLog(@"%d",blk);
}
void(^blk1)(void) = ^{ // malloc_block
NSLog(@"%d",blk);
}
void(^blk2)(void) = [blk copy]; // malloc_block
}

__block block说明符的作用

下面这段代码,我们定义了局部变量val,并尝试在block中修改val的值,然而val=1是编译不过的,因为根据上面的分析,block持有局部变量为值拷贝,我们修改的只是block拷贝的变量值,无法修改原变量val。如果我们需要修改持有的局部变量值,可以使用staticstatic传递的是局部变量的地址。或者我们也可以在局部变量的前面添加上__block说明符

int val = 0;
static int static_val = 0;
__block int block_val = 0;

NSObject *obj = nil;
__block NSObject *block_obj = nil;
void(^blk)(void) = ^{
val = 1; // error
static_val = 1; // true
block_val = 1; // true

obj = [[NSObject alloc] init]; // error
block_obj = [[NSObject alloc] init]; // true
}

那么__block说明符做了什么作用呢,我们将以上代码仍然转换为c代码,可以看到除了static_val还是通过传地址给__main_block_impl_0block_valblock_obj均变为了结构体__Block_byref_block_val_0__Block_byref_block_obj_1,结构体中含有成员变量int block_valNSObject *block_obj,它们分别拷贝block_val的值、指向block_obj对象的地址。
当在block中修改block_valblock_obj的值时,只是分别改变了两个结构体持有的block_val变量的值、block_obj指针变量指向的对象地址。因此,__block说明符只是将变量变成了结构体,并在结构体成员中持有原始值。

struct __Block_byref_block_val_0 {
void *__isa;
__Block_byref_block_val_0 *__forwarding;
int __flags;
int __size;
int block_val;
};
struct __Block_byref_block_obj_1 {
void *__isa;
__Block_byref_block_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *block_obj;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__Block_byref_block_val_0 *block_val; // by ref
__Block_byref_block_obj_1 *block_obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, __Block_byref_block_val_0 *_block_val, __Block_byref_block_obj_1 *_block_obj, int flags=0) : static_val(_static_val), block_val(_block_val->__forwarding), block_obj(_block_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_block_val_0 *block_val = __cself->block_val; // bound by ref
__Block_byref_block_obj_1 *block_obj = __cself->block_obj; // bound by ref
int *static_val = __cself->static_val; // bound by copy

(*static_val) = 1;
(block_val->__forwarding->block_val) = 1;
(block_obj->__forwarding->block_obj) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
int main(int argc, const char * argv[]) {
static int static_val = 0;
__attribute__((__blocks__(byref))) __Block_byref_block_val_0 block_val = {(void*)0,(__Block_byref_block_val_0 *)&block_val, 0, sizeof(__Block_byref_block_val_0), 0};
__attribute__((__blocks__(byref))) __Block_byref_block_obj_1 block_obj = {(void*)0,(__Block_byref_block_obj_1 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, __null};
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val, (__Block_byref_block_val_0 *)&block_val, (__Block_byref_block_obj_1 *)&block_obj, 570425344));
return 0;
}

__Block_byref_block_val_0、__Block_byref_block_obj_1中__forwarding指针的作用

__forwarding是__block结构体中指向自身的指针成员变量,当block在栈上时,访问__block变量值的顺序为:栈__block结构体->__forwarding->变量值,当block被拷贝到堆上时,栈block与堆block同时存在,此时__block结构体在栈上与堆上同时存在,需要让两个__block结构体访问的是堆上的__block结构体成员变量,此时栈上__block结构体的__forwarding指针指向的其实是堆__forwarding指针,不再是栈__block结构体自身。

block引起的循环引用

下面的代码发生了循环引用,对象p持有了block,block中打印了p.name,等于持有了p

@interface Person :NSObject
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, copy) NSString *name;
@end

@interface SomeVC()
@end
@implementaion
- (void)viewDidLoad {
Person *p = [[Person alloc] init];
p.name = @"the person";
p.block = ^{
NSLog(@"%@",p.name);
};
p.block();
}
@end

解决方法有两种:

  • 定义一个__weak局部变量weakPerson,p持有block,block捕获的是弱引用对象指针weakPerson,因此对p也是弱引用,没有产生循环引用
    - (void)viewDidLoad {
    Person *p = [[Person alloc] init];
    p.name = @"the person";
    __weak typeof(p) weakPerson = p;
    p.block = ^{
    NSLog(@"%@",weakPerson.name);
    };
    p.block();
    }
    @end
  • 使用__block,在block执行完后手动将其置为nil,此时不会有循环引用,如果block没有执行,那么循环引用会一直存在
    - (void)viewDidLoad {
    __block Person *p = [[Person alloc] init];
    p.name = @"the person";
    p.block = ^{
    NSLog(@"%@",weakPerson.name);
    p = nil;
    };
    p.block();
    }
    @end

__weakSelf和__storngSelf的使用

这周暂时不写了:D