iOS Block底层解析二

一、__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 修饰吗?嗯 是不会的 因为 GCD 跟你的UIView 没有强引用block 没有形成相互强引用,还有很多第三方库的block里面用self 也是一样的,首先你要判断的是 self有没有强引用第三方的block 比如: Masonry AFN 第三方的block跟你的类不是强引用关系,所以不会形成循环引用

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

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

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

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

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

推荐阅读更多精彩内容

  • 引导语: Block原理是怎样的?本质是什么? _block的作用是什么?使用需要注意什么? block的属性修饰...
    三月木头阅读 226评论 0 2
  • Block的本质<一> 1.对象类型的auto变量 在第一篇文章中我们讲了在block中使用基本类型的自动变量的情...
    雪山飞狐_91ae阅读 1,278评论 0 10
  • 1. Block是啥? 答:Block是将函数及其执行上下文封装起来的对象。 使用终端编译.m内容: 编译之后会生...
    DoBetter1阅读 276评论 0 1
  • Block在开发中常用的,要想解决Block在开发中遇到的问题,我们需要了解Block的本质、截获变量的特性、__...
    字节码阅读 530评论 0 2
  • 朋友在毕业之际选择申请留学。开始忙碌,偏偏我这人,又属于较真的人。 可能是比较介意他之前说的话没有做到,于是就说话...
    小核桃吖阅读 153评论 0 0