iOS-block

一. 查看block内部实现

1.编写block代码

void (^DemoBlock)(int, int) = ^(int a, int b){
    NSLog(@"%d",a+b);
};
DemoBlock(1,3);

输出:2019-01-14 13:01:47.104 iOSWorld[1324:103701] 4
  1. 使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc BlockViewController.m命令查看c++源码,搜索DemoBlock.
void (*DemoBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *, int, int))((__block_impl *)DemoBlock)->FuncPtr)((__block_impl *)DemoBlock, 1, 3);

简化:((void (*)(int, int))是一个函数指针,也就是把后面的代码强转为一个函数指针.删减后得到(从下往上看)

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

static struct __main_block_desc_0 {
 size_t reserved;//为0
 size_t Block_size;//block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
 //__main_block_impl_0和结构体同名, 是一个构造函数,  返回结构体对象
 __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, int a, int b){
  NSLog(a+b);
}
//定义DemoBlock,&__main_block_impl_0是一个函数,有两个参数,第一个为包装的方法,第二个为描述信息
void (*DemoBlock)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
//执行DemoBlock
DemoBlock->FuncPtr(DemoBlock, 1, 3);

代码解释:
1.调用__main_block_impl_0()函数

  • 参数1为__main_block_func_0,也就是我们block里面要做的事情,也就是匿名函数 --> NSLog(@"%d",a+b);
  • 参数2为__main_block_desc_0_DATA,__main_block_desc_0_DATA是一个struct __main_block_desc_0的结构体,结构体参数reserved=0,结构体参数Block_size=sizeof(struct __main_block_impl_0);
  1. 调用后会来到__main_block_impl_0这个结构体的构造函数,将内部impl结构体设置impl.isa = &_NSConcreteStackBlock; impl.Flags = flags;impl.FuncPtr = fp;将内部__main_block_desc_0结构体的Desc = desc;

所以:block是封装了函数调用以及函数调用环境的对象

3.分析(注意!!!!!标注的代码)

  • impl.isa = &_NSConcreteStackBlock;
    block有一个isa指针,说明block也是一个OC对象.
  • impl.FuncPtr = fp
    记录一下block里面的函数
  • Desc = desc;
    记录该block的描述

4.拓展

- (void)test
{
   void(^DemoBlock)(void) = ^{
   };
   
   NSLog(@"%@",[DemoBlock class]);
   NSLog(@"%@",[[DemoBlock class] superclass]);
   NSLog(@"%@",[[[DemoBlock class] superclass] superclass]);
   NSLog(@"%@",[[[[DemoBlock class] superclass] superclass] superclass]);
}

输出:2019-01-14 16:16:15.953 iOSWorld[1542:128655] __NSGlobalBlock__
    2019-01-14 16:16:15.953 iOSWorld[1542:128655] __NSGlobalBlock
    2019-01-14 16:16:15.954 iOSWorld[1542:128655] NSBlock
    2019-01-14 16:16:15.954 iOSWorld[1542:128655] NSObject

block有superclass,说明就是OC对象.

二.block变量捕获

  • 对基本数据类型的局部变量截获其值.
  • 对对象类型的局部变量连同所有权修饰符一起截获,如果以strong修饰,捕获的时候底层就retain一次,如果是weak修饰的,就不retain
    self也是局部变量,所以会捕获self
  • 以指针形式截获静态局部变量
    虽然static修饰的局部变量也是永驻内存,但因为它是局部的,不像全局变量一样谁都可以访问,所以捕获它的指针才能找到它.
  • 不截获全局变量、静态全局变量

2.1 block捕获局部变量

demo1

- (void)test
{
   int a = 10;
   void(^DemoBlock)(void) = ^{
       NSLog(@"%d", a);
   };
   a = 20;
   DemoBlock();
}

输出:2019-01-14 15:00:38.339 iOSWorld[1446:119920] 10

解释:定义完a=10后,编译器开始编译DemoBlock,这时候DemoBlock内部已经捕获了a的值.
继续执行a=20,继续执行DemoBlock()会打印出已经捕获的a=10,所以打印为10.

总结: 对于局部变量,一出作用域就会被销毁了,所以block会及时捕获局部变量并copy一份值到block内部.

2.2 block捕获全局变量

demo2

int a = 10;
- (void)test
{
   void(^DemoBlock)(void) = ^{
       NSLog(@"%d", a);
   };
   a = 20;
   DemoBlock();
}

输出:2019-01-14 15:26:38.208 iOSWorld[1478:122759] 20

解释:int a = 10,接下来编译DemoBlock,继续执行a=20,继续执行DemoBlock()的时候会调用NSLog(@"%d", a),因为a是全局变量,这时候只需要拿到a打印就行了.

总结: 对于全局变量,即使出了作用域也不会被销毁,所以block不会捕获全局变量,啥时候用啥时候取就行.下划线_age其实是self->age,所以会先捕获self

三.3种block的内存存放区域

block分类
_NSConcreteStackBlock:在栈上创建的Block对象
_NSConcreteMallocBlock:在堆上创建的Block对象
_NSConcreteGlobalBlock:全局数据区的Block对象(data区)

  • 没有捕获局部变量的block为GlobalBlock
  • 捕获了局部变量但是没有进行block复制则为StackBlock---栈block容易出错,因为有可能被销毁了
  • 其他的block基本都为MallocBlock(堆block)

block复制
block从栈复制到堆的时候,会调用_Block_object_assign()强引用
block在堆上销毁的时候,会调用_Block_object_dispose()release引用的对象

在ARC有效时,大多数情况下编译器会进行判断,自动生成将Block从栈上复制到堆上的代码,以下几种情况栈上的Block会自动复制到堆上:

  • 调用Block的copy方法 globalBlock copy仍为globalBlock,stackBlock copy后为mallocBlock,mallocBlock copy后引用计数+1
  • 将Block作为函数返回值时
  • 将Block赋值给__strong修饰的变量时
  • 向Cocoa框架含有usingBlock的方法 或者 GCD的API传递Block参数时

所以如果不怕被销毁,比如该block当函数用一次,那么block不一定非得用copy.
__block可以解除循环引用

四.block修改变量

???为什么不可以在block内部修改局部变量

因为:局部变量出了大括号就会销毁,有可能你修改的时候它已经是nil了.
但是:我们可以使用__block修饰局部变量.

__block int a = 10;
void (^DemoBlock)(void) = ^(){
    a = 20;
    NSLog(@"%d",a);
};
DemoBlock()
输出:---------> 20
  1. 使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc main.m命令查看c++源码,搜索DemoBlock.
void (*DemoBlock)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
  1. 发现__main_block_impl_0实现里面多了一个__Block_byref_a_0开头的变量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
}
  1. 看看__Block_byref_a_0,发现它里面有个a,还有个自己类型的__forwarding-->__Block_byref_a_0 *__forwarding
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

4.看看__main_block_func_0这个参数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//创建了一个__Block_byref_a_0类型的和我们定义一样的同名变量a
  __Block_byref_a_0 *a = __cself->a; 
//将新a的__forwarding里面的a设置为20
(a->__forwarding->a) = 20;
//打印新a的__forwarding里面的a
NSLog(a->__forwarding->a);
}

5.将自己(__Block_byref_a_0)的地址值传到自己的第二个参数__forwarding里面,也就是说__forwarding指向的还是自己

__Block_byref_a_0 *__forwarding = (__Block_byref_a_0 *)&a

__forwarding存在的意义:不论在栈上还是堆上,都可以顺利的访问同一个__block变量.

总结:__block原理编译器会将__block变量包装成一个对象,并把局部变量赋值给该对象,这样就能修改对象的变量
你以为你修改的还是那个局部变量,殊不知已经是__block自己生成的对象啦

拓展:__block __weak Person *p = [Person new]
上面代码:生成一个自己的对象block_p包裹了p,
而且block强引用了block_p,但是因为有weak的存在,block_pp是弱引用的.

五.block的内存管理

  • stackBlock对所有的局部变量都是弱引用
  • mallocBlock :
    1.对__block或者__Strong使用_Block_object_assign()进行强引用.
    2.对__weak使用_Block_object_assign()进行弱引用.
    当block移除时,使用_Block_object_dispose()来释放局部变量.

六.解决循环引用的新方法

将在Block内要使用到的对象(一般为self对象),以Block参数的形式传入,Block就不会捕获该对象,而将其作为参数使用,其生命周期系统的栈自动管理,不造成内存泄露。
即原来使用__weak的写法:

__weak typeof(self) weakSelf = self;
self.blk = ^{
    __strong typeof(self) strongSelf = weakSelf;
    NSLog(@"Use Property:%@", strongSelf.name);
    //……
};
self.blk();

改为Block传参写法后:

self.blk = ^(UIViewController *vc) {
    NSLog(@"Use Property:%@", vc.name);
};
self.blk(self);

__weak typeof(self)      weakSelf = self;
__weak UIViewController *weakSelf = self;

你可能感兴趣的:(iOS-block)