散列表结构分析
散列表本质就是一张哈希表
,散列表的类型是SideTable
,有如下定义
struct SideTable {
spinlock_t slock;//开/解锁
RefcountMap refcnts;//引用计数表
weak_table_t weak_table;//弱引用表
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
}
- 包含
一把锁
、一张引用计数表
和一张弱引用表
- 对
SideTable
的存取,会牵扯加锁
和解锁
的耗时操作,为了线程安全。所以当多个对象同时操作SideTable
时,为了保证效率,采用多张SideTable表分散压力
- 当对象分散使用多张表时,当
表中的对象全部释放后
,该表也可以释放
,这样可以及时回收内存
- 由于开表所消耗的内存过大,如果针对每个对象都开一张表,会造成很大程度的内存浪费。所以
SideTable
的核心作用,对引用计数表
和弱引用表
进行处理。
上节课我们在探索retain
流程时,有一步是判断是否是纯isa
?这个过程中调用了sidetable_tryRetain
函数,这里在objc源码
中查看
bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
// SideTables表示有很多张表
SideTable& table = SideTables()[this];
bool result = true;
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);
auto &refcnt = it.first->second;
if (it.second) {
// there was no entry
} else if (refcnt & SIDE_TABLE_DEALLOCATING) {
result = false;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
refcnt += SIDE_TABLE_RC_ONE;
}
return result;
}
<!-- SideTables -->
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
<!-- StripedMap -->
// 散列表底层使用的是StripedMap
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
......
}
SideTables
内部是通过SideTablesMap
的get
方法获取的,而SideTablesMap
是通过StripedMap<SideTable>
定义的。
散列表为什么在内存有多张?最多能够多少张?
- 如果
散列表
只有一张表,意味着全局所有的对象都会存储在一张表中,都会进行开锁解锁
(锁是锁整个表的读写)。当开锁时,由于所有数据都在一张表,则意味着数据不安全
- 如果每个对象都开一个表,会
耗费性能
,所以也不能有无数个表
- 根据
不同系统架构
,可创建8
或64
张SideTable
,在iOS设置上只能创建8张
-
objc源码
中查看sidetable_retain
函数
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
// 获取到当前对象所在的散列表
SideTable& table = SideTables()[this];
if (!locked) table.lock();
// 获取当前散列表的存储空间
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
- 通过当前对象,找到所属的
SideTable
- 通过当前对象,找到
引用计数表
的所属空间 - 所属空间中
并不是直接存储引用计数
,而是使用位域
存储很多信息 - 核心代码:
refcntStorage += SIDE_TABLE_RC_ONE
- 查看
SIDE_TABLE_RC_ONE
的定义
// 弱引用标记
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
// 是否正在析构
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
// 引用计数
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
// 标记引用计数是否越界
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
1左移2位
结果为4,转为十六进制为100
。因为真正的refcnt
在位域中的第2位上存储
,+100
的目的就是不影响前面两位
,实则引用计数+1
。
关于引用计数的问题
打印对象的引用计数,代码如下
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *objc = [[NSObject alloc] init];
NSLog(@"objc:%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
}
// 控制台打印
objc:1
- 在老版
objc源码
中,对象alloc默认为0
。为了避免它的释放,在rootRetainCount
方法中,系统对引用计数默认+1
:

仅在读取时对引用计数+1
,实际上extra_rc
中的引用计数仍然为0
。
- 在
objc4-818.2版本
的源码中,对rootRetainCount
函数做了修改
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
if (bits.nonpointer) {
uintptr_t rc = bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
没有+1
的处理,直接获取extra_rc
。如果使用散列表
,再加上散列表中的引用计数
。
这里的变化源于alloc的底层修改
,当对象alloc
时调用initIsa
函数。在isa初始化时,引用计数已经设置为1

验证weakObjc
的引用计数
修改代码如下,进行调试对象的引用计数
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *objc = [[NSObject alloc] init];
NSLog(@"objc:%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
__weak typeof(id) weakObjc = objc;
NSLog(@"objc:%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
NSLog(@"objc:%ld",CFGetRetainCount((__bridge CFTypeRef)(weakObjc)));
}
// 控制台打印
objc:1
objc:1
objc:2
当前objc对象alloc
初始化之后,引用计数为1
。当对象被弱引用变量持有
,相当于加入弱引用表
并不影响引用计数,所以第二次打印为1
。为什么打印weakObjc
的引用计数为什么是2
呢?下面进行探索......
弱引用表
下面我们来探索为什么打印weakObjc
的引用计数为2呢?
- 在
weak
修饰的变量处设置断点,运行项目,查看汇编代码

- 调用
objc_initWeak
函数,来自libobjc框架

- 在
objc4-818.2源码
中查看objc_initWeak
函数
id
objc_initWeak(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj);
}
storeWeak
是一个模板类,内部逻辑具有高度封装性
,通过传入不同参数
执行不同的逻辑分支。
- 查看
template <HaveOld haveOld, HaveNew haveNew,
enum CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj) {
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
//location:弱引用对象指针
//newObj:原始对象
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
retry:
//判断haveOld
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
//在objc_initWeak中调用时,传入的DontHaveOld,不存在
//将oldTable设置为nil
oldTable = nil;
}
//判断haveNew
if (haveNew) {
//在objc_initWeak中调用时,传入的DoHaveNew,存在
//找到原始对象所在的SideTable
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
//防止弱引用机制之间的死锁
//确保Class的初始化
//为弱引用对象未初始化isa
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&!((objc_class *)cls)->isInitialized()) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);
//赋值previouslyInitializedClass,防止重复初始化
previouslyInitializedClass = cls;
goto retry;
}
}
if (haveOld) {
//存在旧值,将其移除
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (haveNew) {
//调用weak_register_no_lock函数,传入当前对象所在散列表中的weak_table,并传入原始对象和弱引用对象指针
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
if (!newObj->isTaggedPointerOrNil()) {
newObj->setWeaklyReferenced_nolock();
}
//将原始对象赋值给弱引用对象
*location = (id)newObj;
} else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
- 参数为
弱引用对象指针location
和原始对象newObj
- 判断
haveOld
在objc_initWeak
中调用时,传入的DontHaveOld
,不存在将oldTable
设置为nil
- 判断
haveNew
,在objc_initWeak
中调用时,传入的DoHaveNew
,存在找到原始对象
所在的SideTable
- 判断
haveNew
和原始对象
,防止弱引用机制之间的死锁
,确保Class
的初始化,为弱引用对象未初始化isa
- 判断
haveOld
,存在旧值
将其移除 - 判断
haveNew
存在
6.1 调用weak_register_no_lock
函数,传入当前对象所在散列表中的weak_table
,并传入原始对象
和弱引用对象指针
6.2 将原始对象
赋值给弱引用对象
- 将
原始对象
返回,流程结束。
- 查看
weak_register_no_lock
函数
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) {
//referent_id:原始对象
//referrer_id:弱引用对象的指针
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (referent->isTaggedPointerOrNil()) return referent_id;
//确保引用的对象是可用的
if (deallocatingOptions == ReturnNilIfDeallocating || deallocatingOptions == CrashIfDeallocating) {
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
} else {
auto allowsWeakReference = (BOOL(*)(objc_object *, SEL)) lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference), referent->getIsa());
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating = ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
}
if (deallocating) {
if (deallocatingOptions == CrashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
}
weak_entry_t *entry;
//判断在表中是否能找到对象的存储
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//已存在,将弱引用对象追加到表中
append_referrer(entry, referrer);
} else {
//不存在,创建weak_entry_t,将原始对象和弱引用对象关联
weak_entry_t new_entry(referent, referrer);
//重新开辟一张表
weak_grow_maybe(weak_table);
//将new_entry插入到表中
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
- 传入原始对象
referent_id
和弱引用对象的指针referrer_id
- 一系列判断,确保
引用的对象是可用的
- 判断在表中
是否能找到对象的存储
3.1. 已存在,将弱引用对象
追加到表中
3.2 不存在:创建weak_entry_t
,将原始对象
和弱引用对象
关联;重新开辟一张表;将new_entry
插入到表中。 - 将
原始对象
返回,流程结束。
- 查看
append_referrer
函数
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) {
if (! entry->out_of_line()) {
// Try to insert inline.
//传入原始对象所属的weak_entry_t结构体指针和弱引用对象指针
//找到weak_entry_t下的inline_referrers,一个对象可能被赋值给多个弱引用对象
//循环找到空位,如果存在空位直接赋值
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer; return;
}
}
// Couldn't insert inline. Allocate out of line.
//没有空位,重新创建new_referrers
weak_referrer_t *new_referrers = (weak_referrer_t *) calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
//将现有entry下的inline_referrers中的元素,循环导入到新创建的new_referrers中
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
//相关成员变量的覆盖
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
ASSERT(entry->out_of_line());
//扩容相关操作
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
- 传入
原始对象
所属的weak_entry_t结构体指针
和弱引用对象指针
- 找到
weak_entry_t
下的inline_referrers
,一个对象可能被赋值给多个弱引用对象
- 循环找到
空位
,如果存在空位直接赋值
- 没有空位,重新创建
new_referrers
- 将现有
entry
下的inline_referrers
中的元素,循环导入到新创建的new_referrers
中 - 相关成员变量的覆盖
- 扩容相关操作。
关于弱引用的引用计数
weakObjc
引用计数为2
的原因?
- 查看
汇编代码

弱引用对象
在使用时,会调用objc_loadWeakRetained
函数。汇编代码中的两次调用,第一次在执行CFGetRetainCount
函数之前,第二次在执行NSLog
方法之前。
- 在
objc4-818.2源码
中查看objc_loadWeakRetained
函数
id
objc_loadWeakRetained(id *location) {
id obj;
id result;
Class cls;
SideTable *table;
retry:
//对弱引用对象指针取值
obj = *location;
if (obj->isTaggedPointerOrNil()) return obj;
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
//赋值给result临时变量
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
ASSERT(cls->isInitialized());
//调用retain函数,对引用计数+1
if (! obj->rootTryRetain()) {
result = nil;
}
} else {
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL)) lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
} else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
result = nil;
}
} else {
table->unlock();
class_initialize(cls, obj);
goto retry;
}
}
table->unlock();
return result;
}
- 传入
弱引用对象指针
- 对弱引用对象指针取值,赋值给
obj
,相当于原始对象 - 将
obj
赋值给result
,此时引用计数不变 - 核心代码:
if (! obj->rootTryRetain())
,obj
调用retain
函数,引用计数+1
使用弱引用对象
触发objc_loadWeakRetained
函数,对象会进行一次retain
操作。
这样做的目的:避免弱引用对象
在使用过程中,由于原始对象被释放,导致所有正在使用的弱引用对象
全部取值异常,造成大面积的连锁反应
。当objc_loadWeakRetained
函数执行完毕,临时变量会释放,自动恢复对象的引用计数。采用这种方式的好处,让原始对象
和弱引用对象
更加独立,对强弱引用对象进行分开管理
。

timer的强持有
NSTimer
定时器循环引用问题
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)fireHome {
num++;
NSLog(@"hello word - %d",num);
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
创建NSTimer
时传入self
,导致NSTimer
持有self
,而self
又持有timer
。使用NSTimer
的invalidate
方法,可以解除NSTimer
对self
的持有,但NSTimer
的invalidate
方法在dealloc
方法内调用。而self
又被timer
持有,dealloc
方法不会执行。故此双方相互等待,造成循环引用
。
对NSTimer
的target
参数传入一个弱引用self
,能否解除循环引用?
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
运行工程发现依然存在循环引用
,原因是[NSRunLoop currentRunLoop]
->timer
->weak
->self
链路中,在timerWithTimeInterval
内部,使用强引用对象
接收target
参数,所以外部定义为弱引用对象
没有任何意义。
官方文档中,对target
参数的说明

target
:定时器触发时指定的消息发送到的对象。计时器维护对该对象的强引用
,直到它(计时器)失效。
和Block
的区别,Block
将捕获到的弱引用对象
,赋值给一个强引用的临时变量
,当Block
执行完毕,临时变量
会自动销毁,解除对外部变量
的持有。
解决方案
- 方案一:
更换API
,使用携带Block
的方法创建NSTimer
,避免target的强持有
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block;
- 方案二:修改
invalidate
调用时机
// 将NSTimer的invalidate方法写在viewWillDisappear方法中
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}
// 或者写在didMoveToParentViewController方法中
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)didMoveToParentViewController:(UIViewController *)parent {
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
}
}
- 方案三:中介者模式
通过切断target的强持有
解决循环引用
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *objc = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:objc selector:@selector(fireHome) userInfo:nil repeats:YES];
}
void fireHomeObjc(id obj) {
num++;
NSLog(@"hello word - %d -%@",num, obj);
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
- 创建
NSObject
实例对象objc
,通过Runtime
对NSObject
增加fireHome
方法,IMP
指向fireHomeObjc
的函数地址; - 创建
NSTimer
,将objc
传入target
参数,这样避免NSTimer
对self
的强持有; - 当页面退出时,由于
self
没有被NSTimer
持有,正常调用dealloc
方法: - 在
dealloc
中,对NSTimer
进行释放。此时NSTimer
对objc
的强持有解除,objc
也跟着释放。
- 方案四:封装
自定义Timer
创建LGTimerWapper
,实现自定义Timer
的封装
<!-- LGTimerWapper.h -->
#import <Foundation/Foundation.h>
@interface LGTimerWapper : NSObject
- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (void)lg_invalidate;
@end
<!-- LGTimerWapper.m -->
#import "LGTimerWapper.h"
#import <objc/message.h>
@interface LGTimerWapper()
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation LGTimerWapper
- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
if (self == [super init]) {
self.target = aTarget;
self.aSelector = aSelector;
if ([self.target respondsToSelector:self.aSelector]) {
Method method = class_getInstanceMethod([self.target class], aSelector);
const char *type = method_getTypeEncoding(method);
class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
}
return self;
}
void fireHomeWapper(LGTimerWapper *warpper){
if (warpper.target) {
void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer);
} else {
[warpper lg_invalidate];
}
}
- (void)lg_invalidate {
[self.timer invalidate];
self.timer = nil;
}
@end
- 在
LGTimerWapper
中,定义初始化lg_initWithTimeInterval
方法和lg_invalidate
释放方法; - 初始化方法:
2.1 控件内部的target
使用weak
修饰,对ViewController
进行弱持有;
2.2 检测target
中是否存在该selector
。如果存在,对当前类使用Runtime
添加同名方法编号,指向自身内部fireHomeWapper
的函数地址;
2.3 创建真正的NSTimer
定时器,将控件自身的实例对象传入target
,避免NSTimer
对ViewController
强持有。 - 当
NSTimer
回调时,会进入fireHomeWapper
函数:
3.1 函数内部不负责业务处理,如果target
存在,使用objc_msgSend
,将消息发送给target
自身下的selector
方法。 - 当页面退出时,
ViewController
可以正常释放。但LGTimerWapper
和NSTimer
相互持有,双方都无法释放; - 由于双方都无法释放,
NSTimer
的回调会继续调用:
5.1 当进入fireHomeWapper
函数,发现target
已经不存在了,调用LGTimerWapper
的lg_invalidate
方法,内部对NSTimer
进行释放;
5.2 当NSTimer
释放后,对LGTimerWapper
的强持有解除,LGTimerWapper
也跟着释放。
- 方案五:
NSProxy
虚基类
NSProxy
的作用:
-
OC
不支持多继承,但是它基于运行时机制
,可以通过NSProxy
来实现伪多继承
; -
NSProxy
和NSObject
属于同一级别的类,也可以说是一个虚拟类
,只实现了NSObject
的协议部分; -
NSProxy
本质是一个消息转发封装的抽象类
,类似一个代理人。
可以通过继承NSProxy
,并重写以下两个方法实现消息转发:
- (void)forwardInvocation:(NSInvocation *)invocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
NSProxy
除了可以用于多继承
,也可以作为切断强持有的中间人
<!-- LGProxy.h -->
#import <Foundation/Foundation.h>
@interface LGProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
<!-- LGProxy.m -->
#import "LGProxy.h"
@interface LGProxy()
@property (nonatomic, weak) id object;
@end
@implementation LGProxy
+ (instancetype)proxyWithTransformObject:(id)object {
LGProxy *proxy = [LGProxy alloc];
proxy.object = object;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
if (!self.object) {
NSLog(@"异常收集-stack");
return nil;
}
return [self.object methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
if (!self.object) {
NSLog(@"异常收集-stack");
return;
}
[invocation invokeWithTarget:self.object];
}
@end
// LGProxy的使用代码
- (void)viewDidLoad {
[super viewDidLoad];
self.proxy = [LGProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
}
- (void)fireHome {
num++;
NSLog(@"hello word - %d",num);
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
-
LGProxy
初始化方法,将传入的object
赋值给弱引用对象
; - 在
UIViewController
中,创建LGProxy
对象proxy
。创建NSTimer
对象,将proxy
传入target
,避免NSTimer
对ViewController
强持有; - 当
NSTimer
回调时,触发LGProxy
的消息转发方法:
3.1methodSignatureForSelector
:设置方法签名;
3.2forwardInvocation
:自身不做业务处理,将消息转发给object。 - 当页面退出时,
ViewController
可以正常释放:
4.1 在dealloc
中,对NSTimer
进行释放。此时NSTimer
对proxy
的强持有解除,proxy
也跟着释放。
网友评论