Block

2021-02-03  本文已影响0人  生产八哥

Block

Block也算是个类,一共三种类型:

 int a = 10;
    
void(^globalBlock)(void) = ^{
      NSLog(@"global");
};
   
void(^mallocBlock)(void) = ^{
      NSLog(@"malloc - %d", a);//引用a
 };
    
//对堆block进行__weak修饰就会变成栈区block。
void(^__weak stackBlock)(void) = ^{
     NSLog(@"stack - %d", a);//引用a
};
NSLog(@"%@", globalBlock); 全局block
NSLog(@"%@", mallocBlock); 堆block   
NSLog(@"%@", stackBlock);  栈block

非全局gloabl的block在汇编时都会默认赋为栈stack block,因为这时候是编译阶段,没有获取内存空间。malloc block是stack在运行时进行_Block_copy拷贝到堆区得到的,拷贝的前面并没有申请空间的相关代码,所以只能是栈区block。


通过xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c,将block.c 编译成 block.cpp,其中block在底层被编译成了以下的形式

int main(){
    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);
    return 0;
}

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("CJL");
} //block等于__main_block_impl_0,是一个函数

void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));//构造函数

block->FuncPtr(block);//block调用


//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;// isa 保存 Block 结构体实例
    impl.Flags = flags;// 用于保存 Block 结构体的实例指针
    impl.FuncPtr = fp;// 函数指针
    Desc = desc;
  }
};

//**block的结构体类型**
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;//block代码块存储的位置
};

Block本质是一个对象, 对象中封装了一个函数以及函数执行的上下文;Block的调用本质是函数Funptr的调用,因为底层把block的实现的代码部分赋值给了__block_impl的Funptr,也就是调用的函数指针;


__block

__block说明符,其实会把自动变量包含到一个结构体中。

(__Block_byref_age_0 *)&age
 __Block_byref_age_0 *age; // 结构体指针引用

__Block_byref_age_0 *age = __cself->age; // 通过结构体的self指针拿到age结构体的指针
 (age->__forwarding->age) = 18; // 通过age结构体指针修改age值

block捕获外界变量时,在内部会自动生成同一个属性来保存。如果变量用__block修饰,内部会传递此变量的内存地址,即指针浅拷贝,达到在内部修改外部变量的目的。如果没有__block,那么就是值拷贝,即深拷贝,内外指向不同的内存空间。

weakSelf 和 self 指向同一片内存空间,且使用__weak不会导致self的引用计数发生变化
__block修饰的变量在汇编层会封装一层结构体Block_byref,结构体保存了变量的指针地址,相当于指针(浅)拷贝,创建的对象a与传入对象的a指向同一片内存空间。没有被__block修饰的却被block捕获的值相当于值(深)拷贝。Block_byref其中的forwarding 栈上的指向堆上的对象,堆上的指向自身,这样就能找到正确存储的block位置了。

weak修饰 虽然也可以解决循环引用,但是如果block任务耗时,快速返回上个界面时,self就为空了,不会崩溃,相当于白白浪费了性能。所以需要weak-strong-dance

self不持有block时,blcok 中截获 self,一般会延长 self 的生命周期(至少到 block 释放后,才会释放 self)。如果 self 同时持有 block,则会导致循环引用。在日常 block 开发中我们的重点都放在了预防循环引用上,而循环引用之外的延长 self 的生命周期是很容易忽略的一个点。需要注意的是 block 最后都会被执行,不管 UIViewController 是否存活。所以如果需要页面释放后,异步block回调代码不执行,需要进行self空置判断,如果为空就return;

block底层真正结构体:Block_layout
struct Block_layout {
    //指向block类型的类
    void *isa;//8字节
    //用来作标识符的,类似于isa中的位域
    volatile int32_t flags; // contains ref count 4字节
    //保留信息
    int32_t reserved;//4字节
    //函数指针,指向具体的block实现的调用地址
    BlockInvokeFunction invoke;
    //block附加信息,签名等
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

block中可以执行代码块是因为代码块赋给了Block_laout结构体里的invoke。

总结:当用__block修饰的对象类型时使用block整个过程为:


被__block修饰的对象的三次copy

外部变量拷贝时调用的方法是_Block_object_assign

进入_Block_object_assign源码

【第一层】通过_Block_copy实现对象的自身拷贝,从栈区拷贝至堆区

【第二层】通过_Block_byref_copy方法,将对象拷贝为Block_byref结构体类型

【第三层】调用_Block_object_assign方法,对__block修饰的当前变量的拷贝

byref:参数传递


block可以用strong修饰吗?

在ARC中可以,因为在ARC环境中的block只能在堆内存或全局内存中,因此不涉及到从栈拷贝到堆中的操作.

在MRC中不行,因为要有拷贝过程.如果执行copy用strong的话会crash, strong是ARC中引入的关键字.如果使用retain相当于忽视了block的copy过程.


hook block

iOS中hook oc对象用系统的method swizzer可以很方便的做到。但是block作为特殊的oc对象,方法交换并不适用。hook block的本质就是找到invoke函数指针和block的signature方法签名

网上大致有两种实现方式


timer的循环引用

下面这种方式是打不破循环引用的,在页面pop后,timer还会不断的执行任务。

__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
 //weakSelf 和 self 的内存地址&是不一样,但都指向同一片内存空间。

造成这个现象的原因是weakSelf只是打破了self->timer-self这一条线的循环引用,但是不要忘了runloop同时也对timer强持有。RunLoop对整个 对象的空间有强持有,runloop没停,timer 和 weakSelf是无法释放的。
此时timer这个现象和block的循环引用,block捕获的是 weakself对象指针地址,和self无关。

解决方案:只要在恰当的时机执行[self.timer invalidate]; self.timer = nil;即可,或者才用中介者模式,target通过NSProxy虚基类,可以交给其子类实现。

这样做的主要目的是将强引用的注意力转移成了消息转发虚基类只负责消息转发,即使用NSProxy作为中间代理、中间者。


优秀文章1
优秀文章2

上一篇下一篇

猜你喜欢

热点阅读