Timer定时器循环引用问题
我们都知道, 在我们使用NSTimer时, addtarget如果是timer的持有者时, 就会造成循环引用. 因为timer会对当前的target强引用. 那么我们如何解决呢?
-
手动管理timer, 在使用完成后自己关闭timer并置为nil. 这个方法只适用于非循环调用的timer
-
如果是在iOS10以后, 我们可以直接使用timer的block构造方法, 将事件放入block中处理, 这样timer就不会强引用target
let timer = Timer(fire: Date(), interval: 1, repeats: true, block: { timer in
})
复制代码
- 使用其他对象, 替换target.
- 使用NSObject类作为target
我们自己定义一个NSObject类作为timer的target时, 可以利用iOS消息发送/转发的机制, 将原有的类作为NSObject的一个属性, 通过消息转发进行事件处理
@interface MyTarget : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation MyTarget
+ (instancetype)proxyWithTarget:(id)target {
MyTarget *proxy = [[MyTarget alloc] init];
proxy.target = target;
return proxy;
}
// 如果当前类没有对应的方法实现时, 会通过消息转发流程第二步, 返回一个实例去响应事件
// 我们将原有会循环引用的实例返回, 就可以解决timer的循环引用
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
复制代码
- 除了NSObject, iOS还有一个基类NSProxy, 可以专门作为”代理”来处理类似情景
@interface MyProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation MyProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
MyProxy *proxy = [MyProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
复制代码
与NSObject相比, NSProxy更适合做timer的target. 因为对于没有实现的方法, NSObject会通过消息转发的流程进行3次转发, 最终到达
forwardingTargetForSelector:
, 而NSProxy则会直接调用forwardInvocation:
, 并不会进行消息转发流程, 性能要比NSObject高一点
比NSTimer/CADisplayLink更精准的定时器
- NSTimer/CADisplayLink
我们知道, Timer/CADisplayLink定时器是将timer加入到当前runloop中去执行, 当runloop在每次循环中执行timer, 如果当前runloop发生卡顿的话, 那么必然会造成timer不精确.
- GCD-Timer
我们可以通过GCD创建timer, GCD创建的timer与runloop无关, 所以会更加精准
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置时间
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC, 0);
// 设置定时器任务
dispatch_source_set_event_handler(timer, ^{
});
// 启动定时器
dispatch_resume(timer);
// 关闭定时器
dispatch_source_cancel(timer);
复制代码
iOS中的weak是如果实现的
iOS中, weak修饰的对象在被释放时, weak变量会被置为nil, 此时对weak变量发送消息则不会发生崩溃. 那么runtime是怎么将weak变量置为nil呢? 我们通过对象的dealloc函数来看一下具体实现逻辑
- dealloc函数
对象的dealloc会调用_objc_rootDealloc()函数以及rootDealloc()函数
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj) {
ASSERT(obj);
obj->rootDealloc();
}
复制代码
- rootDealloc函数
- 首先判断是否为taggedPointer, 如果是则直接返回;
- 如果不是, 则判断是否是nonpointer, 是否有weak引用表, 是否有关联对象, 是否有c++析构函数等, 如果都没有, 则直接free, 否则会调用object_dispose()
inline void objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
复制代码
- object_dispose函数
object_dispose函数会调用objc_destructInstance()函数进行处理.
- 判断是否有c++的析构函数, 如果有, 则进行成员变量的释放
- 判断是否有关联对象, 如果有, 进行关联对象的移除
- 调用clearDeallocating()函数进行weak指针的操作
id object_dispose(id obj) {
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj) {
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
复制代码
- clearDeallocating()函数
- clearDeallocating()函数中判断是否是优化过的isa指针, 执行clearDeallocating_slow()函数
- clearDeallocating_slow()函数中, 会根据当前地址得到散列表table, 调用weak_clear_no_lock(), 传入参数table.weak_table和this指针
inline void objc_object::clearDeallocating() {
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
NEVER_INLINE void objc_object::clearDeallocating_slow() {
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
复制代码
- weak_clear_no_lock()
- weak_entry_for_referent()函数通过weak_table和当前指针找到对应的entry
- 遍历entry->referrers, 将所有指向当前对象的weak变量的指针置为nil
- 将entry从weak_table中移除
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) {
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
return;
}
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
复制代码
AutoReleasePool
在iOS开发过程中, 如果将对象放到AutoReleasePool中, 那么对象就会在pool结束时自动释放, 那么pool是如何做到自动释放呢? 在pool结束时释放更具体的是什么时机呢?
- 编译后的AutoReleasePool
我们将一个AutoReleasePool使用clang指令编译, 查看编译后的代码
- AutoReleasePool会被编译到一个{}的代码块中, 作为一个局部变量, 声明为__AtAutoreleasePool类型, 在代码块结束时, 会销毁这个局部变量, 执行析构函数
- __AtAutoreleasePool对应一个结构体, 其中在构造函数中, 执行了objc_autoreleasePoolPush(), 析构函数中执行了objc_autoreleasePoolPop()函数
{ __AtAutoreleasePool __autoreleasepool;
int a = 10;
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
复制代码
- Push/Pop的源码
Push/Pop调用的是AutoreleasePoolPage的对应函数
void * objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
复制代码
- AutoreleasePoolPage
- AutoreleasePoolPage继承自AutoreleasePoolPageData
- 其中AutoreleasePoolPageData是通过parent/child组成的双向链表结构
- 每个Page除了保存自己的相关属性以外, 会将加入Pool的对象地址保存到自己的内存中. 当一个page存满时, 会开辟新的page, 通过parent/child关联, 以便后续操作
- 每个page还有一个POOL_BOUNDARY(哨兵)记录不同Pool的边界地址
- next指针指向当前Pool下一个可以存储对象的地址, 这样每次添加时,可以通过next快速找到存储位置
class AutoreleasePoolPage : private AutoreleasePoolPageData {}
struct AutoreleasePoolPageData {
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
};
复制代码
- AutoreleasePoolPage的push函数
- push(): 先判断是否需要新建一个page, 来分别进行处理
- autoreleaseNewPage(): 拿到当前的hotPage, 如果存在, 进入autoreleaseFullPage(), 如果不存在, 进入autoreleaseNoPage()
- autoreleaseFullPage(): 则通过page->child一直找到一个未满的page, 如果全都满了则重新创建一个page, 重新设置hotPage, 并且加入page
- autoreleaseNoPage(): 经过一些列判断前置操作, 重新创建一页page, 将新page设置为hotPage, 并且将POOL_BOUNDARY(哨兵)加入新page
- autoreleaseFast(): 如果当前hotPage没满, 直接加入当前page, 如果满了就进入autoreleaseFullPage(), 如果当前hotPage为nil, 则进入autoreleaseNoPage().
- 经过一系列的处理, 终于将一个对象加入了AutoReleasePool中
static inline void *push() {
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
static __attribute__((noinline)) id *autoreleaseNewPage(id obj) {
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
static __attribute__((noinline)) id *autoreleaseNoPage(id obj) {
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
return setEmptyPoolPlaceholder();
}
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
return page->add(obj);
}
static inline id *autoreleaseFast(id obj) {
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
复制代码
- AutoreleasePoolPage的pop函数
- pop(): 获取当前hotPage, 以及当前stop时的地址, 调用popPage()
- popPage(): 调用releaseUntil() 释放对象直到stop标志, 同时检查当前page的child, 如果需要释放就释放
- releaseUntil(): 获取当前的hotPage, 如果为nil, 则通过parent指针获取父节点; 拿到hotPage以后, 通过next指针拿到最新的对象, 然后将对象释放, 直到当前对象为POOL_BOUNDARY(哨兵)
static inline void pop(void *token) {
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
page = hotPage();
if (!page) {
return setHotPage(nil);
}
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
} else {
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
template<bool allowDebug> static void
popPage(void *token, AutoreleasePoolPage *page, id *stop) {
page->releaseUntil(stop);
if (allowDebug && DebugPoolAllocation && page->empty()) {
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
page->kill();
setHotPage(nil);
} else if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
void releaseUntil(id *stop) {
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
}
复制代码
- AutoReleasePool的真正释放时机
- AutoReleasePool的释放与Runloop是紧密相关的, 当runloop即将进入时, 会进行push()操作; 当runloop即将休眠时, 会先进行一次pop(), 在进行一次push() 保证push/pop成对出现; 在runloop即将退出时, 调用 pop()
- 我们直接打印出主线程的runloop, 核心打印如下:
- 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
- 第2个Observer监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush(), 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
CFRunLoop {
current mode = kCFRunLoopDefaultMode
common modes = {
UITrackingRunLoopMode
kCFRunLoopDefaultMode }
common mode items = {
// Ovserver // Entry 进入的时候
CFRunLoopObserver {order = -2147483647, activities = 0x1,
callout = _wrapRunLoopWithAutoreleasePoolHandler}
// BeforeWaiting | Exit
CFRunLoopObserver {order = 2147483647, activities = 0xa0,
callout = _wrapRunLoopWithAutoreleasePoolHandler}
}
}
复制代码