美文网首页iOS面试总结iOS
iOS开发之Block原理探究

iOS开发之Block原理探究

作者: it_Xiong | 来源:发表于2019-04-18 15:52 被阅读91次
    • Block概述
    • Block本质
    • Block调用
    • Block分类
    • Block循环引用
    • Block原理探究
    • Block截获变量

    Block概述

    在iOS中,block的使用非常方便,我们一般用来进行值的传递,与其他的传递方式(例如delegate,通知,KVO)不同,Block的调用可以使代码更加紧凑,阅读性更好.

    Block本质

    那么Block到底是什么呢?为了弄清楚Block的本质,我们来看看底层源码:

    1. 终端创建一个block.c文件:
    vim block.c
    

    里面写上C代码,一个普通的block调用场景:

    #include "stdio.h"
    
    int main(){
        
        int multiplier = 6;
        int (^block)(int) = ^int(int num){
            return num * multiplier;
        };
        block(8);
        return 0;
    }
    
    
    

    通过以下终端命令编译为c++文件,就得到了一个block.cpp的c++文件

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

    一路翻到最底下,我把比较重要的部分截取出来

    struct __block_impl {
      void *isa;     //isa指针,Block是对象的标志
      int Flags;
      int Reserved;
      void *FuncPtr; //函数指针
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;         //block结构体
      struct __main_block_desc_0* Desc; //block相关描述结构体
      int multiplier;                   //block使用变量
        /*
         __main_block_impl_0     c++中结构体构造函数的声明
         fp                      函数指针
         desc                    block描述
         _multiplier             block使用变量
         flags                   标记
         multiplier(_multiplier) _multiplier赋值给上面的int multiplier
         */
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _multiplier, int flags=0) : multiplier(_multiplier) {
          
        impl.isa = &_NSConcreteStackBlock; //isa赋值
        impl.Flags = flags;                //标记位赋值
        impl.FuncPtr = fp;                 //指针赋值
        Desc = desc;                       //block描述赋值
      }
    };
    
    static int __main_block_func_0(struct __main_block_impl_0 *__cself, int num) {
      int multiplier = __cself->multiplier; // bound by copy 只是值传递
    
            return num * multiplier;
        }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(){
    
        int multiplier = 6;
        /*
         int (^block)(int) = ^int(int num){
         return num * multiplier;
         };
         */
        /*
         __main_block_impl_0  结构体
         __main_block_func_0  (void*)函数指针
         &__main_block_desc_0_DATA block相关描述的结构体
         multiplier block中所使用户的变量
         */
        int (*block)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, multiplier));
        
        /*
          block(8);
         */
        ((int (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 8);
        
        return 0;
    }
    

    通过以上源码可以验证:

    Block就是一个将函数及其执行上下文封装起来的对象.

    Block调用

    block的调用方式和普通的Objective-C方法调用基本一致,这里我们来看下block调用到底是什么?我们还是来看源码:

    /*
    ((__block_impl *)block)->FuncPtr): 强制类型转换为__block_impl类型,取出当中的成员变量 FuncPtr
    ((__block_impl *)block, 8):  函数的参数,block是block本身,8是我们block传进来的参数
    */
        ((int (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 8);
    
    
    图1 图2 图3 图4

    从上面四张图片可以知道,Block的执行过程就是通过__main_block__impl_0函数中传入block函数指针,然后赋值给了 __main_block_impl_0结构体中的 block结构体 impl的属性FuncPtr.

    Block调用本质是就是函数的调用.

    Block分类

    Block分为三种,全局Block,堆Block,栈Block.

    1.全局Block

        void (^block)(NSString *name) = ^(NSString *name) {
            NSLog(@"%@",name);
        };
         NSLog(@"%@",block);
    

    控制台打印结果:

    <__NSGlobalBlock__: 0x1005c1100>
    

    也就是说是一个全局Block
    2.堆Block

        int a = 10; 
        void (^block1)(void) = ^{
            NSLog(@"%d",a);
        };
        
        NSLog(@"%@",block1);
    

    控制台打印结果:

    <NSMallocBlock: 0x60000028ba50>

    也就是说是一个堆Block
    3.栈Block

         int a = 10; 
          NSLog(@"%@",^{
            NSLog(@"%d",a); 
        });
    

    控制台打印结果:

    <NSStackBlock: 0x7ffee5a78298>

    也就是说是一个栈Block

    Block循环引用

    循环引用的产生原因我就不多说了,下面直接说我们常用的解决方法:

    1. __weak
     __weak typeof(self) weakSelf = self;
        self.oneBlock = ^(NSString *name){
           //出了作用域自动释放
            weakSelf.name = name;
        };
     self.oneBlock(@"jack");
    
    1. __block
    __block TwoViewController *blockSelf = self;
        self.oneBlock = ^(NSString *name){
    
            blockSelf.name = name;
            blockSelf = nil;     //防止blockSelf变成野指针
        };
     self.oneBlock(@"jack");
    

    3.把self当做参数传入block

       self.twoBlock = ^(TwoViewController *vc) {
            
            vc.name = @"jack";
        };
        self.twoBlock(self);
    

    Block原理探究

    1. 终端创建一个block.c文件:
    vim block.c
    

    里面写上C代码,一个最简单的无参无返回值block:

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

    编译为c++文件,就得到了一个block.cpp的c++文件

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

    一路翻到最底下,我把比较重要的部分截取出来

    
    //block结构体
    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    //block函数结构体
    struct __main_block_impl_0 {
       //block结构体
      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 = &_NSConcreteStackBlock; //栈block  创建出来未使用就是在栈区
          
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
            printf("hello block");
        }
    
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    
    int main(){
    
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
        /*block函数,把前面的类型去掉
        void (*block)(void) = __main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA));
    */
        
        //调用block函数
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        
        return 0;
    }
    
    impl.isa = &_NSConcreteStackBlock; //栈block 
    

    由此,可以印证上面栈block 创建出来未使用就是在栈区.

    2.现在我们block.c中代码改为如下所示

    #include "stdio.h"
    
    int main(){
        
        int a = 1;
        void (^block)(void) = ^{
            printf("%d",a);
        };
        block();
        return 0;
    }
    
    

    继续编译为c++文件:

    
    //多了个 int 参数
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a;
        
      __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;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
        
        //把传进来的a copy一份, 赋值给了临时变量a,只是值传递
            printf("%d",a);
        }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(){
    
        int a = 1;
        
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        
        return 0;
    }
    
    

    在block里面打印a的值,只是把a当成一个参数传入block内部,而block内部把传入的a赋值给了一个同名的临时变量a,只是一个值传递
    3.我们把block.c中的代码再次改动一下,

    #include "stdio.h"
    
    int main(){
        
        int a = 1;
        void (^block)(void) = ^{
            a = 2;
        };
        block();
        return 0;
    }
    
    

    再次编译为c++文件,这时终端直接报错

    /var/folders/rx/4mhmzqfd0m7brd5j4hwv__xc0000gn/T/block1-18d4f2.i:441:11: error: 
          variable is not assignable (missing __block type specifier)
            a = 2;
            ~ ^
    1 error generated.
    

    终端报错:变量不可赋值(缺少__block类型说明符)
    那么我们这次加上 __block,

    #include "stdio.h"
    
    int main(){
        
        __block int a = 1;
        void (^block)(void) = ^{
            a = 2;
        };
        block();
        return 0;
    }
    
    

    这次编译之后,c++文件:

    
    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
        
      __Block_byref_a_0 *a; // by ref //截获了外部变量a
        
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
          
        impl.isa = &_NSConcreteStackBlock; //堆block
          
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_a_0 *a = __cself->a; // bound by ref
    
        //这里直接拿到了外部变量a的地址,通过地址把a的值改为了2
            (a->__forwarding->a) = 2;
        }
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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};
    int main(){
    
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
        
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_a_0 *a = __cself->a; // bound by ref //地址拷贝
    
            (a->__forwarding->a) = 2;
        }
    

    通过上面的代码可以知道,这里直接拿到了外部变量a的地址,通过地址把a的值改为了2,只是地址传递,所以 __block截获变量,是把变量地址截获,也就是把变量本身传入block内部对其改变,变量本身也会改变.

    Block截获变量

    这次我们用MACOS写上一段.m代码

    
    #import <Foundation/Foundation.h>
    
    //全局变量
    int global_a = 10;
    //静态全局变量
    static int staic_global_a = 20;
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
         
            //基本数据类型的局部变量
            int a = 5;
            
            //对象类型的局部变量
            __unsafe_unretained id unsafe_objc = nil;
            __strong id strong_objc = nil;
            
            //局部静态变量
            static int staic_a = 6;
            
            void(^block)(void) = ^{
                
                NSLog(@"局部变量.基本数据类型 %d",a);
                
                NSLog(@"局部变量.__unsafe_unretained.对象类型 %@",unsafe_objc);
                NSLog(@"局部变量.__strong.对象类型 %@",strong_objc);
                
                NSLog(@"局部静态变量 %d",staic_a);
                
                NSLog(@"全局变量 %d",global_a);
                NSLog(@"静态全局变量 %d",staic_global_a);
                
            };
            
            block();
            
        }
        return 0;
    }
    

    然后在终端用命令 clang -rewrite-objc -fobjc-arc main.m编译为c++代码,这次我们只看block的函数部分:

    //对全局变量,静态全局变量不截获
    int global_a = 10;
    
    static int staic_global_a = 20;
    
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
        //截获局部变量的值
      int a;
        //连同所有权s修饰符一起截获
      __unsafe_unretained id unsafe_objc;
      __strong id strong_objc;
        //以指针形式截获静态局部变量
      int *staic_a;
        
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, __unsafe_unretained id _unsafe_objc, __strong id _strong_objc, int *_staic_a, int flags=0) : a(_a), unsafe_objc(_unsafe_objc), strong_objc(_strong_objc), staic_a(_staic_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    通过以上的代码,我们可以得出结论:

    对于基本数据类型的局部变量截获其值
    对于对象类型的局部变量连同所有权修饰符一起截获
    以指针形式截获局部静态变量
    不截获全局变量.静态全局变量

    一般情况下,对被截获变量进行赋值操作 需要添加__block修饰符,但是它对不同类型的变量却又有不同:
    需要用到__block修饰符的: 局部变量(基本数据类型,对象)
    不需要用到__block修饰符的: 局部静态变量 (直接操作指针), 全局变量,静态全局变量,而后面两个不涉及截获,直接操作本身

    相关文章

      网友评论

        本文标题:iOS开发之Block原理探究

        本文链接:https://www.haomeiwen.com/subject/fpsgwqtx.html