散列表结构分析
在之前的@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);
第三个为什么是”2“呢?按照常规思路打上断点,然后
debug打开汇编
发现这里出现了一个方法objc_initWeak,
我们打开libObjc源码:
objc_initWeak -> storeWeak .... 这个结构跟关联对象差不多
- 首先找到散列表
- 找到散列表里面的weakTable的弱引用表
- 创建一个
weak_entry_t - 把
referent加入到weak_table的数组inline_referrers >= TABLE_SIZE(entry) * 3/4weak_table扩容- 把
new_entry加入到weak_table中
接着继续step over走到了这个方法
找到libobjc源码
objc_loadWeakRetained这个方法一步步调试
发现在563行的时候引用计数还是1,但是到了566行引用计数就变成了2。我们由此猜测,弱引用在
rootTryRetain的时候执行了+1的操作,既然弱引用可以+1那么我再打印一次,这里的count是不是就成了3?我们来验证下
弱引用的时候把当前的对象拷贝了一份到引用计数表里,weakObjc是从自己的弱引用表里操作引用计数。弱引用表和ARC的强引用是相互独立的关系,
Timer的强持有
下面操作有什么问题?
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
我们在dealloc的方法里面销毁定时器,但是当这个页面pop返回的时候页面并没有被释放,所以这个定时器还在一直走。此时造成了循环引用。我们试下用weak修饰self
pop返回的时候仍然没有销毁,那么为什么呢?command + shift + O找到官方文档说明
这里的说明的是直到Timer被销毁之前这里的都是强持有,所以即便我们使用weakSelf修饰,这里仍然造成了循环引用。
解决方法一:使用下面这个方法,这个是没有对self进行强持有
scheduledTimerWithTimeInterval:repeats:block:
解决方法二:中介者模式 target不使用self FBKVO思维
之前的持有方式是:RunLoop -> Timer -> self -> Timer
现在的方式是:RunLoop -> Timer -> TimerProxy 没有对self的操作,所以self在dealloc的时候可以销毁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 销毁了")
}
}
}