iOS爱上底层-Block实现与原理
您已经看过
[清空]
    fa-home|fa-star-o
    当前位置:网站买卖_私人做网站_网站交易>权重网站>iOS爱上底层-Block实现与原理

    iOS爱上底层-Block实现与原理

    权重网站cntkd2022-08-27 23:13410A+A-

    原标题:iOS爱上底层-Block实现与原理

    👇👇关注后回复 进群 ,拉你进程序员交流群👇👇

    作者丨淡墨飘香

    来源:掘金

    链接:

    前言

    很多人在面试的时候都会被问到Block,那么Block分为哪几种类型呢?其实Block共有6种类型,其中三种常用级别,分别是: _NSConcreteGlobalBlock _NSConcreteStackBlock _NSConcreteMallocBlock ,三种系统级别 ,分别是 _NSConcreteAutoBlock _NSConcreteFinalizingBlock _NSConcreteWeakBlockVariable ,本次只介绍3种常用类型。

    Block常见的三种类型

    • 全局Block( __NSGlobalBlock__ )
    • 堆Block( __NSMallocBlock__ )
    • 栈Block( __NSStackBlock__ )
    Block的本质是什么?

    把左边OC(左边)的代码编译成C++(右边),我们会发现原本的block在C++里面被拆解成一个叫 __main_block_impl_0 的构造函数,我们在往上看可以发现,它其实就是一个结构体对象。而这个构造函数传了两个值 __main_block_func_0 和 __main_block_desc_0_DATA 。 __main_block_func_0 :就是Block所执行的内容,在C++底层则变成了一个函数。 __main_block_desc_0_DATA :有两个成员: reserved 和 Block_size 。主机名是什么

    Block是如何捕获外界变量的?

    我们创建一个局部变量a=10,然后看C++代码,可以发现在 __main_block_impl_0 结构体中创建了一个属性int a,并且在函数实现中创建了一个临时变量a,将结构体中的属性赋值给他。由于这次拷贝是值拷贝,所以在函数里面不能对当前的属性进行修改。为了能够改变a的值,要加上 __block ,如下图:

    我们可以发现 __block 修饰的临时变量在C++中变成了一个结构体 __Block_byref_a_0 。并且在调用函数的时候,传的是a的指针,并且在函数的实现中, __Block_byref_a_0 *a = __cself->a 则是进行一次指针拷贝,所以用 __block 修饰的变量可以在Block内部进行修改。

    解决Block循环引用的几种方式 //循环引用

    self.name = @"1234";

    self.block = ^{

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

    };

    self.block;主机名是什么

    很多人都知道Block会引起循环引用,如同上面这段代码,当self持有block,block持有self,就产生了循环引用。接下来就介绍3种解决循环引用的方式

    1、 __block :我们只需要在self.name的下面创建一个中介者vc( __block ViewController *vc = self ),然后将block里面的self.name替换成vc.name,用完之后将vc置为nil即可。(俗称中介者模式)

    // 循环引用

    self.name = @"1234";

    __block ViewController *vc = self;

    self.block = ^{

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

    };

    self.block;

    2、传参:我们只需要将当前的self当做参数传入到block里面,block内部会创建一个临时变量vc,此时我们就打破了互相持有,就会解决循环引用的问题了

    // 循环引用

    self.name = @"1234";

    self.block = ^(ViewController *vc){

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

    };

    self.block(self);主机名是什么

    3、weak and strong:这种方式也是最多人用的,既创建weak,但是有一个问题,如果添加一个延迟执行,weakSelf就会提前释放,导致访问不到外界变量,所以我们又需要在blcok里面strong一下

    // 循环引用

    self.name = @"1234";

    __weak typeof(self) weakSelf = self;

    self.block = ^{

    __strong typeof(weakSelf) 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打破循环引的原理

    在汇编里面,Block会掉用一个叫 _Block_object_assign 的api。而解决循环引用的关键之处也在下面的代码中。

    void _Block_object_assign(void *destArg, const void *object, const int flags) {主机名是什么

    const void **dest = (const void **)destArg;

    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {

    caseBLOCK_FIELD_IS_OBJECT:

    _Block_retain_object(object);

    *dest = object;

    break;

    caseBLOCK_FIELD_IS_BLOCK:

    *dest = _Block_copy(object);

    break;

    caseBLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:

    caseBLOCK_FIELD_IS_BYREF:

    *dest = _Block_byref_copy(object);

    break;

    caseBLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:

    caseBLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:

    *dest = object;主机名是什么

    break;

    caseBLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:

    caseBLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:

    *dest = object;

    break;

    default:

    break;

    }

    }

    以 __weak 为例。我们为了解决循环引用,通常都会写上这样一句代码: __weak typeof(self) weakSelf = self; ,将self加入到弱引用技术表,但是这并不足以解释打破循环引用的原因。而是 weakSelf 和 self 并不是同一个点,他们指向的空间都是同一个。而最关键的是这一行代码 const void **dest = (const void **)destArg; ,他copy的是 weakSelf 的指针地址,而不是self,所以 weakSelf 被释放的时候,不会影响到self。

    Block是如何从栈到堆? void *_Block_copy(const void *arg) {主机名是什么

    struct Block_layout *aBlock;

    if(!arg)returnNULL;

    // The following would be betterdoneas a switch statement

    aBlock = (struct Block_layout *)arg;

    if(aBlock->flags & BLOCK_NEEDS_FREE) {

    // latches on high

    latching_incr_int(&aBlock->flags);

    returnaBlock;

    }

    elseif(aBlock->flags & BLOCK_IS_GLOBAL) {

    returnaBlock;

    }

    else{

    // Its a stack block. Make a copy.

    struct Block_layout *result =

    (struct Block_layout *)malloc(aBlock->deor->size);

    if(!result)returnNULL;

    memmove(result, aBlock, aBlock->deor->size); // bitcopy first主机名是什么

    if __has_feature(ptrauth_calls)

    // Resign the invoke pointer as it uses address authentication.

    result->invoke = aBlock->invoke;

    endif

    // reset refcount

    result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed

    result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1

    _Block_call_copy_helper(result, aBlock);

    // Set isa last so memory analysis tools see a fully-initialized object.

    result->isa = _NSConcreteMallocBlock;

    returnresult;

    }

    }

    这里就是当前Block从栈->堆的过程。1、 aBlock->flags & BLOCK_NEEDS_FREE 会判断当前Block的引用计数器,因为block的引用计数器是不受runtime下层处理的,所以它由自己来进行管理。而且这里的引用计数器是+2,而不是+1,因为+1会对别的属性有影响,所以这里是+2(如下)。主机名是什么

    static int32_t latching_incr_int(volatile int32_t *where) {

    while(1) {

    int32_t old_value = *where;

    if((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {

    returnBLOCK_REFCOUNT_MASK;

    }

    if(OSAtomicCompareAndSwapInt(old_value, old_value+2,where)) {

    returnold_value+2;

    }

    }

    }

    2、 aBlock->flags & BLOCK_IS_GLOBAL 判断当前的Block是否为全局变量,是的话就直接返回

    3、else就是将block复制到栈中,首先会创建一个新的结构体result,然后将旧的Block全部拷贝到新的Block中,然后isa就被标记为 _NSConcreteMallocBlock 。主机名是什么

    总结

    1、解决block循环引用的思路就是中介者模式。

    2、Block的本质就是结构体

    3、当Block捕获到外界变量时,他就会从全局block变成堆block

    -End-

    最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

    面试题】即可获取

    在看点这里好文分享给更多人↓↓返回搜狐,查看更多

    责任编辑:

    iOS爱上底层-Block实现与原理》由《网站买卖_私人做网站_网站交易》整理呈现,请在转载分享时带上本文链接,谢谢!

    支持Ctrl+Enter提交
    网站买卖_私人做网站_网站交易 © All Rights Reserved.  Copyright Your WebSite.Some Rights Reserved.
    Powered by 网站交易