美文网首页
block常见问题及底层初探

block常见问题及底层初探

作者: timelyRain | 来源:发表于2018-07-09 16:01 被阅读29次

    block常见的几个问题:

    • 1、block是什么
      block是一个指向结构体的指针,编译器会将block的内部代码生成对应的函数。

    • 2、block为什么用copy修饰
      正常创建出来的block的内存是放在栈中(如果block内部没有调用外部变量时存放在全局区),程序员无法去管理,什么时候释放内存也不是程序员可以决定的,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃。所以一般情况下,就需要使用copy去修饰,会把block copy到堆上,在ARC中,对block代码块内部的对象强引用,在非ARC中对于引用对象进行一次retain操作。

    • 3、block与循环引用
      在block代码块内部如果使用了当前对象进行调用方法,或者他的的操作,就会对当前对象进行强引用一次,并且如果这个block归当前对象持有,就会导致block和当前对象互相持有引用,而都不能正常释放。(注意:如果block不是当前对象所持有,例如在控制器中使用AFNetWorking请求,AFNetWorking的block并不是当前控制器所持有的,所以这里不用__weak,不会造成循环引用)

    • 4、_strong的使用
      使用_weak来修饰可以避免对控制器或者当前类的循环引用.
      但是有的时候,当前对象self已经销毁了,之后再去执行这个block,里面的weakSelf就是个nil了,可能会出现异常,于是最好是如下写法:

        __weak typeof(self) weakSelf = self;
        self.rightBtnClickHandler = ^() {
            __strong typeof(self) strongSelf = weakSelf;
            [strongSelf defaultRightBtnClick];
            [strongSelf defaultLeftBtnClick];
        };
    

      在block内部使用__strong对weakSelf进行一次强引用,self的引用计数+1,此时strongSelf相当于是block内部的一个局部变量,只要block不结束,self这个对象就不会释放,等到block执行完毕,局部变量strongSelf也会自动销毁,完成释放。

    block底层初识

    接下来看几个常见的面试题

    void test1()
    {
        int value = 10;
        
        void (^block)(void) = ^{
            NSLog(@"value = %d", value);
        };
        
        value = 20;
        
        block(); // 打印结果:value = 10
    }
    
    void test2()
    {
        __block int value = 10;
        
        void (^block)(void) = ^{
            NSLog(@"value = %d", value);
        };
        
        value = 20;
        
        block(); // 打印结果:value = 20
    }
    
    void test3()
    {
        static int value = 10;
        
        void (^block)(void) = ^{
            NSLog(@"value = %d", value);
        };
        
        value = 20;
        
        block(); // 打印结果:value = 20
    }
    
    int value = 10;
    void test4()
    {
        void (^block)(void) = ^{
            NSLog(@"value = %d", value);
        };
        
        value = 20;
        
        block(); // 打印结果:value = 30
    }
    

    可以看出,先test1中,我们更改了局部变量value的值,但是block中的value值并没有变化,但是在test2、3、4中,却能成功更改。这是为什么呢?
    我们尝试着看下它们的底层代码。
    终端执行clang -rewrite-objc main.m

    block_clang.png

    把文件编译成c++代码,得到.cpp文件,打开后估计有接近10万行代码,直接command+⬇️,我们来到文件的最后,再往上面滑一点,可以看到如下:


    block_底层代码.png

    我们可以看到 void (*block)(void) 是一个没有参数没有返回值的函数指针,既然是一个函数指针,那它就是一个变量,变量里面只能保存函数地址,等式右边接收是下面这个函数的返回值:

      __test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    

    这个函数有4个参数,参数 flags= 0,这个参数其实就相当于Swift中指定了一个默认值,不传也有值,可以忽略。所以上面test1中传了3个参数,test2中传了4个参数,其中第三个参数是value,前者是传值,后者是传的value的地址,故后者value的值发生了变化

    block_test1.png block_test2.png

    总结:

    • block可以修改全部变量和静态变量,不可以修改局部变量,如果想要修改使用__block,
    • block之所以能够修改全局变量、静态变量、__block修饰的局部变量,是因为把指向变量的指针地址copy到block结构体内部,而局部变量是copy的变量值到block内部.

    相关文章

      网友评论

          本文标题:block常见问题及底层初探

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