OC底层原理探索之block分析上

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

block类型

GlobalBlock:位于全局区,在block内部不使用外部变量,或者只使用静态变量或者全局变量
MallocBlock:位于堆区,在block内部使用局部变量或者OC属性,并且赋值给强引用或者copy修饰的变量
StackBlock:位于栈区,与堆区的block一样,可以在内部使用局部变量或者OC属性,但是不能赋值给强引用或者copy修饰的变量

以下是上面三种类型的block创建方式
不使用外部变量:

   void (^block)(void) = ^{ };
复制代码

image.png
使用了局部变量,并且强引用:

   int a = 10;
   void (^block)(void) = ^{
       NSLog(@" - %d",a);
   };
复制代码

image.png

使用了局部变量,但是没有强引用:

   int a = 10;
   void (^ __weak block)(void) = ^{
       NSLog(@"- %d",a);
   };
复制代码

image.png
block默认是强引用,指向的是^{}的内存空间。

block捕获外部变量

- (void)blockDemo2{
    
    NSObject *objc = [NSObject new];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
    
    void(^strongBlock)(void) = ^{ // 这里实际上是strongBlock对objc捕获 所以这里 +1
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    strongBlock();

    void(^__weak weakBlock)(void) = ^{ // + 1
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    weakBlock();
    
    void(^mallocBlock)(void) = [weakBlock copy];
    mallocBlock();
    
}
复制代码

最终,对外部的objc引用计数是多少呢?
image.png
分析:这里的疑问点主要是在strongBlock()这里,按照我们的常识strongBlock对objc捕获所以这里 +1应该是2,那么为什么打印成了3?
原因这里的strongBlock是一个堆区的block,它是从栈上拷贝一份到了堆区,所以这里也要+1,具体的我们下面结合着源码libclosure分析。
所以上面的strongBlock = __weak weakBlock + [weakBlock copy]

block内存拷贝

- (void)blockDemo1{
    int a = 0;
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
  
    // 深浅拷贝
    id __strong strongBlock = weakBlock;
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;
    strongBlock1();
}
复制代码

文中的_LGBlock是一个自定义的block,这里的重点关注的是block的内存拷贝,所以这个细节可以忽略。运行上述代码会崩溃。因为当前的blc->invoke = nil, 也侧面验证了blcweakBlock指向的是同一片内存空间,所以当blc置为nil的时候,此时崩溃。那么怎么修改呢,其实我们可以这样做。

// 深浅拷贝 此时这里是个堆区block 值跟weakBlock一样,但是是一个全新的内存地址
id __strong strongBlock = [weakBlock copy];
复制代码

block堆栈释放差异

- (void)blockDemo3{
    int a = 0;
    void(^__weak weakBlock)(void) = nil;
    {
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"---1");
    }
    weakBlock();
}
复制代码

image.png
在代码块中栈区strongBlock赋值给了栈区weakBlock
那如果我们这样修改呢?

- (void)blockDemo3{
    int a = 0;
    void(^__weak weakBlock)(void) = nil;
    {
        void(^strongBlock)(void) = ^{
            NSLog(@"---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"---1");
    }
    weakBlock();
}
复制代码

此时运行,程序崩溃在了weakBlock()这里。此时的strongBlock是一个堆区的block,堆区的block赋值给weakBlock时候把该Block也变成了堆区的block
image.png
但是strongBlock在出了作用域之后释放了,所以此时的weakBlock指向的地址也为空,所以报错。其实主要是要区分堆栈释放的区域。

block拷贝到堆区

  • 手动copy
  • block作为返回值
  • 被强引用或者copy修饰
  • 系统的API包含usingBlock

block循环引用

下面的例子由于self -> block -> self所以造成了循环引用。

    self.block = ^(void){
        NSLog(@"%@", self.name);
    };
    self.block();
复制代码

解决的办法是加一个__weak:self -> block -> weakSelf(nil)

    __weak typeof(self) weakSelf = self;
    self.block = ^(void){
         NSLog(@"%@",weakSelf.name);
    };
    self.block();
复制代码

image.png
那么我再加一个延时操作呢?

    __weak typeof(self) weakSelf = self;
    self.block = ^(void){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakSelf.name);
        });
    };
    self.block();
复制代码

image.png
此时的weakSelf.name已经为空。还没有来得及反应就已经释放了。那这个怎么解决呢?

    __weak typeof(self) weakSelf = self;
    self.block = ^(void){
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    self.block();
复制代码

image.png
weak``strong合理的应用安排。block内部的strongSelf是一个临时变量,出了作用域就释放。

解决办法二手动释放: self -> block -> self -> block

    __block ViewController *vc = self;
    self.block = ^(void){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil;
        });
    };
    self.block();
复制代码

image.png
解决办法三: 引入参数,就不会捕获self

   self.block = ^(ViewController * vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
        });
    };
    self.block(self);
复制代码

image.png

weak相关面试题

以下代码块会造成循环引用嘛?

static ViewController *staticSelf_;
- (void)blockWeak_static {
    // 是同一片内存空间
    __weak typeof(self) weakSelf = self;
    staticSelf_ = weakSelf;
}
复制代码

运行,发现dealloc并没有走,所以此时造成了循环引用。分析:weakSelfself指向的是同一片内存空间。
image.png
此时staticSelf_ = weakSelf后,全局变量持有了self,所以不会释放。

接着,下面的会造成循环引用吗?

- (void)block_weak_strong {
    __weak typeof(self) weakSelf = self;
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf;
        weakSelf.doStudent = ^{
            NSLog(@"%@", strongSelf);
        };
       weakSelf.doStudent();
    };
   self.doWork();
}
复制代码

运行,发现dealloc并没有走,所以此时也造成了循环引用。分析:在NSLog(@"%@", strongSelf);这一行的时候,block对strongSelf进行捕获,此时strongSelf.count+1,所以不会被释放。
解决方式:不引入这个strongSelf
image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享