ObjC如何通过runtime修改Ivar的内存管理方式(一)

为什么要这么做?

在iOS 9之前,UITableView(或者更确切的说是 UIScrollView)有一个众所周知的问题:

@property (nonatomic, assign) id delegate;

苹果将 delegate 的内存修饰符声明为了assign,这是 MRC 时代防止循环引用的不二法门。但是到了 ARC 时代,苹果引入了弱引用修饰符(weak)对原先的(assign)暨非强引用修饰符进行了细分。在大多数场景下,将 delegate 声明为assign并不会产生什么严重后果,因为 delegate 对象(例如 UIViewController)通常持有了这个 UIScrollView,当 delegate 对象释放的时候,UIScrollView 也会被一起释放。

然而只要存在发生意外的风险,意外就一定会发生。如果在 delegate 对象释放的时候,UIScrollView 因为某些原因正在被其他对象强持有而导致没有被一起释放,那么当 UIScrollView 在之后调用 delegate 方法的时候就会崩溃,因为这个时候 delegate 已经是一个野指针了。最常见的导致 UIScrollView 没有被及时释放的原因是滚动所带来的动画,因为系统在渲染动画的时候需要强持有这个 view,而 UIScrollView 这种天生内置动画效果的类就变成了受到这个 assign 修饰符影响最广泛的类。

因为国内用户对iOS系统的更新并不像国外那样普遍,至今仍然有大量手机运行着iOS 7.x和8.x,很多app也因此一直保持着对iOS 7.x和8.x系统的支持,所以这个问题在iOS 11都即将到来的时代仍然持续不断地困扰着众多的iOS开发者。

第一个非常流行的解决方案:

在 delegate 对象的 dealloc 方法里将 UIScrollView 的 delegate 属性置空。

这个看似简单的解决办法却也带来了两个额外的问题,一是只能对有源代码的类进行修改,那些没有源代码的第三方库是没有办法进行修复的。二是就算是自己写的类,人都会犯错或疏忽大意,忘记在dealloc里面将 delegate 置空会导致这个问题依然还会时不时的出现。在后面的文章为了简明起见,我们将这种方法称之为方案1

那有没有办法解决上面提到的这两个问题呢?答案是肯定的。可能已经有人想到用 oc runtime 的方法替换的去做,替换 NSObject 的 dealloc 方法和 UIScrollView 的setDelegate:方法。具体方法在这里就不展开细说了,大家有兴趣可以参考这里。在后面的文章为了简明起见,我们将这种方法称之为方案2


我们为什么还要继续?

提出方案2的时候,这个关于 UIScrollView 崩溃的问题已经比较完美地被解决了。剩下的无非比较权衡方案2的各种实现之间的优劣而已,那我们为什么还要继续呢?

我在最开始在崩溃日志上看到 UIScrollView 的崩溃的时候,经过 google 和 stackoverflow 大法搞明白崩溃的原因之后,跳入我脑中的完美解决方案,即不是方案1也不是方案2,而是:

如何将一个已经在编译时确定为__unsafe_unretained的成员变量在运行时重新声明为__weak

我们姑且称之为方案3。事情往往没有那么简单,在这条直接粗暴看似捷径的小路上,其实荆棘遍地步履维艰。方案3需要对 objective c 有着深入的理解和认知,所需要的逻辑和方法也远比方案1方案2晦涩难懂。如果你只想解决UIScrollView 在ios 9之前因为 delegate 被声明为assign所导致的崩溃的话,那么无论方案1或者方案2都是非常简单有效的解决方案,直接套用即可。如果你和我一样,想顺便探索一下 objective c 的秘密的话,我邀请你和我一起继续前行。


成员变量 Ivar 及内存修饰符

既然问题的症结在于成员变量 Ivar 在编译时所使用的修饰符是错误的,那 Ivar 以及它的修饰符到底是什么呢?

如果你熟悉oc的源码,你可能很清楚的知道 Ivar 与属性(property)的不同。我们现在写代码所使用的通常都是使用 property 来间接定义 Ivar 。当前的 XCode 已经很少需要在声明 property 的时候同时声明 Ivar ,大部分场景下编译器会自动声明对应的 Ivar(使用 property 的名字前面加下划线的方式命名),并为之创建默认的gettersetter。这极大的简化了代码,避免像 Java 一样一个类包含大量冗余方法。例如:

// MCCLabelView.h
@interface MCCLabelView : UIView
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值