iOS 底层 - 对象类型的auto变量

本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !

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

  1. 如果block是在栈空间,如果访问到了auto变量,不论此auto变量是被强制指针指向着或者被弱指针指向着,block内部都肯定不会对auto变量产生强引用;(因为block本身都是随时可能被销毁的,就完全没必要强引用外面的变量)
- (void)test {
         XYHPerson *person = [[XYHPerson alloc] init];
         person.age = 10;
         ^{
           NSLog(@"---------%d", person.age);
         };
}
  1. 如果block是在堆空间
    1> 会调用block内部的copy函数
    2> copy函数内部会调用_Block_object_assign函数
    3> _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak 、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

  2. 如果block从堆空间移除
    1>会调用block内部的dispose函数
    2>dispose函数内部会调用_Blocl_object_dispose函数
    3> _Blocl_object_dispose函数会自动释放(引用计数器减一)引用的auto变量(类似于release)

// Runtime copy/destroy helper functions (from Block_private.h)
运行时 复制、销毁 函数 在Block_private.h文件中
#ifdef __OBJC_EXPORT_BLOCKS
extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int);
extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int);
  • copy 函数 栈上的block赋值到堆时调用
  • dispose 函数 堆上的block被废弃时调用

关于底层结构分析

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;

  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};


可以看到__main_block_desc_0中相对于访问基本数据类型多了
 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
 void (*dispose)(struct __main_block_impl_0*); 
 这两个函数;

那么为什么会多这两个函数呢 ?

原因是因为当block内部需要访问的变量是一个auto对象,对象是通过引用计数管理的,而这两个函数的作用就是对其进行内存管理的。同时需要注意基本数据类型是系统自动管理。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    XYHPerson * person = [[XYHPerson alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"-------%@", person);
    });
    NSLog(@"touchesBegan:withEvent:");
}

上述情况person什么时候被释放 ?为什么

答:person会在3秒过后被释放(在XYHPerson初始化的时间忽略不计的情况下);
原因:dispatch_after() 是GCD的API , 且是以block为方法参数的,系统对该block做了copy操作,那么block内部访问的person对象会引用计数器加一,只有在block结束后才会对其引用计数器减一,这个时候person被释放。
-->dispatch_block_t block:

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
        dispatch_block_t block);

两次打印,哪一个先输出 ?为什么

答:先输出 touchesBegan:withEvent: ; dispatch_after()是异步3秒后回到主线程执行;

如果在上述代码中假如了耗时操作,还会在3秒后打印person吗?如果不会,为什么 ?

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    XYHPerson * person = [[XYHPerson alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"-------%@", person);
    });
  NSInteger count = 0;
    for (long long i = 0; i < 5000000000; i++)
    {
        count ++;
    }
    NSLog(@"touchesBegan:withEvent:");
}
   

答:不会;原因:耗时操作和延迟操作都是在主线程,dispatch_after又是异步,所以耗时操作先执行,在耗时操作没执行完之前时无法暂停来执行其他任务的。
准确的说:dispatch_after的真正含义是在3秒后把任务添加进队列中,并不是表示在3秒后执行,大部分情况该函数能达到我们的预期,只有在对时间要求非常精准的情况下才可能会出现问题。

如果把dispatch_get_main_queue()替换成其他队列,结果会有什么不同 ?如下

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  
  XYHPerson * person = [[XYHPerson alloc] init];
   dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), queue, ^{
        NSLog(@"我是延迟执行-------%@", person);
    });
    NSLog(@"耗时任务开始");
    NSInteger count = 0;
    for (long long i = 0; i < 5000000000; i++)
    {
        count ++;
    }
    NSLog(@"耗时任务结束");
}
   

答:会在3秒钟的时候来到dispatch_after中执行任务;

直观的查看输出结果:

2020-03-12 17:30:13.742847+0800 -测试[8613:2014102] 耗时任务开始
2020-03-12 17:30:16.802072+0800-测试[8613:2014252] 我是延迟执行-------< XYHPerson: 0x60000390c3b0>
2020-03-12 17:30:25.724886+0800-测试[8613:2014102] 耗时任务结束
2020-03-12 17:30:25.725023+0800-测试[8613:2014102] XYHPerson - dealloc

可以看出 17:30:13 ~ 17:30:16 相差时间基本是为3秒 !

下面代码中person什么时候被释放 ?

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    XYHPerson * person = [[XYHPerson alloc] init];
    __weak XYHPerson *weakP = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"1-------%@", weakP);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@", person);
        });
    });
}

答:person大概在3秒后被释放;这其中dispatch_after的block中再次执行dispatch_after函数;由于在第二次执行延迟操作中对auto变量是强引用,所以person会在该强引用执行后释放;这样的使用我们在开发中经常用到,比如在网络请求时我们的block种需要用到self,一般会在block外部先对其进行一次弱引用,之后再block内部再进行强引用。如下:

 kWeakSelf(self);
  [BRStringPickerView showStringPickerWithTitle:@"" dataSource:list defaultSelValue:@"" isAutoSelect:NO themeColor:kColorFromHex(0x999999) resultBlock:^(id selectValue) {
  kStrongSelf(self);
  self.jdPostTypelb.text = selectValue;
  NSLog(@"--------------- %@",selectValue);
  } cancelBlock:^{
  }];

总结 :person什么时候被释放取决于编译器,而编译器的判断依据是看dispatch_after函数里有没有强引用person,如果有,那么强引用的代码什么时候执行完毕什么时候就是释放的时机 !至于dispatch_after内部再次执行延迟操作、打印输出这些是被看作一个整体的,在这中间有强引用person,所以会在此之后释放。

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

推荐阅读更多精彩内容