1.Block的使用

Block是什么?

块,封装了函数调用以及调用环境的OC对象,

Block的声明

//1.
@property (nonatomic, copy) void(^myBlock1)(void);
// 2.BlockType:类型别名
typedef void(^BlockType)(void);
//3.
//返回值类型(^block变量名)(参数1类型,参数2类型, ...)
void(^block)(void);

Block的定义

// ^返回值类型(参数1,参数2,...){};
//1.无返回值,无参数
void(^block1)(void) = ^{};//2.无返回值,有参数
void(^block2)(int) = ^(int a) {} ;//3.有返回值,无参数(不管有没有返回值,定义的返回值类型都可以省略)
int(^block3)(void)^int {};
//以上Block的定义也可以这样写:
int(^block3)(void) = ^ {};//4.有返回值,有参数
int(^block5)(int) = ^int(int a) {return 3*a;
};//Block的调用
//1.无返回值,无参数
block1();
//2.有返回值,有参数
int a = block5(2);//使用示例
int multipler = 7;
int (^myBlock)(int) = ^(int num) {return num * multiplier;
};printf("%d", myBlock(3));
// prints "21"

2.Block的底层数据结构

Block本质上也是一个OC对象,它内部也有个ias指针;

Block是封装了函数调用以及调用环境的OC对象;

Block的底层数据结构如下图所示:

通过Clang将以下Block代码转换为C++代码,来分析Block的底层实现。

// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
//main.m
int main(int argc, const char * argv[]) {@autoreleasepool {void(^block)(void) = ^ {NSlog(@"调用了block");};block();}return 0;
}
// main.cpp
struct __main_block_impl_0 {struct __block_impl impl;         // block的结构体struct __main_block_desc_0* Desc; // block的描述对象,描述block的大小等/*  构造函数** 返回值:__main_block_impl_0 结构体** 参数一:__main_block_func_0 结构体** 参数二:__main_block_desc_0 结构体的地址** 参数三:flags 标识位*/__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock; //_NSConcreteStackBlock 表示block存在栈上impl.Flags = flags; impl.FuncPtr = fp;Desc = desc;}
};// __main_block_func_0 封装了block里的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_58a448_mi_0);
}struct __block_impl {void *isa;     // block的类型int Flags;     // 标识位int Reserved;  // void *FuncPtr; // block的执行函数指针,指向__main_block_func_0
};static struct __main_block_desc_0 {size_t reserved;size_t Block_size; // block本质结构体所占内存空间
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; /*** void(^block)(void) = ^{NSLog(@"调用了block");};** 定义block的本质:** 调用__main_block_impl_0()构造函数** 并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA** __main_block_func_0 封装了block里的代码** 拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,** 把这个地址赋值给 block*/void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,&__main_block_desc_0_DATA));/*** block();** 调用block的本质:** 通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用*/      ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);}return 0;
}Block底层数据结构就是一个 __main_block_impl_0 结构体对象,其中有  __block_impl 和 __main_block_desc_0 两个结构对象成员。
main:表示block所在的函数
block:表示这是一个block
impl:表示实现(implementation)
0: 表示这是该函数中的第一个block
__main_block_func_0 结构体封装了block里的代码;
__block_impl 结构体才是真正定义block的结构,其中的FuncPtr指针指向 __main_block_func_0;
__main_block_desc_0 是block的描述对象,存储着block内存大小等;
定义block的本质:
调用 __main_block_impl_0() 构造函数,并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA 。拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,把这个地址赋值给block变量。
调用block的本质:
通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用。

Block 的变量捕获机制

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制。

对于全局变量,不会捕获到block内部,访问方式为直接访问;

对于auto类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,用来存储这个变量的值,访问方式为值传递;

对于static类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,用来存储这个变量的地址,访问方式为指针传递;

对于对象类型的局部变量,block会连同它的所有权修饰符一起捕获。

auto类型的局部变量

auto自动变量:我们定义出来的变量,默认都是auto类型,只是省略了。

auto int age = 10;

通过Clang将以下代码转换为C++代码:

int age = 10;
void(^block)(void) =^ {NSLog(@"%d", age);
}
block();
//__main_block_impl_0 对象内部会生成一个相同的age变量;
//__main_block_impl_0() 构造函数多了个参数,用来捕获访问的外面的age变量的值,将它赋值给__main_block_impl_0 对象内部的 age 变量。struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int age;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int age = __cself->age; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_5ed490_mi_0,age);
}......int age = 10;void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

由于是值传递,我们修改外部的age变量的值,不会影响到block内部的age变量。

int age = 10;
void(^block)(void) = ^ {NSLog(@"%d", age);
};
age = 20;
block();
// 10

static类型的局部变量

static 类型的局部变量会捕获到block内部,访问方式为指针传递。

通过Clang将以下代码转换成为C++代码:

static int age = 10;void(^block)(void) = ^{NSLog(@"%d",age);};block();
__main_block_impl_0 对象内部会生成一个相同类型的 age 指针;
__main_block_impl_0() 构造函数多了个参数,用来捕获访问的外面的age变量的地址,将它赋值给 __main_block_impl_0 对象内部的age指针。struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int *age;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int *age = __cself->age; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_a4bc7d_mi_0,(*age));
}......static int age = 10;void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

由于是指针传递,我们修改外部的age变量的值,会影响到block内部的age变量。

static int age = 10;void(^block)(void) = ^{NSLog(@"%d",age);};age = 20;block();// 20

全局变量

全局变量不会捕获到 block 内部,访问方式为直接访问

通过 Clang 将以下代码转换为 C++ 代码:

int _age = 10;
static int _height = 20;
......void(^block)(void) = ^{NSLog(@"%d,%d",_age,_height);};block();
  • __main_block_impl_0对象内并没有生成对应的变量,也就是说全局变量没有捕获到 block 内部,而是直接访问。
int _age = 10;
static int _height = 20;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;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_12efa5_mi_0,_age,_height);
}......void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

为什么局部变量需要捕获,全局变量不用捕获呢?

为什么局部变量需要捕获,全局变量不用捕获呢?

  • 作用域的原因,全局变量哪里都可以直接访问,所以不用捕获;
  • 局部变量,外部不能直接访问,所以需要捕获;
  • auto 类型的局部变量可能会销毁,其内存会消失,block 将来执行代码的时候不可能再去访问那块内存,所以捕获其值;
  • static 变量会一直保存在内存中, 所以捕获其地址即可。

对象类型的auto变量

当block内部访问了对象类型的auto变量时:

如果block是在栈上,将不会对auto变量产生强引用
如果block被拷贝到堆上:block内部的desc结构会新增两个函数:copy(__main_block_copy_0, 函数名命名规范同__main_block_impl_0)dispose( __main_block_dispose_0)会调用block内部的copy函数copy函数内部会调用 _Block_object_assign 函数——Block_object_assign 函数黑根据auto变量的修饰符(__strong、__weak、 __unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用如果block从堆上移除会调用block内部的dispose函数dispose函数内部会调用 __Block_object_dispose 函数__Block_object_dispose 函数会自动释放引用的auto变量(release)
函数 调用时机
copy 函数 栈上的 block 复制到堆时
dispose 函数 堆上的 block 被废弃时

如下代码,block 保存在堆中,当执行完作用域2的时候,Person 对象并没有被释放,而是在执行完作用域1的时候释放,说明 block 内部对 Person 对象产生了强引用。

typedef void(^MyBlock)(void);int main(int argc, const char * argv[]) {@autoreleasepool { //作用域1MyBlock block;{ //作用域2Person *p = [Person new];p.name = @"zhangsan";      block = ^{NSLog(@"%@",p.name);};}NSLog(@"-----");}return 0;
}
// -----
// Person-dealloc

通过 Clang 将以上代码转换为 C++ 代码:

// 弱引用需要运行时的支持,所以需要加上 -fobjc-arc -fobjc-runtime=ios-8.0.0
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

__main_block_impl_0中生成了一个Person *__strong p指针,指向外面的 person 对象,且是强引用。

typedef void(*MyBlock)(void);struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;Person *__strong p; // 强引用__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_p, int flags=0) : p(_p) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {Person *__strong p = __cself->p; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("name")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}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[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; MyBlock block;{Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_0);block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, 570425344));}NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_2);}return 0;
}

添加了__weak修饰后,当执行完作用域2的时候,Person 对象就被被释放了。

typedef void(^MyBlock)(void);int main(int argc, const char * argv[]) {@autoreleasepool { //作用域1MyBlock block;{ //作用域2__weak Person *p = [Person new];p.name = @"zhangsan";      block = ^{NSLog(@"%@",p.name);};}NSLog(@"-----");}return 0;
}
// Person-dealloc
// -----

同样的,通过 Clang 将以上代码转换为 C++ 代码。 __main_block_impl_0中生成了一个Person *__weak p指针,指向外面的 person 对象,且是弱引用。 说明当 block 内部 访问了对象类型的 auto 变量时,如果 block 被拷贝到堆上,会连同对象的所有权修饰符一起捕获。

......
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;Person *__weak p; //弱引用__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _p, int flags=0) : p(_p) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {Person *__weak p = __cself->p; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_c61841_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("name")));
}
......

_block修饰的变量

__block作用

默认情况下block是不能修改外面的auto变量的,解决办法?

变量用static修饰(原因:捕获static类型的局部变量是指针传递,可以访问到该变量的内存地址)

全局变量

__block(我们只是希望临时用一下这个变量临时改一下而已,而改为static变量和全局变量会一直在内存中)

__block修饰符

__block可以用于解决block内部无法修改auto变量值的问题;

__block不能修饰全局变量、静态变量;

编译器会将__block变量包装成一个对象(struct_Block_byref_age_0 byref:按地址传递);

加__block修饰不会修改变量的性质,它还是auto变量;

一般情况下,对被捕获变量进行赋值(赋值!= 使用)操作需要添加__block修饰符。比如给给数组添加或删除对象,就不用加 __block

使用示例

    __block int age = 10;void(^block)(void) = ^{age = 20;NSLog(@"block-%d",age);};block();NSLog(@"%d",age);// block-20// 20

通过 Clang 将以上代码转换为 C++ 代码。

  • 编译器会将 __block 修饰的变量包装成一个__Block_byref_age_0对象;
  • 以上age = 20;的赋值过程为:通过 block 结构体里的(__Block_byref_age_0)类型的 age 指针,找到__Block_byref_age_0结构体的内存(即被 __block 包装成对象的内存),把__Block_byref_age_0结构体里的 age 变量的值改为20。
  • 由于编译器将 __block 变量包装成了一个对象,所以它的内存管理几乎等同于访问对象类型的 auto 变量,但还是有差异,下面会讲到。
struct __Block_byref_age_0 {void *__isa;__Block_byref_age_0 *__forwarding;int __flags;int __size;int age;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_age_0 *age; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref(age->__forwarding->age) = 20;NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9578d0_mi_0,(age->__forwarding->age));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}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[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9578d0_mi_1,(age.__forwarding->age));}return 0;
}

__block的内存管理

当block在栈上时,并不会对__block变量产生强引用

当block被copy到堆时

​ block内部的desc结构会新增两个函数 copy(__main_block_copy_0,函数名命名规范同__main_block_impl_0)  dispose(__main_block_dispose_0

​ 会调用block内部的copy函数

​ copy函数内部会调用 _Block_object_assign 函数

_Block_object_assign 函数会对 __block变量形成引用(retain)

当block从堆中移除时

​ 会调用block内部的dispose函数

​ dispose函数内部会调用 _Block_object_dispose 函数

_Block_object_dispose 函数会自动释放引用的 __block变量(release)

__block 的__forwarding指针存在的意义?
为什么要通过 age 结构体里的__forwarding指针拿到 age 变量的值,而不直接 age 结构体拿到 age 变量的值呢?
__block 的__forwarding是指向自己本身的指针,为了不论在任何内存位置,都可以顺利的访问同一个 __block 变量。block 对象 copy 到堆上时,内部的 __block 变量也会 copy 到堆上去。为了防止 age 的值赋值给栈上的 __block 变量,就使用了__forwarding;
当 __block 变量在栈上的时候,__block 变量的结构体中的__forwarding指针指向自己,这样通过__forwarding取到结构体中的 age 给它赋值没有问题;
当 __block 变量 copy 到堆上后,栈上的__forwarding指针会指向 copy 到堆上的 _block 变量结构体,而堆上的__forwarding指向自己;
这样不管我们访问的是栈上还是堆上的 __block 变量结构体,只要是通过__forwarding指针访问,都是访问到堆上的 __block 变量结构体;给 age 赋值,就肯定会赋值给堆上的那个 __block 变量中的 age。

3.5.5 对象类型的 auto 变量、__block 变量内存管理区别

  • 当 block 在栈上时,对它们都不会产生强引用
  • 当 block 拷贝到堆上时,都会通过 copy 函数来处理它们
对象类型的 auto 变量(假设变量名叫做p) __block 变量(假设变量名叫做a)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/); _Block_object_assign((void*)&dst->a, (void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);
_Block_object_assign函数会根据 auto 变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用 _Block_object_assign函数会对 __block 变量形成强引用(retain)
  • 当 block 从堆上移除时,都会通过 dispose 函数来释放它们
对象类型的 auto 变量(假设变量名叫做p) __block 变量(假设变量名叫做a)
_Block_object_dispose((void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/); _Block_object_dispose((void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);

3.5.6 被 __block 修饰的对象类型

  • 当 __block 变量在栈上时,不会对指向的对象产生强引用
  • 当 __block 变量被 copy 到堆时 ①__Block_byref_object_0即 __block 变量内部会新增两个函数:  copy(__Block_byref_id_object_copy)  dispose(__Block_byref_id_object_dispose) ② 会调用 __block 变量内部的 copy 函数 ③ copy 函数内部会调用_Block_object_assign函数 ④ _Block_object_assign函数会根据所指向对象的修饰符(__strong__weak__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于 ARC 时会 retain,MRC 时不会 retain)
  • 如果 __block 变量从堆上移除 ① 会调用 __block 变量内部的 dispose 函数 ② dispose 函数内部会调用_Block_object_dispose函数 ③ _Block_object_dispose函数会自动释放指向的对象(release)
int main(int argc, char * argv[]) {NSString * appDelegateClassName;@autoreleasepool {// Setup code that might create autoreleased objects goes here.appDelegateClassName = NSStringFromClass([AppDelegate class]);__block NSObject *object = [[NSObject alloc] init];void(^block)(void) = ^{object = [[NSObject alloc] init];};}return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
struct __Block_byref_object_0 {void *__isa;__Block_byref_object_0 *__forwarding;int __flags;int __size;void (*__Block_byref_id_object_copy)(void*, void*); // copyvoid (*__Block_byref_id_object_dispose)(void*);     // disposeNSObject *__strong object;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_object_0 *object; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_object_0 *_object, int flags=0) : object(_object->__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_object_0 *object = __cself->object; // bound by ref(object->__forwarding->object) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->object, (void*)src->object, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->object, 8/*BLOCK_FIELD_IS_BYREF*/);}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, char * argv[]) {NSString * appDelegateClassName;/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));__attribute__((__blocks__(byref))) __Block_byref_object_0 object = {(void*)0,(__Block_byref_object_0 *)&object, 33554432, sizeof(__Block_byref_object_0), __Block_byref_id_object_copy_131,__Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_object_0 *)&object, 570425344));}return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}static void __Block_byref_id_object_copy_131(void *dst, void *src) {// __block 对象结构体的地址+40个字节,即为结构体中 object 对象的地址_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

注意:在 MRC 下使用 __block 修饰对象类型,在 block 内部不会对该对象进行 retain 操作,所以在 MRC 环境下可以通过 __block 解决循环引用的问题。

示例(MRC)

// 对象类型的捕获,连同所有权修饰符一起捕获,所以 block 对 person 强引用HTPerson *person = [[HTPerson alloc] init];void(^block)(void)  = [^{NSLog(@"%p", person);} copy];[person release];block();[block release];// 0x10053da30
// -[HTPerson dealloc]
// __block 修饰的对象类型的捕获,MRC 下在 block 内部不会对该对象进行 retain 操作__block HTPerson *person = [[HTPerson alloc] init];void(^block)(void)  = [^{NSLog(@"%p", person);} copy];[person release];block();[block release];// -[HTPerson dealloc]
// 0x1007337d0

4.Block 的类型

block 有 3 种类型,可以通过调用 class 方法或者 isa 指针 查看具体类型,最终都是继承自 NSBlock 类型。

block类型 描述 环境
__ NSGlobalBlock __( _NSConcreteGlobalBlock ) 全局block,保存在数据段 没有访问auto变量
__ NSStackBlock __( _NSConcreteStackBlock ) 栈block,保存在栈区 访问了auto变量
__ NSMallocBlock __( _NSConcreteMallocBlock ) 堆block,保存在堆区 __ NSStackBlock __调用了copy

打印各种 block 的类型,以及遍历 block 的父类类型,如下:

int main(int argc, const char * argv[]) {@autoreleasepool {void(^block1)(void) = ^{NSLog(@"hello");};/*  block2** 在ARC下会自动copy,从栈复制到堆,所以为__NSMallocBlock__类型** 在MRC下为__NSStackBlock__类型,需要手动调用copy方法才会变为__NSMallocBlock__类型**     同时,在不需要该block的时候需要手动调用release方法*/ int age = 10;void(^block2)(void) = ^{NSLog(@"%d",age);};NSLog(@"%@,%@,%@", [block1 class], [block2 class], [^{NSLog(@"%d",age);} class]);Class class = [block1 class];while (class) {NSLog(@"%@",class);class = [class superclass];}}return 0;
}
// __NSGlobalBlock__,__NSMallocBlock__,__NSStackBlock__
// __NSGlobalBlock__
// __NSGlobalBlock
// NSBlock
// NSObject

每一种类型的 block 调用 copy 后的结果如下所示:

block类型 副本源的配置存储区 复制效果
_NSConcreteGlobalBlock 程序的数据段区 什么也不做
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteMallocBlock 引用计数增加

__ NSStackBlock __ 存在的问题:

以下是在 MRC 环境下,block 类型为__NSStackBlock__。 当 test() 函数执行完毕,栈上的东西可能会被销毁,数据就会变成垃圾数据。尽管 block 还能正常调用,但是输出的 age 的值发生了错乱。

void (^block)(void);
void test()
{    // __NSStackBlock__int age = 10;block = ^{NSLog(@"%d", age);};
}
int main(int argc, const char * argv[]) {@autoreleasepool {test();block();}return 0;
}
// 272632936

解决办法:调用copy方法,将栈 block 复制到堆。

void (^block)(void);
void test()
{    // __NSMallocBlock__int age = 10;block = [^{NSLog(@"%d", age);} copy];
}
int main(int argc, const char * argv[]) {@autoreleasepool {test();block();[block release];}return 0;
}
// 10

Block 的 copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下几种情况:

​ 手动调用block的copy方法时;

​ block作为函数返回值时(Masonry 框架中用很多);

​ 将block赋值给__strong 指针时;

​ block作为Cocoa API中方法名含有usingBlock 的方法参数时;

​ block 作为 GCD API 的方法参数时。

block作为属性的写法:

ARC下写strong 或者 copy 都会对block进行强引用,都会自动将block从栈copy到堆上;

建议都写成copy,这样MRC和ARC下一致。

// MRC
@property (nonatomic, copy) void(^block)(void);
// ARC
@property (nonatomic, copy) void(^block)(void);
@property (nonatomic, strong) void(^block)(void);

Block 的循环引用问题

为什么block会产生循环引用?

1.自循环引用:如果block 对当前对象的某一成员变量进行捕获的话,可能会对他产生强引用。而当前block又犹豫当前对象对其有一个强引用,就产生了自循环引用的问题;

2.大环引用:我们如果使用__block的话,在ARC下可能会产生循环引用(MRC则不会),在ARC下可以通过断环的方式去解除循环引用。但是有一个弊端,如果该block一直得不到调用,循环引用就一直存在。

//. ARC
__weak typeof(self) weakSelf = self;
self.block = ^{NSLog(@"%p", weakSelf);
};__unsafe_unretained id weakSelf = self;
self.block = ^{NSLog(@"%p", weakSelf);
};
//注意:__unsafe_unretained 会产生悬垂指针

用__block解决(必须要调用block):

​ 缺点:必须要调用block,而且block里要将指针置为nil。如果一直不调用block,对象就会一直保存在内存中,造成内存泄露。

 __block id weakSelf = self;self.block = ^{NSLog(@"%p",weakSelf);weakSelf = nil;};self.block();

//. MRC用__unsafe_unretained 解决:同ARC用 __block 解决(在MRC下使用__block修饰对象类型,在block内部不会对该对象进行retain操作,所以在MRC环境下可以通过__block解决循环引用的问题)__block id weakSelf = self;
self.block = ^ {NSLog(@"%p", weakSelf);
};

总结:

block的本质是什么?

封装了函数调用以及调用环境的OC对象。

block的修饰属性词为什么是copy?使用block有哪些使用注意?

block 一旦没有进行copy操作,就不会在堆上。

使用注意:循环引用问题。

block在给NSMutableArray添加或移除对象,需不需要添加__block?

不需要

block的变量捕获机制

block的变量捕获机制,是为了保证block内部能够正常正常访问外部的变量。

​ 对于全局变量,不会捕获到block内部,访问方式为直接访问;

对于auto类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,用来存储这个变量的值,访问方式为值传递;

对于static类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,用来存储这个变量的地址,访问方式为指针传递;

对于对象类型的局部变量,block会连同它的所有权修饰符一起捕获。

为什么局部变量需要捕获,全局变量不用捕获呢?

作用域的原因,全局变量哪里都可以直接访问,所以不用捕获;

局部变量,外部不能直接访问,所以需要捕获;

auto类型的局部变量可能会销毁,起内存会消失,block将累执行代码的时候不可能再去访问那块内存,所以捕获其值;

static变量会一直保存在内存中,所以捕获其地址即可。

self会不会捕获到block内部?

会捕获

OC方式都有两个隐式参数,方法调用者self和方法名 _cmd。参数也是一种局部变量。

_name会不会捕获到block内部?会捕获。不是将 _name 变量进行捕获,而是直接将 self捕获到block内部,_name是一个属性
__NSStackBlock_存在的问题:
如果没有将block从栈上copy到堆上,那我们访问栈上的block的话,可能会由于变量作用域结束导致栈上的block以及__block变量被销毁,而造成内存崩溃。或者数据可能会变成垃圾数据,尽管将来block还呢正常调用,但是它捕获的变量的值已经错乱了。
解决办法:将block的内存放在堆里,意味着它就不会自动销毁,而是我们程序员来决定什么时候销毁它。

默认情况下block是不能修改外面的auto变量的,解决办法?

变量用static修饰(原因:捕获static类型的局部变量是指针传递,可以访问到该变量的内存地址)

全局变量

__block(我们只希望临时用一下这个变量临时改一下而已,而改为static变量和全局变量会一直在内存中)

__block修饰符使用注意点:在MRC下使用__block修饰对象类型,在block内部不会对该对象进行retain操作,所以在MRC环境下可以通过__block解决循环引用的问题。
__block的_forwarding指针存在的意义?为什么要通过age结构体里的__forwarding指针拿到age变量的值,而不直接age结构体拿到age变量的值呢?见__block的_forwarding指针
为什么通过_weak去修饰成员变量或对象就可以达到规避循环引用的目的呢?block对于对象类型的局部变量联通所有权修饰符一起截获,多亿如果我们在外部定义的对象是__weak所有权修饰符的,那么在block中所产生的结构体里所持有的变量也是__weak类型的

Q:解决在 block 内部通过弱指针访问对象成员时编译器报错的问题:

    __weak typeof(self) weakSelf = self;self.block = ^{__strong typeof(weakSelf) strongSelf = weakSelf;NSLog(@"%d",strongSelf->age);};

Q:以下代码的打印结果是?

    __block int multiplier = 6;int (^block)(int) = ^(int num) {return num * multiplier;};multiplier = 4;NSLog(@"%d",block(2));// 8

Q:以下代码有问题吗?

    __block id weakSelf = self;self.block = ^{NSLog(@"%p",weakSelf);};
  • 在 MRC 下,不会产生循环引用;
  • 在 ARC 下,会产生循环引用,导致内存泄漏,解决方案如下。
    __block id weakSelf = self;self.block = ^{NSLog(@"%p",weakSelf);weakSelf = nil;};self.block();

缺点:必须要调用 block,而且 block 里要将指针置为 nil。如果一直不调用 block,对象就会一直保存在内存中,造成内存泄漏。

Block详解------已完结相关推荐

  1. Linux进程详解(二)完结

    原创架构师之路2019-08-13 22:08 接Linux进程详解(一) 4. 进程运行 程序运行时大部分进程状态为运行或睡眠.调度算法解决可以跑的运行状态(就绪和运行),剩下的不可以跑的进程就是睡 ...

  2. Ardupilot姿态控制详解(完结篇)

    本文主要是对过去写的一些APM中姿态控制函数博文的汇总. 按照之前写的博文以及个人认为的学习顺序,给出APM姿态控制器的学习顺序如下: APM姿态旋转理论基础 Ardupilot姿态控制器 PID控制 ...

  3. LSTM反向传播详解(完结篇)Part3/3代码实现

    1.前记   LSTM系列的前面两篇文章<LSTM反向传播详解Part1><LSTM反向传播详解Part2>将LSTM的理论梳理了一遍,本文主要着重用代码实现LSTM,其实也是 ...

  4. oc开发之block详解

    2019独角兽企业重金招聘Python工程师标准>>> BLOCK是什么?苹果推荐的类型,效率高,在运行中保存代码.用来封装和保存代码,有点像函数,BLOCK可以在任何时候执行.标识 ...

  5. linux存储--inode与block详解(七)

    基本概念 首先讲下inode和块的基本概念.在Linux系统中,文件由元数据和数据块组成.数据块就是多个连续性的扇区(sector),扇区是文件存储的最小单位(每个512字节).块(block)的大小 ...

  6. inode与block详解

    创建一个文件后,会同时创建一个inode和一个block,inode存放的是文件的属性信息,但是不包括文件名,并存放所对应数据所在的block块的地址的指针:block存放文件的数据,每个block最 ...

  7. linux存储--inode与block详解(八)

    Linux下的格式化命令是mkfs,mkfs在格式化的时候需要制定分区以及文件系统类型.该命令其实就是把我们的连续的磁盘空间进行划分和管理.我在我的机器上执行了一下,输出如下: # mkfs -t e ...

  8. 信息安全-5:RSA算法详解(已编程实现)[原创]

    转发注明出处:http://www.cnblogs.com/0zcl/p/6120389.html 背景介绍 1976年以前,所有的加密方法都是同一种模式: (1)甲方选择某一种加密规则,对信息进行加 ...

  9. HDFS 概念之 block 详解

    一般情况下,任何磁盘都有'最小读写单位'的概念,可以理解为该磁盘的'block'.建立在该磁盘之上的文件系统也有'block'的概念,一般为磁盘'block'大小的整数倍.对于用户来说,这些读写限制都 ...

最新文章

  1. 11大Java开源中文分词器的使用方法和分词效果对比
  2. A query was run and no Result Maps were found
  3. 文字两侧加横线的解决方案
  4. 通过JDK动态代理实现拦截器
  5. 文远知行公布自动驾驶运营路线图:全新升级自动驾驶方案,建立500辆规模的自动驾驶车队...
  6. PLC编程入门基础技术知识
  7. HTML制作简易个人简历(表单)
  8. (十五)TcpClient
  9. Vasp 石墨烯能带计算
  10. 去国企1年后,我后悔了!重回大厂内卷
  11. tp6多表联合查询的几种方式(模糊搜索+分页+字段限制)
  12. reboot Linux 命令使用,linux中的reboot命令的详细解释
  13. Java exception was raised during method invocation
  14. 浙江大学2020计算机考研复试线,浙江大学2020考研复试分数线已公布
  15. 微服务架构-服务网关(Gateway)-服务网关在微服务中的应用
  16. 线性代数28——复矩阵和快速傅立叶变换
  17. 怎么用python读取excel图_Python如何读取excel中的图片
  18. 结构-02. 有理数加法(15)
  19. Hive教程(一)---hive入门
  20. [转载]从100PV到1亿级PV网站架构演变

热门文章

  1. screen 状态为Attached 连不上
  2. 《自己拯救自己》--[英]塞缪尔·斯迈尔斯
  3. photoshop 自学网站
  4. 外置网卡 Mac os 11.0 Big Sur 驱动解决方案
  5. 8月8日科技联播:库比蒂诺想修超级高铁,市长点名要苹果掏钱
  6. Kotlin 的?和!!
  7. 百度博客搜索悄然上线
  8. 2022-2028全球与中国砂当量测定仪市场现状及未来发展趋势
  9. 小e和siri_小E VS siri ?谁更智能?
  10. 2012 苏州瑞晟微电子 面试(共两轮,每次近一个半小时)