美文网首页
关于Block的几个知识点分析说明

关于Block的几个知识点分析说明

作者: Moclin | 来源:发表于2018-05-13 17:21 被阅读0次

    前言

    最近复习一下Block的知识,发现一些书籍和网上的文章对Block的某些知识点的解释存在不清晰甚至存在错误。所以决定写下这篇,对其中的一些知识点进行分析。

    PS:这里没有对Block的详细介绍,阅读需要先对Block的整体知识有所了解。

    关于Block的几个知识点分析说明

    1. 在Block中不能对截获的自动变量赋值

    我们都知道,在Block中不能截获的自动变量进行修改。我在网上看到很多文章都没有对这个点进行说明,甚至有些文章还有错误理解。实际上这里的修改应该表述为赋值更加准确,也就是说不能修改变量的值内容。对于数值变量来说,是不能修改的其数值;对于指针变量来说,是不能修改其指针的值,也就是指针指向的内存。

    明白上面所说的,下面来看一下,为什么在Block中不能对截获的自动变量赋值。

    首先,使用clang -rewrite-objc 文件名命令把下面代码转换

    int main()
    {
        NSObject *obj = [NSObject new];
        int val = 2;
        void (^blk1)(void) = ^{
            NSLog(@"%@ - %d\n", obj, val);
        };
        blk1();
        
        return 0;
    }
    

    转换后的关键代码如下

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      NSObject *obj;
      int val;
      //构造函数
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int _val, int flags=0) : obj(_obj), val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    int main()
    {
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
        int val = 2;
        void (*blk1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, val, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)blk1)->FuncPtr)((__block_impl *)blk1);
    
        return 0;
    }
    

    可以看到,__main_block_impl_0的构造函数objval参数都是通过值传递的,也就是说改变结构体内objval并不能改变Block外部的原变量,所以在Block内改变截获后的自动变量对于原来的变量就没有意义了。
    这个问题的关键是C语言的参数传递,我在另一篇文章已经对C语言的参数传递进行了分析:C语言参数传递

    上面已经可以回答为什么在Block内不能对截获的自动变量进行赋值了,那么使用__block修饰的自动变量有为什么可以赋值呢?其实同样是参数传递原因。
    首先,使用__block修饰的自动变量通过clang转换后,会变成一个结构体。而Block结构体的构造函数是接收了这个结构体的地址,将结构体的地址赋值给其成员变量(使用__block修饰的自动变量的结构体被Block结构体以成员变量的方式持有),所以使用__block修饰的自动变量在Block中是可以重新赋值的。

    2. 不同存储域的Block之间的关系

    根据存储域,Block可分为以下三种:

    _NSConcreteGlobalBlock  //全局
    _NSConcreteStackBlock   //栈
    _NSConcreteMallocBlock  //堆
    

    Block是通过Block结构体的isa成员变量来指明Block是属于那种Block的。那么什么样的Block,或者什么时候,Block会分配在不同的存储域呢?

    通过断点可以Block的类型,如下图:


    pic1.png

    这里不贴具体代码和图片,通过断点可以知道:全局Block以及没有截获自动变量的Block是属于_NSConcreteGlobalBlock类的,除此之外,不论在何时何创建的Block应该都是_NSConcreteStackBlock。那么_NSConcreteMallocBlock呢?

    实际上,通过断点看到的除了_NSConcreteGlobalBlock都是,其他都是_NSConcreteMallocBlock,这又是为什么呢?跟我前面所说的不一样啊。其实是这样的:在ARC中,把Block赋值给使用strong修饰的变量时,编译器会自动插入copy操作(会把栈上的block复制到堆上)。所以我们会只看到_NSConcreteMallocBlock,验证这一点可以通过把Block赋值给使用__weak修饰的变量,再进行断点查看。

    最后,_NSConcreteMallocBlock。只有在_NSConcreteStackBlock复制到堆时,Block才会变成_NSConcreteMallocBlock,而对_NSConcreteGlobalBlock进行``copy```操作是不会改变改变Block的类型的。

    3. Block在ARC有效和无效的情况的不同

    关于ARC有效和无效的情况下,Block的一些不同,感觉所有书籍和文章提及得比较少,毕竟现在都用ARC了。在这里只是做一下整理,不作详细探究。

    首先,是2中提到的,ARC无效的时候,是没有自动copy的,所以ARC无效时,如果手动copy,那么就不会看到_NSConcreteMallocBlock。另外一点是,ARC无效时,在Block中使用使用了__block修饰的自动变量是不会被retain的,所以在ARC有效和无效的不同情况下,__block的使用有着很大区别,无效时相当于__weak,可用于避免循环引用。

    相关文章

      网友评论

          本文标题:关于Block的几个知识点分析说明

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