iOS block底层分析

一:block的基本使用:

1.

     ^{

           NSLog(@"----blcok");

     }();

2. block访问变量

        int  age =30;

      void(^blcok)(int,int)= ^(inta,intb){

          NSLog(@"*****blcok-----%d",age);//其实是把age变量存储到了block对象里面

      };

        blcok(10,20);

二:block的基本概念:

     1. block本质也是一个OC对象,它内部也有个ISA指针;

      2.  block是封装了函数调用以及函数调用环境的OC对象;

三:内部解析:

    1.   定义block以后,使用xcrun命令编译成C++代码; xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main7-arm64.cpp

         编译完成后可以看到block的结构如下:

 struct __main_block_impl_0 {

   struct __block_impl impl;      //  因为impl是第一个成员;所以impl的地址就是__main_block_impl_0结构体的地址;

   struct __main_block_desc_0* Desc;

// 结构体里面放的函数;构造函数(类似于init方法);返回__main_block_impl_0这个结构体函数;入参的FP是__main_block_func_0这个结构体;

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

     impl.isa = &_NSConcreteStackBlock; // _NSConcreteStackBlock类型

     impl.Flags = flags;

     impl.FuncPtr = fp;

     Desc = desc;

   }

 };

从该结构中可以看出:block主要有1个结构指针__main_block_desc_1和一个__block_impl结构体;以及block外面访问的auto变量和自身的构造函数;继续分析一下__main_block_desc_1和__block_impl内部:

__block_impl结构体:

 struct __block_impl {

   void *isa;    //ISA 指向block类型的

   int Flags;   //标记位

   int Reserved; //预留位 一般是0

   void *FuncPtr; 函数指针;指向构造函数的__main_block_func_0这个函数;

 };

__main_block_func_0该函数就是block的 ^{}这个函数;我们来分析这个函数的具体实现如下:

 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 

     NSLog((NSString *)&__NSConstantStringImpl__var_folders_kc_zlwckf1s2hzcw2f11qmlq2780000gp_T_main_de43bb_mi_0);

 }

可以看出入参是将block对象传入到了这个函数里面,而且也是block实现的具体内容;我们继续分析其他的结构类型;还有__main_block_desc_1;

__main_block_desc_1结构体:

 static struct __main_block_desc_1 {

   size_t reserved;  //预留位一般也是为0

   size_t Block_size;//  返回的是block结构的自身内存占用的大小;

 }

block内部的结构如果上面所说;接下来我们看一下;调用的流程和具体的声明如下:

 定义声明block3:

 void (*block3)(void) = & __main_block_impl_0(

                  __main_block_func_0,参数1

                  &__main_block_desc_0_DATA));参数2

 执行block函数:

           block3->FuncPtr(block3);

从声明定义和调用为起点,来串一下整个流程的调用关系:

从声明上可以看出,在定义block的时候,调用构造__main_block_impl_0函数会传入2个参数__main_block_func_0函数指针和__main_block_desc_0_DATA描述的结构体指针;在构造函数中:将__main_block_func_0函数指针、__main_block_desc_0_DATA赋值给了impl.FuncPtr、__main_block_desc_0* Desc;也就是说block内部的FuncPtr 指向了__main_block_func_0函数;__main_block_desc_0* Desc等于__main_block_desc_1这个结构体;当我们调用  block3->FuncPtr(block3),实际就是调用__main_block_func_0并且将block对象传入到__main_block_func_0函数中;来执行;

四、  block的变量捕获机制capture

1、    从基本使用的内容里面看第二个例子,如果block访问了局部变量,block 内部会生成一个变量,来存储auto自动变量称为捕获,称之为捕获;

2、    捕获的规则如下:

局部变量:

      auto(默认)变量 :自动变量,离开作用域就自动销毁;auto变量会捕获到block内部;访问方式是: 值传递(将10的值传递过来的);

      static 变量:static变量会捕获到block内部里面,访问方式是:指针传递;

  全局变量:

               blcok不会捕获变量的值。访问方式是:直接获取;blcok内部不会管理全局变量;直接获取全局变量的值;

3、    我们继续看一下如果访问局部变量block内部结构体:

 struct __main_block_impl_0 {

   struct __block_impl impl;

   struct __main_block_desc_0* Desc;

   int age;  //捕获到block内部的变量;

   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {

     impl.isa = &_NSConcreteStackBlock;

     impl.Flags = flags;

     impl.FuncPtr = fp;

     Desc = desc;

   }

 };

可以看出block内部确认多了一个int age;

 4、如果是static修饰的变量:

 auto int age = 10;

static int height = 20;

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

 int age = __cself->age; // bound by copy

 int *height = __cself->height; // bound by copy

           NSLog((NSString *)&__NSConstantStringImpl__var_folders_kc_zlwckf1s2hzcw2f11qmlq2780000gp_T_main_6aa546_mi_0,age,(*height));

       }

主要看一下这void (*block3)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height))代码;从内部结构的__main_block_func_0函数看也确实访问的&heigt地址;

 blcok内部打印的时候是获取的height的地址存储的值;所以是指针传递;并且 height在内存中只有1份;直到应用程序退出的时候,才会销毁;所以不需要传值,传入指针既可!

 而age是作用域是函数结束就释放对象,再次调用重新开辟内存空间,所以需要传入值;来保存到block结构体内部函数__main_block_func_0内部可以获取到  age变量的值来使用所以需要传入值;


5、扩展一下Static的基本知识点:

    Static 修饰局部变量时,不可以改变其作用域,在程序中永远只有一份内存;改变生命期,直到程序退出才会释放储存单元;

   static关键字修饰局部变量:

   当static关键字修饰局部变量时,只会初始化一次且在程序中只有一份内存;

   关键字static不可以改变局部变量的作用域,但可延长局部变量的生命周期(直到程序结束才销毁)。

   static关键字修饰全局变量:

   当static关键字修饰全局变量时,作用域仅限于当前文件,外部类是不可以访问到该全局变量的(即使在外部使用extern关键字也无法访问)。

   extern关键字:

   想要访问全局变量可以使用extern关键字(全局变量定义不能有static修饰)。

   全局变量是不安全的,因为它可能会被外部修改,所以在定义全局变量时推荐使用static关键字修饰。

 Static和extern的区别:

 (1)extern修饰的全局变量默认是有外部链接的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过extern全局变量的声明,就可以使用全局变量。

 (2)static修饰的全局静态变量,作用域是声明此变量所在的文件。

五:block的基本类型

   1、从上面的内部结构可以看出来ISA指向了_NSConcreteStackBlock这种类型;说明block是有类型的;

   2、block 有3种类型:

         栈block __NSStackBlock__          :访问auto变量是Stack  放在栈区;(调用copy函数是从栈区复制到了堆区,变成了堆blcok)

           全局blcok __NSGlobalBlock__  :没有访问auto变量是global  放在数据段 (调用copy函数是什么没有操作);

          堆blcok的 __NSMallocBlock__, :  stackBlock调用了copy函数;就是堆block;global调用copy依然是global的类型的lblock;(堆blcok调用copy函数,引用计数加1);

   3.block的继承关系           

      __NSGlobalBlock__ ---->__NSGlobalBlock------>NSBlock------->NSObject  继承树;

   4.内存中存储的位置:

                   堆:动态分配内存,需要程序员申请内存 也需要程序员自己管理内存    堆blcok的 __NSMallocBlock__

                   栈:系统管理内存                    栈block __NSStackBlock__;

                   数据区:编译期生成                    全局blcok __NSGlobalBlock__

                   代码段:存放代码,编译期生成;

六:ARC情况环境下,编译器会根据情况自动将栈上的block复制到堆上上,比如以下情况:

       1.block作为函数返回值时;

      2.将block赋值给__strong指针时; block可以使用copy和strong修饰;系统默认都是强指针;

      3.blcok 作为cocoa api 中含有usingblock的方法参数时;

      4.block作为GCD的方法入参时;

  七:block 访问auto对象类型

1.举例说明:  

     typedefvoid(^MJBlock)(void);

        MJBlock   block;

        {

            LZHPerson* person = [[LZHPersonalloc]init];

            person.age=10;

//            __weak LZHPerson * weakPersion = person;

            block = ^{

                NSLog(@"%d",person.age);

            };

            block();

        }

        NSLog(@"-------------------");

从上面的例子看分析:person对象什么时候释放的;释放的时机是什么?

2.分析block内部结构如下所示:

         static struct __main_block_desc_0 {

         size_t reserved;

         size_t Block_size;

         void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);

         void (*dispose)(struct __main_block_impl_0*);

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

从上面结构看出来:block结构对象内部的dec结构会多出来2个指针函数*copy和*dispose;

      修饰对象的时候:block内部的desc的会自动添加2个函数指针;__main_block_copy_0, __main_block_dispose_0;因为对象类型需要进行内存管理操作;

          _Block_object_assign((void*)&dst->person, (void*)src->person, _Block_object_assign 根据外面变量的类型对block内部作用相应的类型引用;

          _Block_object_dispose((void*)src->person

           弱引用是需要运行时,才可以生成C++代码;所以weak修饰的变量是不可以直接编译的,需要加上MRC的语法;

            xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main7-arm64.cpp

           1.blcok外面的auto变量是什么类型,在捕获的时候block内部也同样生成相应的类型变量;系统默认都是strong;强引用类型

          2.当外部的对象使用strong修饰的时候,对象的生命周期和block的生命周期是一样的;如果使用weak修饰,对象的生命就是自己本身的生命周期 ;

          对象类型总结:

                  一:当block内部访问了对象类型的auto变量时

                      1:如果block是在栈上,将不会对auto变量产生强引用;(比如:没有强指针指向block模块;没有赋值符号);

                      2:如果block被拷贝到堆上;

                         会调用block内部的copy函数

                         copy函数内部会调用_Block_object_assign的函数

                         _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出等价的引用关系,类似于retain(形成强引用、弱引用);

                         注:此处的引用是指的捕获变量的强、弱引用;不是指的block的y强弱引用;

                      3:如果block从堆上移除(block对象释放)

                         会调用block内部的dispose函数

                         dispose函数内部会调用_Block_object_dispose函数

                         _Block_object_dispose 函数会自动释放引用的auto变量,类似release;

                      4: copy函数 的调用时机是:栈上的block复制到堆时调用

                         dispose函数调用时机:堆上的block被废弃时调用;

                      5:如果在堆区使用strong修饰变量,那么该auto变量对象的生命周期跟block的生命周期保持一致;也会使其引用计数器加一;

      注:

    在block内部调用block的时候;多层block调用,直接找到该对象的强引用的位置,强引用的位置处就是该对象的生命周期;不需要去处理弱引用指针;

    因为弱引用为栈block;括号结束就直接释放了。

    八: __block修饰局部变量类型;

1.  如果外部对象使用__block修饰;在编译期间会对这个变量属性包装成一个  __Block_byref_ages_0 *ages对象;其内部结构如下:

block内部结构如下:

 struct __main_block_impl_0 {

   struct __block_impl impl;

   struct __main_block_desc_0* Desc;

   __Block_byref_ages_0 *ages; // by ref

   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_ages_0 *_ages, int flags=0) : ages(_ages->__forwarding) {

     impl.isa = &_NSConcreteStackBlock;

     impl.Flags = flags;

     impl.FuncPtr = fp;

     Desc = desc;

   }

 };

__Block_byref_ages_0对象结构如下:

  struct __Block_byref_ages_0 {

      void *__isa;  //ISA指向了viod* 类型的指针 

     __Block_byref_ages_0 *__forwarding; //forwarding指针是指向自己本身的指针;

      int __flags; //标记位

      int __size; //__Block_byref_ages_0的大小

      int ages;__blcok //   修饰的属性;真正修改的值;

 };

2、调用blcok函数的内部结构;从  (ages->__forwarding->ages) = 20,可以看出来__blcok修饰的变量值可以改变;其实修改的是__Block_byref_ages_0内部的变量的值;

 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

 __Block_byref_ages_0 *ages = __cself->ages; // bound by ref

           (ages->__forwarding->ages) = 20;

           NSLog((NSString *)&__NSConstantStringImpl__var_folders_kc_zlwckf1s2hzcw2f11qmlq2780000gp_T_main_7d9569_mi_0,(ages->__forwarding->ages));

       }

3.__blcok修饰成员变量总结:

 __block可以用于解决block内部无法修改auto变量值的问题

 __block不能修饰全局变量、静态变量(static)

 编译器会将__block变量包装成一个对象

 __block修改变量:age->__forwarding->age

   九: __block的内存管理

             blcok在捕获对象的时候,会自动生成copy * 和dispose* 指针;

        __blcok修饰的int类型是block在执行copy操作的时候会对这个对象进行强引用(_strong),而__block在访问外面对象的auto变量的时候,会对这个对象进行等价的操作;(根据外面是强引用或者是弱引用)。

                1.当block在栈上时,并不会对__blcok变量产生强引用

                2.当block被 copy到堆时:

                  会调用blcok内部的copy函数;

                  copy函数内部会调用_Block_object_assign函数

                 _Block_object_assign函数会对_blcok变量形成强引用(retain)

               3.blcok从堆中移除时

                会调用block内部的dispose函数

                dispose函数内部会调用_Block_object_dispose函数

                _Block_object_dispose函数会自动释放引用的__block变量(release)

          大总结:对象类型的auto变量和__block变量;

             相同点:

              1.当block在栈上时候,对它们都不会产生强弱引用;

              2.当block拷贝到堆上时,都会通过copy函数来处理它们;

              3. __block变量(假设变量名字叫做a)

                _Block_object_assign((void*)&dst->ages, (void*)src->ages, 8  BLOCK_FIELD_IS_BYREF*

              4.对象类型的auto变量(假设变量名叫做P)

                _Block_object_assign((void*)&dst->person, (void*)src->person, 3BLOCK_FIELD_IS_OBJECT

              5.当block从堆上移除时,都会t通过dispose函数来释放它们;

               __block变量(假设变量名叫做a)

                _Block_object_dispose((void*)src->a, 8  BLOCK_FIELD_IS_BYREF

              6.对象类型的auto变量(假设变量名叫做p)

                _Block_object_dispose((void*)src->p, 3  BLOCK_FIELD_IS_BYREF

              7.(ages->__forwarding->ages) = 20; 为什么已经拿到了block中的agers结构体还要使用__forwarding->age去访问age的值;

                因为:当栈上的__block对象copy到堆区后,栈上的__block的forwarding指针指向了堆区;来保证修改的20的值,都可以访问到堆区的内存数据;

十: __blcok修饰对象类型


 struct __Block_byref_objc_0 {

   void *__isa;

 __Block_byref_objc_0 *__forwarding;

  int __flags;

  int __size;

  void (*__Block_byref_id_object_copy)(void*, void*); --新增

  void (*__Block_byref_id_object_dispose)(void*);-- 新增

  NSObject *__strong objc; --新增

 };

 struct __main_block_impl_0 {

   struct __block_impl impl;

   struct __main_block_desc_0* Desc;

   __Block_byref_objc_0 *objc; // by ref

   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_objc_0 *_objc, int flags=0) : objc(_objc->__forwarding) {

     impl.isa = &_NSConcreteStackBlock;

     impl.Flags = flags;

     impl.FuncPtr = fp;

     Desc = desc;

   }

 };

 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

   __Block_byref_objc_0 *objc = __cself->objc;

             NSLog((NSString *)&__NSConstantStringImpl__var_folders_kc_zlwckf1s2hzcw2f11qmlq2780000gp_T_main_9baefb_mi_0,(objc->__forwarding->objc));

         }

 static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 8 BLOCK_FIELD_IS_BYREF);}

 static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 8  BLOCK_FIELD_IS_BYREF);}

 static struct __main_block_desc_0 {

   size_t reserved;

   size_t Block_size;

   void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);

   void (*dispose)(struct __main_block_impl_0*);

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

注意:

    1.当__blcokb变量在栈上时,不会对指向的对象产生强引用

    2.当block被 copy到堆时:

     会调用blcok内部的copy函数;

    copy函数内部会调用_Block_object_assign函数

    _Block_object_assign函数会对_blcok结构体形成强引用(retain);

       __blcok对象的结构内部会调用copy函数;

      __block在执行copy操作的时候会对这个对象进行等价的操作;(根据外面是强引用或者是弱引用)。(注意MRC不会产生强引用);

 3.blcok从堆中移除时

     会调用block内部的dispose函数

     dispose函数内部会调用_Block_object_dispose函数

     _Block_object_dispose函数会自动释放引用的__block结构体对象(release)

十一: block的循环引用问题

1.arc

    一:使用__weak、__unsafe_unretained解决;当block在捕获对象的时候;block指向对象为弱引用;

    1.typeof (person) 类型为person的类型;简写操作,等价LZHPerson * weakPerson;

    2. __unsafe_unretained  不会retain;相当于弱引用相当于weak;跟weak的区别是当对象释放的时候,指针指向的内存不会自动置为nil;weak会自动置为nil;不安全的;

       (weak置nil后再次访问内存对象;因为是nil不会(野指针)闪退,而__unsafe_unretained会产生野指针会闪退);

        二:__block来解决循环引用;

          __block LZHPerson * person = [[LZHPerson alloc]init];

           person.age = 10;

           person.block = ^{

               NSLog(@"%d",person.age);

               person = nil;

           };

           person.block();

              1.必须要执行blcok;

              2.blockn内部需要把__内部的对象指针置为nil;

2.mrc情况下:

 1.使用__unsafe_unretained解决;MRC是不支持__weak;

 2.__blcok可以直接解决blockl;因为__blcok修饰对象;内部一定是弱引用;

       

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容