OC底层原理探索之内存管理中

429 阅读3分钟

散列表结构分析

在之前的@Synchronization中我们知道一共可以有这么多张表

class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
    // ...
}

那么散列表到底是什么,我们找到对SideTable的结构体,发现里面有一张弱引用表和一张引用计数表

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
	//...
};

弱引用表

在探究之前先来一个测试题,下面打印输出的是多少

NSObject *objc = [[NSObject alloc]init];
NSLog(@"%ld -%@", (long)CFGetRetainCount(objc), objc);
__weak typeof(id) weakObjc = objc;
NSLog(@"%ld -%@", (long)CFGetRetainCount(objc), objc);
NSLog(@"%ld -%@", (long)CFGetRetainCount(weakObjc), weakObjc);

image.png 第三个为什么是”2“呢?按照常规思路打上断点,然后debug打开汇编

image.png

发现这里出现了一个方法objc_initWeak, 我们打开libObjc源码: objc_initWeak -> storeWeak .... 这个结构跟关联对象差不多

  1. 首先找到散列表
  2. 找到散列表里面的weakTable的弱引用表
  3. 创建一个weak_entry_t
  4. referent加入到weak_table的数组inline_referrers
  5. >= TABLE_SIZE(entry) * 3/4weak_table扩容
  6. new_entry加入到weak_table

接着继续step over走到了这个方法 image.png 找到libobjc源码objc_loadWeakRetained这个方法一步步调试 image.png 发现在563行的时候引用计数还是1,但是到了566行引用计数就变成了2。我们由此猜测,弱引用在rootTryRetain的时候执行了+1的操作,既然弱引用可以+1那么我再打印一次,这里的count是不是就成了3?我们来验证下 image.png 弱引用的时候把当前的对象拷贝了一份到引用计数表里,weakObjc是从自己的弱引用表里操作引用计数。弱引用表和ARC的强引用是相互独立的关系, ​

Timer的强持有

下面操作有什么问题?

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

image.png

我们在dealloc的方法里面销毁定时器,但是当这个页面pop返回的时候页面并没有被释放,所以这个定时器还在一直走。此时造成了循环引用。我们试下用weak修饰self

image.png pop返回的时候仍然没有销毁,那么为什么呢?command + shift + O找到官方文档说明 image.png

这里的说明的是直到Timer被销毁之前这里的都是强持有,所以即便我们使用weakSelf修饰,这里仍然造成了循环引用。

解决方法一:使用下面这个方法,这个是没有对self进行强持有

scheduledTimerWithTimeInterval:repeats:block:

解决方法二:中介者模式 target不使用self FBKVO思维

之前的持有方式是:RunLoop -> Timer -> self -> Timer 现在的方式是:RunLoop -> Timer -> TimerProxy 没有对self的操作,所以selfdealloc的时候可以销毁Timer,Timer就自动断开了对TimerProxy的强持有。

public class WeakTimerProxy: NSObject {
    weak var target: NSObjectProtocol?
    var sel: Selector?
    
    /// required,实例化timer之后需要将timer赋值给proxy,否则就算target释放了,timer本身依然会继续运行
    public weak var timer: Timer?
    
    public required init(target: NSObjectProtocol?, sel: Selector?) {
        self.target = target
        self.sel = sel
        super.init()
        // 加强安全保护
        guard target?.responds(to: sel) == true else {
            return
        }
        
        // 将target的selector替换为redirectionMethod,该方法会重新处理事件
        let method = class_getInstanceMethod(self.classForCoder, #selector(WeakTimerProxy.redirectionMethod))!
        class_replaceMethod(self.classForCoder, sel!, method_getImplementation(method), method_getTypeEncoding(method))
    }
    
    @objc func redirectionMethod () {
        // 如果target未被释放,则调用target方法,否则释放timer
        if self.target != nil {
            self.target!.perform(self.sel)
        } else {
            self.timer?.invalidate()
            FCLog("timer 销毁了")
        }
    }
}