iOS之block的实现原理(二)

本文详细解析了Block的三种类型:全局Block、栈Block和堆Block。深入介绍了Block的实现原理,包括Block的结构体组成、变量作用域及内存管理等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

block的几种类型:

https://www.jianshu.com/p/6568f245deb2

  • NSGlobalBlock
    block 内部没有引用外部变量的 Block 类型都是 NSGlobalBlock 类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。
  • NSStackBlock
    block 内部引用外部变量,retain、release 操作无效,存储于栈区,变量作用域结束时,其被系统自动释放销毁。MRC 环境下,[[mutableAarry addObject: blockA],(在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在其所在作用域结束也就是函数出栈后,从mutableAarry中取到的blockA已经被回收,变成了野指针。正确的做法是先将blockA copy到堆上,然后加入数组。支持copy,copy之后生成新的NSMallocBlock类型对象。
  • NSMallocBlock
    如上例中的_block[blockA copy]操作后变量类型变为 NSMallocBlock,支持retain、release,虽然 retainCount 始终是 1,但内存管理器中仍然会增加、减少计数,当引用计数为零的时候释放(可多次retain、release 操作验证)。copy之后不会生成新的对象,只是增加了一次引用,类似retain,尽量不要对Block使用retain操作。



 

block的实现原理:

 OC代码: 

int a=10;

  void (^blk)(void) = ^(){

printf("This is a block.");

};

    

上面的OC代码被转化成了C语言的代码后,可以看出:block被转化成了指向__main_block_impl_0结构体的指针。这个结构体包含两个__block_impl和__main_block_desc_0两个结构体,和一个方法。

 

    struct__main_block_impl_0 {

        struct __block_impl impl;

        struct __main_block_desc_0* Desc;

 

              int a;

   //下面这个是结构体构造函数 ,用来初始化变量__block_impl impl__main_block_desc_0    

__main_block_impl_0(void *fp,struct __main_block_desc_0 *desc,int _a, int flags=0):a(_a) {

            impl.isa = &_NSConcreteStackBlock;

            impl.Flags = flags;

            impl.FuncPtr = fp;

            Desc = desc;

                  }

    };

 

 

OC被转化成了以下C语言代码:

 

int a=10;

 

void (*blk)(void) =(void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA,a);

 

简化的C语言代码:

 

   void (*blk)(void) = &__main_block_impl_0(

     (void *)__main_block_func_0

      &__main_block_desc_0_DATA

);

   

//__main_block_impl_0 函数 参数:参数1 __main_block_func_0指针参数2 __main_block_desc_0_DATA :描述结构体大小 参数就是block之外的变量 a

 

//__main_block_func_0函数,即block对应的函数体。该函数接受一个__cself参数,即对应的block自身。

static void__main_block_func_0(struct __main_block_impl_0 *__cself) {

int a=__cself->a;

        printf("This is a block.");

    }

 

 

   

 struct __block_impl {

       void *isa;//isa指针,如果我们对runtime了解的话,就明白isa指向Class的指针  (_NSConcreteStackBlock,_NSConcreteMallocBlock_NSConcreteGlobalBlock)

 

   int Flags;//Flags,当block被copy时,应该执行的操作

   int Reserved;//Reserved为保留字段

   void *FuncPtr;//FuncPtr指针,指向block内的函数实现

    };

 

 

    staticstruct __main_block_desc_0 {

        size_t reserved;//reserved为保留字段默认为0

        size_t Block_size;//Block_size为sizeof(struct __main_block_impl_0),用来表示block所占内存大小。因为没有持有变量,block大小为impl的大小加上Desc指针大小

    } __main_block_desc_0_DATA = {0, sizeof(struct __main_block_impl_0)};

//__main_block_desc_0_DATA为__main_block_desc_0的一个结构体实例
这个结构体,用来描述block的大小等信息。如果持有可修改的捕获变量时(即加__block),会增加两个函数(copy和dispose)

 

 

 

OC代码:

 

blk();

 

转成C语言:

 

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

简化的C语言代码:

blk->FuncPtr(blk);block的创建和调用,可以看出执行block就是调用一个以block自身作为参数的函数,这个函数对应着block的执行体

 

//如果一个block外部的变量加了 __block修饰系统就会创建一个结构体内部封装了变量的地址

    

    __blockint a = 10;

 

    //结构体__Block_byref_a_0的变量

    struct __Block_byref_a_0 {

        void *__isa;

        __Block_byref_a_0 *__forwarding;//保存自己的地址,____forwarding,指向自己的指针,当从栈copy到堆时,指向堆上的block

        int __flags;// 0

        int __size; //当前结构体的大小

        int a;

    };

    __attribute__((__blocks__(byref)))__Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0, sizeof(__Block_byref_a_0),10};

 

 

 

 

 

block的类型用_NSConcreteStackBlock来表示,表明这个block位于栈中。同样地,还有_NSConcreteMallocBlock_NSConcreteGlobalBlock

由于block也是NSObject,我们可以对其进行retain操作。不过在将block作为回调函数传递给底层框架时,底层框架需要对其copy一份。比方说,如果将回调block作为属性,不能用retain,而要用copy。我们通常会将block写在栈中,而需要回调时,往往回调block已经不在栈中了,使用copy属性可以将block放到堆中。或者使用Block_copy()和Block_release()。

 

原理解析:

参考原理:https://www.cnblogs.com/dahe007/p/6067591.html

https://blog.youkuaiyun.com/wangyanchang21/article/details/79785274

 

block的本质就是指向结构体的指针。其中有变量isa指针(每个对象中都有一个isa指针);funcptr指针(指向block申明的函数),flag说(block copy的时候会用到)。block创建的时候,入口就是一个结构体,结构体中有一个block类型的变量,还有一个desc描述结构体,还有一个对block结构体中变量赋值的函数。当调用block的时候,实际上就是运行时动态的给对象发送消息执行相应的方法。根据block中的isa找到对象和方法地址,在方法区执行对应的block的实现函数。


当访问局部变量的时候,block入口mainblockimpl结构体中会多一个访问的变量。会把mianlockiml作为参数在block的实现函数中传入,并把外部的变量赋值给block实现函数内部。block会在以下几个时机进行copy的操作。作为参数,属性强引用  返回值  gcd

__block的作用:将值传递变成了指针引用传递。给外部变量加了__block后,会吧变量包装成一个blockbrfnum的结构体(这个结构体中有isa指针,外部变量,标志位flags,结构体大小size,还有一个指向拷贝到堆上的指针blockbrefnum *,也就是通过这个指针变量传递给block)。block内部修改了值后,外部也改变。block中多了一个block_bref-的指针(在block中就是一个结构体blockbrefnum),这个指针的作用是指向copy到堆上的指针。入口中的局部变量变成了指针,在block的实现函数中通过指针传给了block,(block的执行函数mainblockfun中 通过block_breyf的forwarding来修改外部变量。)
block会保存代码,在适当的时机调用。
block的三种类型:全局的block:不吊用外部变量或调用全局变量属性。
栈block,函数作用域结束或函数返回,销毁,通过copy到堆上保存。
堆block由开发人员自己控制,引用计数为0时销毁。
mainblockfun是block要执行的函数.
mainblockdesc是block描述的结构体.
blockiml是block的结构体.
mainblockimpl是入口函数.

mainblockcopy从栈上拷贝到堆上调用;

mainblockdispose从堆内释放的时候调用:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值