iOS的OC的block底层原理(面试来复习下底层)

链接:https://juejin.im/post/6890071440998498311

推荐阅读:阿里、字节:一套高效的iOS面试题之Block

前言

在iOS开发中,相信大家在开发中很频繁使用block,使用block来作为参数,属性,回调等等。虽然知道怎么使用block,但是block在底层的原理是怎样的,应该还是有的人不是很清楚的,这篇文章就是主要介绍block的底层原理的。

1. Block的基础

Block是一个OC的对象,它封装了一段代码,这段代码可以在任何时候执行。Block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。可以嵌套定义,可以定义在方法内部和外部。

1.1 Block种类

在实际使用的Block种根据内存情况,可以将其分为3种

  • _NSConcreteGlobalBlock :全局Block。
  • _NSConcreteMallocBlock:堆Block。
  • _NSConcreteStackBlock:栈Block(copy之前的)。

通过一个小的demo可以分别打印出来

 void (^GlobalBlock)(void) = ^{
        NSLog(@"执行GlobalBlock");
    };
    int a = 10;
    void (^MallocBlock)(void) = ^{
        NSLog(@"执行MallocBlock- %d",a);
    };
    GlobalBlock();
    MallocBlock();
    NSLog(@"我是全局block--%@",GlobalBlock);
    NSLog(@"我是堆block--%@",MallocBlock);
    NSLog(@"我是栈%@",^{
        NSLog(@"执行StackBlock--%d",a);
    });

//打印结果===================
执行GlobalBlock
执行MallocBlock- 10
我是全局block--<__NSGlobalBlock__: 0x10c443090>
我是堆block--<__NSMallocBlock__: 0x600003b937b0>
我是栈<__NSStackBlock__: 0x7ffee37bb458>

但是Block的种类有6种,另外3种是系统级别的很少用到。Block的种类如下:

void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 };

1.2 Block循环引用

在使用Block的时候,最容易遇到的就是循环引用这种错误了。

@property (nonatomic, copy) NSString *name;
@property(nonatomic,copy) void(^testBlock)(void);

    self.name = @"jason";
    self.testBlock = ^{
        NSLog(@"%@",self.name);
    };
    self.testBlock();

其实在Xcode上写这段代码的时候也会直接报

Capturing 'self' strongly in this block is likely to lead to a retain cycle

为什么会循环引用呢?因为self持有了block,block持有了self(self.name),就形成了self->block->self这样的闭环,然后导致循环引用。一般情况下,为了避免引起循环引用会加一个__weak来修饰。

    self.name = @"jason";
    __weak typeof(self) weakSelf = self;
    self.testBlock = ^{
        NSLog(@"%@",weakSelf.name);
    };
    self.testBlock();

此时的weakSelf是在一张弱引用表里面的,此时的持有状态是weakSelf持有了self,self持有了block,block持有了weakSelf,这时候看上去是不是还是一个闭环?但是weakSelf持有self的时候引用次数并没有处理的就是数量没有增加的,所以此时正常还是会执行到析构函数(delloc)。只是指向了self而已。如果在testBlock加一个延迟的然后再打印,此时再打印出来的是weakSelf.name是一个nil

    self.name = @"jason";
    __weak typeof(self) weakSelf = self;
    self.testBlock = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakSelf.name);
        });
    };
    self.testBlock();

因为在延迟的过程中,selfdelloc之后会被回收,此时的weakSelf就会被设置为nil了。为了防止这种情况发生,可以加一个__strong的修饰

       __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });

此时相当于一个临时的strong来持有了weakSelf,在strong还没被销毁的情况下weakSelf也不会销毁,只有打印完之后才会销毁。

2.Block的本质

为了方便接下来的介绍,创建一个block.c文件,通过clang来查看block的底层源码分析,其中block.c的源码如下

#include "stdio.h"
int main(){
    void(^block)(void) = ^{
        printf("TestJason");
    };
    block();
    return 0;
}

在该文件的目录下,用终端输入命令行,就会在该目录下生成一个block.cpp的文件

clang -rewrite-objc block.c -o block.cpp 

通过block.cpp文件可以看到部分的源码

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 = &_NSConcreteSt
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值