关于 Block 中捕获 self 的分析
问题
最近遇到一个已经使用了weak-strong dance的block依旧强引用了self的情况,好在block没被VC持有只是延迟释放,但这里的关键是用了weak_self的blcok理应不会强持有self才对,莫非之前的代码都有问题?下面是”有问题的”代码(为方便理解已删掉部分无关代码)
- (void)requestQBossYellowDiamondAdvWithId:(int)appid
{
qz_weakify(self);
[[QBossEngine instance] getAdv:_uin appid:appid iReqFlag:0x01 key:@"" advCnt:1 advid:0 iPullAsExposeOper:1 withDone:^(NSDictionary *dict , NSString *traceInfo){
qz_strongify(self);
_qbosstraceInfo = traceInfo;
_bannerImageLink = dict[@"img"];
}];
}
的确有加weakify和strongify(宏的具体展开可参照下面的demo代码),但仔细看代码的话会发现访问成员变量的时候都没有加self,其实这里有默认一个条件,即_qbosstraceInfo等同于self->_qbosstraceInfo,一般来讲这样理解是没错的,但是qz_strongify在block内重新定义了一个self的话也适用嘛?两者如果等同的话block应该只捕获外部的weak_self才对,但实际运行结果又与假设的不符,看来只能分析具体的实现了
重写成C++代码
下面是仿照qz_strongify写法的demo代码
- (void)testBlock {
__weak KDTest *weak_self = self;
id blockVar = ^{
_Pragma("clang diagnostic push")
_Pragma("clang diagnostic ignored \"-Wshadow\"")
__strong KDTest *self = weak_self;
_Pragma("clang diagnostic pop")
self->_testString = @"1";
_testString = @"2";
self.testString = @"3";
};
}
接着通过Clang重写成C++,重点看self->_testString = @"1";和_testString = @"1";这两句,重写后的结果如下
(*(NSString *__strong *)((char *)self + OBJC_IVAR_$_KDTest$_testString)) = (NSString *)&__NSConstantStringImpl__var_folders_yz_mzcvr8_x7n18p3pdyf1f5n8m0000gn_T_block_6c1266_mi_0;
(*(NSString *__strong *)((char *)self + OBJC_IVAR_$_KDTest$_testString)) = (NSString *)&__NSConstantStringImpl__var_folders_yz_mzcvr8_x7n18p3pdyf1f5n8m0000gn_T_block_6c1266_mi_1;
可以看到里面使用的同一个self,(char *)self + OBJC_IVAR_$_KDTest$_testString),不过其实这也证明不了什么,因为就算重定义了self两个也都是指向一个地址,重点还是看是否有强引用self,下面是block生成的结构体
struct __KDTest__testBlock_block_impl_0 {
struct __block_impl impl;
struct __KDTest__testBlock_block_desc_0* Desc;
KDTest *__weak weak_self;
__KDTest__testBlock_block_impl_0(void *fp, struct __KDTest__testBlock_block_desc_0 *desc, KDTest *__weak _weak_self, int flags=0) : weak_self(_weak_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到里面只捕获了一个weak_self,一开始我以为这就是最终结论,肯定是工具误报没错了(´▽`) ,不过so上有个类似问题,里面有一句话
if you write self->_testItVar you access data member of structure self, if you write only _testIVar you access ivar from current structure that is visible
大概意思就是不写self的时候访问的是当前可见的structure的变量,放在这里来说就是即使自己重新定义了一个self,不加self使用的仍然是实例方法传进来的self,重定义的self只对显式的访问有效,所以那就是说C++方法有问题喽?刚好周会上也有说到重写C++,其实真正编译的时候代码不会转成C++,实际的实现不一定是这样,所以这里的C++代码对不对是要打问号的,那么把上面的demo代码转成汇编肯定不会有错了吧
汇编代码
利用Xcode自带的汇编器分析下实现,由于转成的汇编代码(基于ARMv7)太长这里只讲关键部分
首先对于实例方法会带上两个隐藏的参数,一个是self,一个是cmd,下面是调用testBlock方法之前的初始化部分
push {r4, r5, r6, r7, lr}
add r7, sp, #12
sub sp, #60
add r2, sp, #48
str r0, [sp, #56]
str r1, [sp, #52]
ARM汇编有规定第一个参数会放入r0中,所以对应这里r0就是self,可以看到有将self的值存入栈内,栈上的偏移为56
下面是创建block的部分(简单一句赋值汇编就有这么长ಥ_ಥ)
.loc 1 20 32 prologue_end @ /Users/kodyzhou/Downloads/block.m:20:32
ldr r0, [sp, #56]
.loc 1 20 20 is_stmt 0 @ /Users/kodyzhou/Downloads/block.m:20:20
str r0, [sp, #12] @ 4-byte Spill
mov r0, r2
ldr r1, [sp, #12] @ 4-byte Reload
bl _objc_initWeak
Ltmp1:
add r1, sp, #48
add r2, sp, #16
movw lr, :lower16:(___block_descriptor_tmp-(LPC0_0+4))
movt lr, :upper16:(___block_descriptor_tmp-(LPC0_0+4))
LPC0_0:
add lr, pc
movw r3, :lower16:("___19-[KDTest testBlock]_block_invoke"-(LPC0_1+4))
movt r3, :upper16:("___19-[KDTest testBlock]_block_invoke"-(LPC0_1+4))
LPC0_1:
add r3, pc
movw r9, #0
movw r12, #0
movt r12, #49664
movw r4, :lower16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC0_2+4))
movt r4, :upper16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC0_2+4))
LPC0_2:
add r4, pc
ldr r4, [r4]
.loc 1 21 8 is_stmt 1 @ /Users/kodyzhou/Downloads/block.m:21:8
add.w r5, r2, #24
add.w r6, r2, #20
.loc 1 21 19 is_stmt 0 @ /Users/kodyzhou/Downloads/block.m:21:19
str r4, [sp, #16]
str.w r12, [sp, #20]
str.w r9, [sp, #24]
str r3, [sp, #28]
str.w lr, [sp, #32]
adds r2, #24
str r0, [sp, #8] @ 4-byte Spill
mov r0, r2
str r6, [sp, #4] @ 4-byte Spill
str r5, [sp] @ 4-byte Spill
bl _objc_copyWeak
ldr r0, [sp, #56]
.loc 1 21 19 discriminator 1 @ /Users/kodyzhou/Downloads/block.m:21:19
bl _objc_retain
add r1, sp, #16
.loc 1 21 19 @ /Users/kodyzhou/Downloads/block.m:21:19
str r0, [sp, #36]
.loc 1 21 8 discriminator 2 @ /Users/kodyzhou/Downloads/block.m:21:8
mov r0, r1
bl _objc_retainBlock
block在创建的时候一开始是放在栈上的,调用了最后的_objc_retainBlock后才会拷贝到堆上,block本质就是一个结构体,布局如下图,当需要捕获外部变量的时候会把捕获的变量放到结构体内,总之这里关键就是要看是否有将self强引用并捕获到block内,我们首先要先找到存放block指针的地方
movw r4, :lower16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC0_2+4))
movt r4, :upper16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC0_2+4))
LPC0_2:
add r4, pc
ldr r4, [r4]
.loc 1 21 8 is_stmt 1 @ /Users/kodyzhou/Downloads/block.m:21:8
add.w r5, r2, #24
add.w r6, r2, #20
.loc 1 21 19 is_stmt 0 @ /Users/kodyzhou/Downloads/block.m:21:19
str r4, [sp, #16]
这里就是用来初始化block第一个成员isa指针的部分,将指针存到r4然后通过str指令写入栈内,可以看到它在栈上的偏移是16,按照struct的布局继续往下看
str.w r12, [sp, #20]
str.w r9, [sp, #24]
str r3, [sp, #28]
str.w lr, [sp, #32]
adds r2, #24
str r0, [sp, #8] @ 4-byte Spill
mov r0, r2
str r6, [sp, #4] @ 4-byte Spill
str r5, [sp] @ 4-byte Spill
bl _objc_copyWeak
ldr r0, [sp, #56]
.loc 1 21 19 discriminator 1 @ /Users/kodyzhou/Downloads/block.m:21:19
bl _objc_retain
add r1, sp, #16
.loc 1 21 19 @ /Users/kodyzhou/Downloads/block.m:21:19
str r0, [sp, #36]
在连续存储了栈偏移为20、24等几个变量后,可以看到有句ldr r0, [sp, #56],前面说到这里存储的是self的地址,把self地址存到r0后马上调用了_objc_retain方法,这个方法会将r0指向的对象引用计数+1,然后随即将这个对象的地址存放到栈偏移36的地方,这里应该就是强引用self的部分了,证据找到了!不过为了让结果更明显顺便贴下当显式指明self情况时的汇编代码
.loc 1 21 9 is_stmt 1 @ /Users/kodyzhou/Downloads/block.m:21:9
add.w r5, r2, #20
.loc 1 21 20 is_stmt 0 @ /Users/kodyzhou/Downloads/block.m:21:20
str r4, [sp, #12]
str.w r12, [sp, #16]
str.w r9, [sp, #20]
str r3, [sp, #24]
str.w lr, [sp, #28]
adds r2, #20
str r0, [sp, #4] @ 4-byte Spill
mov r0, r2
str r5, [sp] @ 4-byte Spill
bl _objc_copyWeak
add r0, sp, #12
.loc 1 21 9 discriminator 1 @ /Users/kodyzhou/Downloads/block.m:21:9
bl _objc_retainBlock
可以看到这时没有objc_retain只执行了objc_copyWeak,所以不加self会导致额外的retain即强持有self
最后的最后看一下block调用的反编译结果
int ___19-[KDTest testBlock]_block_invoke(int arg0) {
var_18 = objc_loadWeakRetained(arg0 + 0x28);
rax = var_18 + *_OBJC_IVAR_$_KDTest._testString;
objc_storeStrong(rax, @"1");
objc_storeStrong(*(arg0 + 0x20) + *_OBJC_IVAR_$_KDTest._testString, @"2");
[var_18 setTestString:@"3"];
rax = objc_storeStrong(var_18, 0x0);
return rax;
}
可以看到不同于重写的C++方法,这里加不加self会导致不同的赋值方式,不加self的情况会使用block中持有的self来访问。
至此可以确定在block中重定义了self的情况下_qbosstraceInfo和self->_qbosstraceInfo不等同,前者会导致blcok强持有外部的self。
总结
对于strongify有两种不同实现,各有优缺点
__strong KDTest *self = weak_self;
第一种是重新定义一个和self命名不同的变量比如strong_self,然后后面都用这个strong_self来操作,这种写法优点是含义很明确、不会造成误解,因为只用了strong_self所以很明确不会捕获外部的self,但缺点是得时刻注意不要错写成self__strong KDTest *strong_self = weak_self;
第二种就是空间里面使用的,重新定义的变量就叫self(其实这里编译器也不让重新定义self的,只是在宏里面强行掩盖掉了),优点是发消息的时候不用担心写错了直接用self就行,但缺点是直接访问成员变量时必须指明self否则会强引用住外部的self,由于很容易误以为写不写self是一样的,对于不熟悉的人很容易忽视掉这最重要的一点
总而言之要把握weak-strong dance正确的使用姿势还是需要多多注意,不明白实现的话很容易写出有问题的代码,終わり(´-ω-`)
更多精彩内容欢迎关注腾讯 Bugly的微信公众账号:

腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!
关于 Block 中捕获 self 的分析的更多相关文章
- dispatch_async 的 block 中是否该使用_weak self
问题分析 我看过很多文章关于在dispatch_async的block里面使用_weak self, 但是让我疑惑的是,以下代码是否需要必须使用_weak self, 因为我也看到了很多观点说,在有些 ...
- iOS 中block中使用了外部变量的分析
例子1: ; void (^blk)(void) = ^(){ printf("in block %d[%p]\n", val, &val); //in block 10[ ...
- AI框架中图层IR的分析
摘要:本文重点分析一下AI框架对IR有什么特殊的需求.业界有什么样的方案以及MindSpore的一些思考. 本文分享自华为云社区<MindSpore技术专栏 | AI框架中图层IR的分析> ...
- block中如何避免循环引用
使用 weak–strong dance 技术 block 可以直接引用 self,但是要非常小心地在 block 中引用 self.因为在 block 引用 self,可能会导致循环引用.如下例所示 ...
- block中出现此种报错: Incompatible block pointer types initializing 'float (^__strong)(float, float)' with an expression of type 'int (^)(float, float)'
当block(代码块)的返回值是float时,应注意的地方:定义的返回值类型一定要与return的返回值类型一样 我们以两个数的四则运算来举例 在main.m文件中的四则运算中,我采用两种返回值类型( ...
- Block中的引用循环
原文地址:http://www.cnblogs.com/lujianwenance/p/5910490.html Block在实际的开发中非常的常用,事件回调.传值.封装成代码块调用等等.很多人都对b ...
- Block中__block实现原理
三.Block中__block实现原理 我们继续研究一下__block实现原理. 1.普通非对象的变量 先来看看普通变量的情况. #import <Foundation/Foundation.h ...
- iOS开发-多层嵌套block中如何使用__weak和__strong
1.关于__weak__weak只能在ARC模式下使用,也只能修饰对象(比如NSString等),不能修饰基本数据类型(比如int等)__weak修饰的对象在block中不可以被重新赋值.__weak ...
- Block详解一(底层分析)
本篇博客不再讲述Block的基本定义使用,最近而是看了很多的block博客讲述的太乱太杂,所以抽出时间整理下block的相关底层知识,在讲述之前,提出几个问题,如果都可以回答出来以及知道原理,大神绕过 ...
随机推荐
- 有关Lambda的一些思考
问题: What do lambda expressions do? Can we write all functions as lambda expressions? In what cases a ...
- laravel的路由设置,路由参数和路由命名(三)
laravel中必须先配置路由,才能使用.不像tp中不配置也能使用,因为tp可以通过pathinfo进行自动解析. 一.简单的路由设置 我们一般在routes/web.php文件中配置网页端路由. / ...
- VS2015一新建项目就出现未将对象引用设置到对象的实例怎么办?[z]
https://blog.csdn.net/tiandyoin/article/details/79722800 在控制面板-卸载或修复程序太麻烦,而且不一定保证解决,可以这样------打开--C: ...
- 《探索未知种族之osg类生物》目录
精力有限,博客园不在更新<探索未知种族之osg类生物>.在这里列出所有文章目录(持续更新)有兴趣的同学可以看看. 探索未知种族之osg类生物[目录] 前序 探索未知种族之osg类生物--- ...
- Java高级应用简笔
1. Annotation 使用范围: package, class, method, field 常用: @Override, @Deprecated, @SuppressWarnings 自定义注 ...
- Read-only file system
mount -o remount rw /
- Python之PIL库
Python PIL PIL (Python Image Library) 库是Python 语言的一个第三方库,PIL库支持图像存储.显示和处理,能够处理几乎所有格式的图片. 一.PIL库简介 1. ...
- 从阿里巴巴面试题到java类加载机制
首先很经典的阿里巴巴面试题 加上我自己的一些疑惑代码 public class Text { public static int k = 0; public final int k1 = 3; //自 ...
- 让粒子可以在白色背景显示 [Blending Shader 实操]
Unity3D 提供了粒子特效的各种shader,今天要说的是 Additive(因为项目最初就是用了Additive 发生了问题.. ε=ε=ε=┏(゜ロ゜;)┛) Additive Particl ...
- PostgreSQL 数据库备份
--CMD管理员进入 --进入目录: C:\Program Files\PostgreSQL\9.6\bin --备份: pg_dump -U postgres MP > C:\mptest.b ...