iOS底层原理 - 探寻block本质 之 copy

面试题引发的思考:

Q: block的属性修饰词为什么是copy?

  • 如果block是在栈上,将不会对auto变量产生强引用;
  • 如果block被copy到堆上:
    1> 会调用block内部的copy函数;
    2> copy函数内部会调用_Block_object_assign函数;
    3> _Block_object_assign函数会根据auto变量的修饰符(__strong_weak)做出相应的操作,形成强引用、弱引用(仅限于ARC时会retain,MRC时不会)
  • 如果block从堆上移除:
    1> 会调用block内部的dispose函数;
    2> dispose函数内部会调用_Block_object_dispose函数;
    3> _Block_object_dispose函数会自动释放引用的auto变量,类似于release

对象类型的auto变量

iOS底层原理 - 探寻block本质(一)中介绍到block底层原理以及block的变量捕获,那么block对对象类型变量的捕获同对基本数据类型变量的捕获是否相同?

(1) 栈空间、堆空间的强引用、弱引用

Q: 首先查看一下代码,block访问对象类型变量时,对象何时销毁?

// TODO: -----------------  Person类  -----------------
@interface Person : NSObject
@property (nonatomic, assign) int age;
@end

@implementation Person
- (void)dealloc {
    NSLog(@"------------ Person - dealloc");
}
@end

// TODO: -----------------  main  -----------------
typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            block = ^{
                NSLog(@"------------ 内部 %d", person.age);
            };
        }  // 执行完毕,person没有被释放
        NSLog(@"------------ 外部");
    }
    return 0;
}

// 打印结果
Demo[1234:567890] ------------ 外部 
Demo[1234:567890] ------------ Person - dealloc

由打印结果可知:
大括号执行完毕,person没有被释放。
person对象是在大括号内声明的局部变量,它的生命周期仅限于这个大括号内,打印结果是什么原因造成的?

上篇文章介绍到:personauto变量,person会被捕获到block内部,即block对person进行强引用; 那么block销毁之前,person是不会被销毁的。

查看源码,符合我们的结论。

强指针引用

下面我们进入MRC环境:

// MRC环境
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            block = ^{
                NSLog(@"------------ 内部 %d", person.age);
            };
            [person release];
        }  // 执行完毕,person被释放
        NSLog(@"------------ 外部");
    }
    return 0;
}

// 打印结果
Demo[1234:567890] ------------ Person - dealloc
Demo[1234:567890] ------------ 外部

由打印结果可知:
大括号执行完毕,person被释放。
原因是:在MRC环境下,block访问auto变量,会在栈空间,不会对person进行强引用。

对block进行copy操作,person没有被释放

// MRC环境
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            block = [^{
                NSLog(@"------------ 内部 %d", person.age);
            } copy];
            [person release];
        }
        // 执行完毕,person没有被释放
        NSLog(@"------------ 外部");
        // 需要block销毁掉,person才会被释放
        [block release];
    }
    return 0;
}

// 打印结果
Demo[1234:567890] ------------ 外部 
Demo[1234:567890] ------------ Person - dealloc

由打印结果可知:
大括号执行完毕,person没有被释放。
原因是:对栈空间的block进行copy操作,将栈空间的block拷贝到堆中,会对person进行强引用;
堆空间的block可能会对person进行一次retain操作,保证person不被销毁,堆空间的block销毁之后会对person进行release操作。

总结可知:堆区的block对person对象有强引用作用,栈空间的block对person对象没有强引用作用。


(2) 关键字__weak

切回ARC环境,执行下列代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;

            __weak Person *weakPerson = person;
            block = ^{
                NSLog(@"------------ 内部 %d", weakPerson.age);
            };
        }
        // 执行完毕,person没有被释放
        NSLog(@"------------ 外部");
    }
    return 0;
}

// 打印结果
Demo[1234:567890] ------------ Person - dealloc
Demo[1234:567890] ------------ 外部

将代码转化成C++,_weak修饰变量,需要告知编译器使用ARC环境及版本号:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

__weak修饰变量

由源码可知:_weak修饰的变量,在生成的__main_block_impl_0中也是使用_weak修饰。
所以对person对象是弱引用,不能改变person对象的生命周期。


(3) _main_block_copy_0函数和 __main_block_dispose_0函数

block捕获对象类型的变量时__main_block_impl_0内部结构体__main_block_desc_0中多了copy函数和dispose函数两个参数:

__main_block_copy_0、__main_block_dispose_0函数

copy函数和dispose函数中传入的都是__main_block_impl_0结构体本身。

a> copy函数本质是__main_block_copy_0函数,其内部调用_Block_object_assign函数;
_Block_object_assign中传入的是person对象的地址,person对象,以及8

b> dispose函数本质是__main_block_dispose_0函数,其内部调用_Block_object_dispose函数;
_Block_object_dispose函数传入的参数是person对象,以及8

1> _Block_object_assign函数

block进行copy操作的时候就会自动调用__main_block_desc_0内部的__main_block_copy_0函数;
__main_block_copy_0函数内部会调用_Block_object_assign函数;
_Block_object_assign函数会自动根据__main_block_impl_0结构体内部的person的指针类型,对person对象产生强引用或者弱引用。

2> _Block_object_dispose函数

当block从堆中移除时就会自动调用__main_block_desc_0中的__main_block_dispose_0函数;
__main_block_dispose_0函数内部会调用_Block_object_dispose函数;
_Block_object_dispose会对person对象做释放操作,类似于release,即断开对person对象的引用,而person究竟是否被释放还是取决于person对象自己的引用计数。

总结可知:

当block内部访问了对象类型的auto变量时:

  • 如果block是在栈上,将不会对auto变量产生强引用;
  • 如果block被拷贝到堆上:
    1> 会调用block内部的copy函数;
    2> copy函数内部会调用_Block_object_assign函数;
    3> _Block_object_assign函数会根据auto变量的修饰符(__strong_weak)做出相应的操作,形成强引用,弱引用。
  • 如果block从堆上移除:
    1> 会调用block内部的dispose函数;
    2> dispose函数内部会调用_Block_object_dispose函数;
    3> _Block_object_dispose函数会自动释放引用的auto变量,类似于release

(4) 以下几个示例中person都在何时销毁?

1> 示例一:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person *person = [[Person alloc] init];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"------------ %@", person);
    });
    NSLog(@"------------ touchesBegan");
}
打印结果

打印结果显示:block执行完毕后person对象销毁。

iOS底层原理 - 探寻block本质(一)可知:
ARC环境中,block作为GCD API的方法参数时会自动进行copy操作,将block复制到堆上,所以block内部copy函数会对person进行强引用。

当block执行完毕需要销毁时,调用dispose函数释放对person对象的引用,person没有强指针引用时会被销毁。

2> 示例二:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person *person = [[Person alloc] init];

    __weak Person *weakPerson = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"------------ %@", weakPerson);
    });
    NSLog(@"------------ touchesBegan");
}
打印结果

打印结果显示:person对象先销毁,然后执行block,打印为null

block对weakPerson__weak弱引用,所以block内部copy函数会对person进行弱引用。
touchesBegan: withEvent:执行完毕时,person没有强指针引用时会被销毁,所以block执行时打印null

3> 示例三:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person *person = [[Person alloc] init];

    __weak Person *weakPerson = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"------------1 %@", person);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"------------2 %@", weakPerson);
        });
    });
    NSLog(@"------------ touchesBegan");
}
打印结果

打印结果显示:外层block执行完毕后,即1秒后person对象销毁,然后执行内层block,即3秒后打印为null

外层block对person进行强引用,内层block对person进行弱引用。
当外层block执行完毕时,person有强指针引用;然后内层block执行时person没有强指针会被销毁,所以内层block执行时打印null

4> 示例四:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person *person = [[Person alloc] init];

    __weak Person *weakPerson = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"------------1 %@", weakPerson);

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"------------2 %@", person);
        });
    });
    NSLog(@"------------ touchesBegan");
}
打印结果

打印结果显示:外层block执行完毕后,然后执行内层block,3秒后person对象销毁。

外层block对person进行弱引用,内层block对person进行强引用。
person的强引用何时结束,person何时dealloc,所以3秒后person对象才销毁。

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

推荐阅读更多精彩内容

  • 快过年了,又大了一岁。那些被逼婚的其实还是让人妒忌的,证明还算年轻,重点是无拘无束。 有一群人,他们已婚有小孩,快...
    鞥咕阅读 441评论 0 0
  • 静: 快过年了 去年的现在,你在广西游山玩水呢 时间真快,我们还一起去看了庙会 我不知道我还能在这个世界上活多久,...
    二毛砸阅读 188评论 0 0
  • 我欲从此过 诸位列队侯 灯光照我 星光佑我 一路向着前
    圈儿里人阅读 217评论 0 1