浅谈Block上

258 阅读5分钟
本节内容针对block的一些实例进行分析,block的分类,循环引用等。先有一个大致的印象,然后再去分析block的底层结构。

block的分类

  • NSGlobalBlock
      1. 位于全局区
      1. 在Block内部不使用外部变量,或者只使用静态变量和全局变量
static int a = 12;

void (^globalBlock)(void) = ^{

    NSLog(@"---%d", a);

};
// <__NSGlobalBlock__: 0x1082af100>
NSLog(@"%@",globalBlock);
  • NSMallocBlock
    • 位于堆区
    • 在block内部使用变量或者OC属性,并且赋值给强引用或者Copy修饰的变量
int a = 10;

void (^block)(void) = ^{

    NSLog(@"haha - %d",a);

};

NSLog(@"%@",block);
  • NSStackBlock
    • 位于栈区
    • 与MallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或Copy修饰的变量
int a = 10;

void (^__weak block)(void) = ^{

    NSLog(@"haha - %d",a);

};

NSLog(@"%@",block);

栈Block拷贝到堆Block的四种情况(block里面有变量或者oc属性的情况下

  1. 手动copy
  2. block作为返回值或参数
  3. 被强引用或者被Copy修饰
  4. 系统API包含usingBlock

捕获外部变量-对外部变量的引用计数处理

NSObject *objc = [NSObject new];

NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
 // block 底层源码
 // 捕获 + 1
 // 堆区block
 // 栈 - 内存 -> 堆  + 1
void(^strongBlock)(void) = ^{ 

    NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));//3

};

strongBlock();

void(^__weak weakBlock)(void) = ^{ // + 1

    NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));//4

};

weakBlock();

void(^mallocBlock)(void) = [weakBlock copy];//5

mallocBlock();

对于

void(^strongBlock)(void) = ^{ 

    NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));//3

};

等于


void(^__weak weakBlock)(void) = ^{ // + 1

    NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));//4

};

weakBlock();

void(^mallocBlock)(void) = [weakBlock copy];//5

这两个的效果,一个是捕获变量+1,然后copy到堆上面+1,所以是3。这个在下一节源码分析中会讲到

对block copy的初探

我们知道block用copy修饰,当然arc下copy和strong效果相同,但是为什么要用copy修饰呢,为什么要将block拷贝到堆上面呢 我们先看一个demo

- (void)blockDemo1{

    int a = 0;

    void(^ __weak weakBlock)(void) = ^{

        NSLog(@"-----%d", a);

    };
    //_DWBlock就是一个模拟源码对block的封装
    struct _DWBlock *blc = (__bridge struct _DWBlock *)weakBlock;

    // 深浅拷贝

    id __strong strongBlock = weakBlock;

    blc->invoke = nil;

    void(^strongBlock1)(void) = strongBlock;

    strongBlock1();


}

这个demo运行会崩溃,因为我们把blc的invoke置为了nil,所以下面调用的时候就找不到了,所以报错 截屏2021-08-21 下午2.35.53.png 在上面的weakBlock和StrongBlock都是栈Block 如果改为id __strong strongBlock = [weakBlock copy];就可以正常运行,对block进行了copy,strongBlock是堆上的block,所以之后修改了blc但是和strongBlock没有任何关系。

demo2

- (void)blockDemo3{

// 压栈 内存平移

NSObject *a = [NSObject alloc];

void(^__weak weakBlock)(void) = nil;

{//一个代码块


    void(^__weak strongBlock)(void) = ^{

        NSLog(@"---%@", a);

    };

    weakBlock = strongBlock;

    NSLog(@"1 - %@ - %@",weakBlock,strongBlock);

    }

    weakBlock();

}
//打印结果
//**1 - <__NSStackBlock__: 0x7ffee5f47450> - <__NSStackBlock__: 0x7ffee5f47450>**
//**---(null)**

我们知道在栈上面,内存由系统回收,weakBlock()的作用域在整个函数内,所以weakBlock()的时候会执行

我们把void(^__weak strongBlock)(void) = ^{改为void(^ strongBlock)(void) = ^{ 此时会崩溃,输出1 - <__NSMallocBlock__: 0x600003334840> - <__NSMallocBlock__: 0x600003334840>崩溃在weakBlock()-Thread 1: EXC_BAD_ACCESS (code=1, address=0x10); 截屏2021-08-21 下午2.47.31.png 这个时候是堆block我们看到出了代码块之后,strongBlock就没有了,此时weakBlock也就为空了,所以后面在调用会崩溃。

Block的循环引用

我们知道,大多数情况下,A中持有了B,当A释放到时候,B也释放,在有些情况下,A中持有了B,B中也持有了A,然后就造成一个相互的引用循环。 eg:

@property (nonatomic, strong) DWBlock block;

@property (nonatomic, copy) NSString *name;

self.block = ^{

    NSLog(@"%@", self.name);

};
self.block();

在dealloc里面打印NSLog(@"dealloc 来了");发现当页面退出的时候,并没有走dealloc。 这种情景比较熟悉,self持有了block,block里面又持有了self,造成一个循环引用。 我们都知道加上__weak来破除循环引用

__weak typeof(self) weakSelf = self;

self.block = ^{

    NSLog(@"%@", weakSelf.name);

};

如果block里面有一个耗时操作,这个时候会怎么样呢

__weak typeof(self) weakSelf = self;

self.block = ^{

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    NSLog(@"%@", weakSelf.name);

    });

};

self.block();

//**dealloc 来了**
//**(null)**

这个时候就会出现问题,先走了dealloc,weakSelf被释放,打印的值不准确。

当然我们可以对weakSelf进行一个强持有,从而保证打印的准确性。

__weak typeof(self) weakSelf = self;

self.block = ^{

    __strong typeof(self) strongSelf = weakSelf;

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    NSLog(@"%@", strongSelf.name);

    });

};

self.block();

这也是一些block里面用到__strong的情况之一,__strong typeof(self) strongSelf = weakSelf;虽然是个强引用,但是它是一个局部变量,出了作用域之后就会被回收掉,所以这个里面不存在循环引用的情况。对于循环引用的情况也要具体情况具体分析,有时候会用到__strong保持对象不会回收。

我们也可以在合适的时候将self置为nil来破除循环引用

__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();

前面产生循环引用的原因是因为block对里面的变量进行了持有,那么我们将vc作为参数来处理,就可以巧妙的避免了这个问题的产生

self.block = ^(ViewController *vc){

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    NSLog(@"%@",vc.name);

    });

    };

self.block(self);

上面我们介绍了三种破除循环引用的方式,下面我们来分析下下面的示例是否会产生循环引用。 eg1:

static ViewController *staticSelf_;

- (void)blockWeak_static {

    // 是同一片内存空间

    __weak typeof(self) weakSelf = self;

    staticSelf_ = weakSelf;

    // staticSelf_ -> weakSelf -> self

}

由于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();

}

虽然__strong typeof(self) strongSelf = weakSelf;strongSelf是一个临时变量,但是 weakSelf.doStudentstrongSelf有一个持有,导致它引用计数+1,它持有了weakSelf,weakSelf持有了self,当页面退出的时候,它里面的引用计数还不能被清空回收,所以也不会走dealloc。