美文网首页
iOS Block底层解析二

iOS Block底层解析二

作者: KOLee | 来源:发表于2020-04-13 23:09 被阅读0次

    一、__block 的解析

    • 接上一篇《iOS Block底层解析一》,在block中我们要修改局部变量(自动局部变量,自动局部类对象)前面都加__block修饰 自动是auto的意思,就是我们写代码苹果自动给我们生成的 比如:int a = 10;其实是 auto int a = 10; 下面就直接说局部变量吧,这么麻烦的叫法膈应人

    • 为啥局部变量在block里面不能直接修改,从上一篇的分析我们知道我们传进去的局部变量,相当于函数的参数,你在函数里面改参数,外面的局部变量会变吗?例如:

    void test8(int a) {
        a++;
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int b = 10;
            test8(b);
            NSLog(@"%d",b);      
        }
        return 0;
    }
    打印结果: 
    2020-04-13 21:12:24.922403+0800 blockTest[3434:163752] 10
    
    • 类对象的属性修改要不要加__block呢? 其实不用的,跟NSMutableArray等 增、删、改、查是一样的,我们修改的都是她里面的东西,其实是因为block最后拿到的还是person地址里面的name值 例如:
     // 对象类型
    void test6() {
        Person *p = [Person new];
        p.name = @"hello block!";
        void(^block)(void) = ^{
            p.name = @"fuck block!";
            NSLog(@"--- %@",p.name);
        };
        block();
    }
    打印结果:
    2020-04-13 21:18:06.198269+0800 blockTest[3486:167284] --- fuck block!
    
    void test8() {
        NSMutableArray * arr = [NSMutableArray new];
        void(^block)(void) = ^{
            [arr addObject:@2];
            NSLog(@"--- %@",arr);
        };
        block();
    }
    打印结果:
    2020-04-13 21:25:44.568467+0800 blockTest[3515:170553] --- (
        2
    )
    
    • 但是我们改变他们本身就会报错,例如:


      图片.png
      图片.png
    • 一定要知会的一点就是有__block修饰的捕获变量和没有__block修饰的捕获变量要分开,内存管理上不要混淆,这样思路才更清晰。这也是跟上一篇分开解析的原因

    • 其实我们要加__block就两种情况,局部变量和局部类对象,其他的都可以直接访问在block中修改,废话有点多 开搞 开搞

    // __block作用
    void test7() {
        
        __block int age = 10;
        __block NSObject *objc = [[NSObject alloc] init];
        void(^block)(void) = ^ {
            objc = nil;
            age = 20;
        };
        block();
    }
    
    • 老套路clang一波
    把转换的去掉简化代码,摆好步骤:
    // 第一步
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            test7();
        }
        return 0;
    }
    
    // 第二步
    void test7() {
        // __block int age = 10;
        __Block_byref_age_0 age = {
            0,
            &age,
            0,
            sizeof(__Block_byref_age_0),
            10
            
        };
        //    __block NSObject *objc = [[NSObject alloc] init];
        __Block_byref_objc_1 objc = {
            0,
            &objc,
            33554432,
            sizeof(__Block_byref_objc_1),
            __Block_byref_id_object_copy_131,
            __Block_byref_id_object_dispose_131,
            objc_msgSend((id)(objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))
         };
        
        // 调用
        void(*block)(void) = &__test7_block_impl_0(__test7_block_func_0, &__test7_block_desc_0_DATA, (__Block_byref_objc_1 *)&objc, (__Block_byref_age_0 *)&age, 570425344));
        block->FuncPtr(block);
    }
    
    // 第三步
    struct __Block_byref_age_0 {
      void *__isa; // isa指针
    __Block_byref_age_0 *__forwarding; // 指向自己的指针
     int __flags; // 不清楚是什么鬼 猜测是标识符什么的
     int __size; // 当前结构体的大小
     int age; // 捕获值
    };
    
    // 第三步
    struct __Block_byref_objc_1 {
      void *__isa; // isa指针
    __Block_byref_objc_1 *__forwarding; // 指向自己的指针
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);  // 执行copy操作
     void (*__Block_byref_id_object_dispose)(void*); // 执行dispose操作
     NSObject *objc;// 捕获 NSObject类型指针
    };
    
    // 第四步
    static void __test7_block_func_0(struct __test7_block_impl_0 *__cself) {
    
        // 拿到 __Block_byref_objc_1里面 *objc指针
      __Block_byref_objc_1 *objc = __cself->objc; // bound by ref
        // 拿到__Block_byref_age_0里面 *age指针
      __Block_byref_age_0 *age = __cself->age; // bound by ref
            //objc->__forwarding指针指向 __Block_byref_objc_1 拿到里面的的objc 完成赋值
            (objc->__forwarding->objc) = __null;
            //age->__forwarding指针指向 __Block_byref_age_0 拿到里面的的age 完成赋值
            (age->__forwarding->age) = 20;
    }
    
    // 第五步
    struct __test7_block_impl_0 {
      struct __block_impl impl;
      struct __test7_block_desc_0* Desc;
      __Block_byref_objc_1 *objc; //__Block_byref_objc_1 类型结果体
      __Block_byref_age_0 *age; //__Block_byref_age_0类型结果体
      __test7_block_impl_0(void *fp, struct __test7_block_desc_0 *desc, __Block_byref_objc_1 *_objc, __Block_byref_age_0 *_age, int flags=0) : objc(_objc->__forwarding), age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 看得眼花,那我们走一个,其实前面搞好了,这里就一目了然:

    1.第一步一样,第二步多了两个东西,其实是一个鬼东西,差不多 __Block_byref_age_0和__Block_byref_objc_1两个结构体
    2.第三就是对这两个结构体的解析,是不是就是一个类,包装成类,然后再保存在block结构体里面
    3.第四步就是赋值个过程了__forwarding指针为啥要这样搞,明明已经拿到*objc的指针了,又(objc->__forwarding->objc) 这样拿objc,这他么不是吓搞吗?我们想一下,前面说的内存管理,很多时候block会copy到堆上的,然后苹果是这样设计的 如图:


    图片.png
    图片.png
    这样搞的好处就是:
    • block在栈上的时候 block在栈上的 __forwarding指针指向自己
    • block在堆上的时候 block在栈上的 __forwarding指针指向堆内存 block在堆上的_forwarding指针指向堆内存
    • 所以你是指向栈还是堆 最后都能找到这个变量

    二、__weak 修饰、 __strong修饰、block的循环引用问题

    • 其实到上面block已经搞完了,这个步骤都是根据之前的解析的都可以推导出来,还是要自己去多推导 还是说说吧
    • __weak 修饰的时候可以解除block的循环引用问题,首先你要明白怎么样才会造成循环引用问题:
    1. 两个是否相互强引用 2.两个以上是否形成强引用闭环
    • 那么我们可以得出结论 block只有在堆上才会形成强引用,就是执行copy操作的时候 对就是结合上一篇文章说道的内存管理总结:


      图片.png
      图片.png
    • 有些同学就问了 GCD里面的block调用self会吗 UIview animation的block调用self会吗 self要用__weak 修饰吗?嗯 是不会的 因为你self 跟GCD UIview 没有强引用block 没有形成相互强引用,还有很多第三方库的block里面用self 也是一样的,首先你要判断的是 self有没有强引用第三方的block

    • 有同学会问 NSTimer的block里面会是强引用呢? 因为timer是self的属性strong的啊,然后你在人家的block里面调用肯定就形成相互强引用啦

    • 最后关于循环引用的问题就是前面说的两个

    1. 两个是否相互强引用 2.两个以上是否形成强引用闭环
    • 还有种特殊的场景比较少见,用__weak 修饰的对象,什么情况下会在block里面再用__strong修饰,就是在block里面执行多线程时候,为啥这么说,因为__weak 修饰的对象block结束的时候block里面的对象就释放了,可是你后面的线程还要使用block里面的对象所以就会有空指针的问题(weak/__weak 修饰的对象释放后为nil)

    • 关于block的底层解析就到这里了,也是对自己学习block的一个总结吧,以后遇到block的难题基本都是这样一套推倒,还有一些深入的东西和细节估计说得不到位,希望各位大老爷指出

    一个人的精力有限,对你的思念无限

    相关文章

      网友评论

          本文标题:iOS Block底层解析二

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