iOS底层原理11: 定时器, Weak, Autoreleasepool

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.
  1. 使用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;
}

复制代码
  1. 除了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函数
  1. 首先判断是否为taggedPointer, 如果是则直接返回;
  2. 如果不是, 则判断是否是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()函数进行处理.

  1. 判断是否有c++的析构函数, 如果有, 则进行成员变量的释放
  2. 判断是否有关联对象, 如果有, 进行关联对象的移除
  3. 调用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()函数
  1. clearDeallocating()函数中判断是否是优化过的isa指针, 执行clearDeallocating_slow()函数
  2. 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()
  1. weak_entry_for_referent()函数通过weak_table和当前指针找到对应的entry
  2. 遍历entry->referrers, 将所有指向当前对象的weak变量的指针置为nil
  3. 将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指令编译, 查看编译后的代码

  1. AutoReleasePool会被编译到一个{}的代码块中, 作为一个局部变量, 声明为__AtAutoreleasePool类型, 在代码块结束时, 会销毁这个局部变量, 执行析构函数
  2. __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
  1. AutoreleasePoolPage继承自AutoreleasePoolPageData
  2. 其中AutoreleasePoolPageData是通过parent/child组成的双向链表结构
  3. 每个Page除了保存自己的相关属性以外, 会将加入Pool的对象地址保存到自己的内存中. 当一个page存满时, 会开辟新的page, 通过parent/child关联, 以便后续操作
  4. 每个page还有一个POOL_BOUNDARY(哨兵)记录不同Pool的边界地址
  5. 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函数
  1. push(): 先判断是否需要新建一个page, 来分别进行处理
  2. autoreleaseNewPage(): 拿到当前的hotPage, 如果存在, 进入autoreleaseFullPage(), 如果不存在, 进入autoreleaseNoPage()
  3. autoreleaseFullPage(): 则通过page->child一直找到一个未满的page, 如果全都满了则重新创建一个page, 重新设置hotPage, 并且加入page
  4. autoreleaseNoPage(): 经过一些列判断前置操作, 重新创建一页page, 将新page设置为hotPage, 并且将POOL_BOUNDARY(哨兵)加入新page
  5. autoreleaseFast(): 如果当前hotPage没满, 直接加入当前page, 如果满了就进入autoreleaseFullPage(), 如果当前hotPage为nil, 则进入autoreleaseNoPage().
  6. 经过一系列的处理, 终于将一个对象加入了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函数
  1. pop(): 获取当前hotPage, 以及当前stop时的地址, 调用popPage()
  2. popPage(): 调用releaseUntil() 释放对象直到stop标志, 同时检查当前page的child, 如果需要释放就释放
  3. 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的真正释放时机
  1. AutoReleasePool的释放与Runloop是紧密相关的, 当runloop即将进入时, 会进行push()操作; 当runloop即将休眠时, 会先进行一次pop(), 在进行一次push() 保证push/pop成对出现; 在runloop即将退出时, 调用 pop()
  2. 我们直接打印出主线程的runloop, 核心打印如下:
    1. 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
    2. 第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}
    }
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享