Closed fabcz closed 2 years ago
厉害了,我 merge 一下你的 commit,然后明天周末发个 1.0.6
太强啦
偶然看到,厉害
我们内部的一些库都是全局MRC,也是为了防止类似这种问题
- (instancetype)cc_initWithFrame:(CGRect)frame { UIView *view = [self cc_initWithFrame:frame]; view.layer.lks_hostView = view; return view; }
为什么把 view.layer.lks_hostView = view; 注释掉了
膜拜大佬
@TheLittleBoy 应该是为了方便讲解Case,简化了反编译后的代码流程。正式的 Pull Request还是保留的
@TheLittleBoy 应该是为了方便讲解Case,简化了反编译后的代码流程。正式的 Pull Request还是保留的
方案二 和 原方案 有什么区别吗?
@TheLittleBoy 仔细看,方案二是 initWithFrame_correct
, init
开头的,编译会有优化,就刚好 “匹配” 上了
@TheLittleBoy 仔细看,方案二是
initWithFrame_correct
,init
开头的,编译会有优化,就刚好 “匹配” 上了
确实是刚好"匹配"上,解决问题的代码很简单就是方法重命名以 init 开头,算是 iOS 的冷门知识吧
我们内部的一些库都是全局MRC,也是为了防止类似这种问题
掌握在自己手上确实靠谱,但同时这些库维护成本也高了,看团队如何取舍吧,不然时不时来个类似的问题查起来也痛苦
我们内部的一些库都是全局MRC,也是为了防止类似这种问题
掌握在自己手上确实靠谱,但同时这些库维护成本也高了,看团队如何取舍吧,不然时不时来个类似的问题查起来也痛苦
是的=。=,维护起来确实要小心,所以尽量控制在很小的范围内,最核心的部分
这个应该不是iOS16或者XCode14的新特性吧,为啥老版本的系统上没有问题,iOS16上大量出现呢
另外,是不是只要是return一个局部对象的时候都会有这样的问题啊,而不只是init方法
今天刚看到,很棒👍🏻
Demo.zip
背景
先上一段不规范的代码
正常情况下 App 跑起来界面是看不到灰色控件的(property 修饰符是 weak),但在 Debug 环境下有 lookin 的加持,控件得以正常展示,而上线后没有 lookin 就凉了界面直接消失
问题原因
引发这个问题的原因是 hook 了 UIView 的 init 方法,交换的方法内部建了个临时变量引发了 TLS 优化导致引用计数 + 1,方法内部引用计数不平衡因此就延迟释放了
问题分析
先过一下 objc4 源码里几个函数的基本概念
Thread-local storage(TLS)
TLS + objc_retainAutoreleasedReturnValue + objc_autoreleaseReturnValue
objc_autoreleaseReturnValue
方法时,runtime 将这个返回值 object 储存在TLS
中,然后直接返回这个 object (不调用autorelease
);同时,在外部接收这个返回值的objc_retainAutoreleasedReturnValue
里,发现TLS
中正好存了这个对象,那么直接返回这个 object (不调用retain
)。TLS
做中转,很有默契的免去了对返回值的内存管理。objc_retainAutoreleasedReturnValue
、acceptOptimizedReturn
// Try to accept an optimized return. // Returns the disposition of the returned object (+0 or +1). // An un-optimized return is +0. static ALWAYS_INLINE ReturnDisposition acceptOptimizedReturn() { ReturnDisposition disposition = getReturnDisposition(); setReturnDisposition(ReturnAtPlus0); // reset to the unoptimized state return disposition; }
// Prepare a value at +1 for return through a +0 autoreleasing convention. id objc_autoreleaseReturnValue(id obj) { if (prepareOptimizedReturn(ReturnAtPlus1)) return obj; return objc_autorelease(obj); }
// Try to prepare for optimized return with the given disposition (+0 or +1). // Returns true if the optimized path is successful. // Otherwise the return value must be retained and/or autoreleased as usual. static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) { ASSERT(getReturnDisposition() == ReturnAtPlus0);
}
void objc_storeStrong(id location, id obj) { id prev = location; if (obj == prev) { return; } objc_retain(obj); *location = obj; objc_release(prev); }
// Accept a value returned through a +0 autoreleasing convention for use at +0. id objc_unsafeClaimAutoreleasedReturnValue(id obj) { if (acceptOptimizedReturn() == ReturnAtPlus0) return obj; return objc_releaseAndReturn(obj); }
汇编代码简化如下:临时保存的局部变量,加入 TLS 进行优化 lookin`-[UIView(Hook) cc_initWithFrame:]: objc_msgSend objc_retainAutoreleasedReturnValue // 被优化,引用计数不变 objc_retain // retain 引用计数 + 1 objc_storeStrong // release 引用计数 - 1 objc_autoreleaseReturnValue // cache 到 TLS,引用计数 + 1
汇编代码简化如下:无临时变量持有返回值,不使用 TLS 进行优化 lookin`-[UIView(Hook) correct_initWithFrame:]: objc_msgSend objc_unsafeClaimAutoreleasedReturnValue // 引用计数不变
汇编代码简化如下:方法以 init 开头 lookin`-[UIView(Hook) initWithFrame_correct:]: objc_msgSend objc_retain // 引用计数 + 1 objc_retain // 引用计数 + 1 objc_storeStrong // 引用计数 - 1 objc_storeStrong // 引用计数 - 1