Timer cyclic reference is faulty

As we all know, when we use an NSTimer, the addtarget causes a circular reference if it is the holder of the timer. Because the timer has a strong reference to the current target. So how do we solve this?

  • Manage the Timer manually and close the timer and set it to nil when it is finished. This method only works for non-circular calls to the timer

  • If we’re after iOS10, we can just use the timer’s block constructor and put the event in a block so that the timer doesn’t have a strong reference to target

let timer = Timer(fire: Date(), interval: 1, repeats: true, block: { timer in
            
})
Copy the code
  • Replace target with another object.
  1. Use the NSObject class as target

When we define an NSObject class as the target of the timer, we can use the iOS message sending/forwarding mechanism to treat the original class as a property of NSObject and process the event through message forwarding

@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;
}

// If the current class has no corresponding method implementation, it will return an instance to respond to the event through the second step of the message forwarding process
// We can resolve the timer's circular reference by returning the original instance
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.target;
}

Copy the code
  1. In addition to NSObject, iOS has a base class, NSProxy, that can be used specifically as a proxy to handle situations like this
@interface MyProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak.nonatomic) id target;
@end

@implementation MyProxy

+ (instancetype)proxyWithTarget:(id)target
{
    // The NSProxy object does not need to call init because it does not have init method
    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
Copy the code

NSProxy is a better target for a timer than NSObject. Because for no implementation method, NSObject will pass the message forwarding process forward three times, and eventually reach forwardingTargetForSelector:, while NSProxy will direct call forwardInvocation:, It doesn’t do the message forwarding process, and it performs a little better than NSObject

More accurate timer than NSTimer/CADisplayLink

  • NSTimer/CADisplayLink

As we know, the Timer/CADisplayLink Timer adds the Timer to the current runloop and executes the Timer. When the Runloop executes the Timer in each loop, if the current Runloop lags, the Timer will inevitably be inaccurate.

  • GCD-Timer

We can create a timer with GCD. The timer created by GCD is independent of runloop, so it is more accurate

// Create a timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0, queue);

// Set the time
dispatch_source_set_timer(timer,
                            dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
                            interval * NSEC_PER_SEC.0);

// Set the timer task
dispatch_source_set_event_handler(timer, ^{
    
});

// Start timer
dispatch_resume(timer);

// Turn off the timer
dispatch_source_cancel(timer);
Copy the code

Weak in iOS is if implemented

In iOS, when a weak-decorated object is released, the weak variable is set to nil, and sending a message to the Weak variable does not crash. So how does Runtime set weak to nil? Let’s look at the implementation logic through the object’s dealloc function

  • Dealloc function

Object dealloc calls the _objc_rootDealloc() and rootDealloc() functions

- (void)dealloc {
    _objc_rootDealloc(self);
}

void _objc_rootDealloc(id obj) {
    ASSERT(obj);
    obj->rootDealloc(a); }Copy the code
  • RootDealloc function
  1. If taggedPointer is a taggedPointer, return it.
  2. If not, it will determine whether nonpointer, weak reference table, associated object, c++ destructor, etc. If not, it will directly free, otherwise object_dispose() will be called.
inline void objc_object::rootDealloc(a)
{
    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); }}Copy the code
  • Object_dispose function

The object_Dispose function calls the objc_destructInstance() function.

  1. Determine if there is a c++ destructor, and if so, release the member variable
  2. Check whether there are associated objects. If yes, remove the associated objects
  3. Call the clearDeallocating() function to operate on the weak pointer
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(a);bool assoc = obj->hasAssociatedObjects(a);// This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
        obj->clearDeallocating(a); }return obj;
}
Copy the code
  • ClearDeallocating () function
  1. The clearDeallocating_slow() function determines whether it is an optimized ISA pointer
  2. In the clearDeallocating_slow() function, the hash table table will be obtained according to the current address, and weak_clear_NO_lock () is called, passing in the table. Weak_table and this pointer
inline void objc_object::clearDeallocating(a) {
    if (slowpath(! isa.nonpointer)) {// Slow path for raw pointer isa.
        sidetable_clearDeallocating(a); }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(a); }assert(!sidetable_present());
}

NEVER_INLINE void objc_object::clearDeallocating_slow(a) {
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables(to)this];
    table.lock(a);if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock(a); }Copy the code
  • weak_clear_no_lock()
  1. The weak_entry_for_referent() function finds the corresponding entry through the weak_table and the current pointer
  2. Iterating through entry->referrers sets all Pointers to the current object’s weak variable to nil
  3. Remove entry from 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(a); }}}weak_entry_remove(weak_table, entry);
}
Copy the code

AutoReleasePool

In iOS development, if you place an object in the AutoReleasePool, the object will be automatically released when the pool ends. How can a pool be automatically released? What is the specific time to release at the end of the pool?

  • The compiled AutoReleasePool

Let’s compile an AutoReleasePool using the clang directive to see the compiled code

  1. AutoReleasePool is compiled into a {} block of code as a local variable of type __AtAutoreleasePool. At the end of the block, the local variable is destroyed, executing the destructor
  2. __AtAutoreleasePool corresponds to a structure where objc_autoreleasePoolPush() is executed in the constructor and objc_autoreleasePoolPop() is executed in the destructor

{ __AtAutoreleasePool __autoreleasepool; 

        int a = 10;
}

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(a); } ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); }void * atautoreleasepoolobj;
};
Copy the code
  • The source code of Push/Pop

Push/Pop calls the function corresponding to AutoreleasePoolPage

void * objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push(a); }void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}
Copy the code
  • AutoreleasePoolPage
  1. AutoreleasePoolPage inherits from AutoreleasePoolPageData
  2. AutoreleasePoolPageData is a two-way linked list structure composed of parent/child
  3. In addition to saving its own attributes, each Page saves the addresses of objects added to the Pool into its own memory. When a page is full, a new page is created, through the parent/ Child association, for subsequent operations
  4. Each page also has a POOL_BOUNDARY(sentinel) that records the boundary address of the different pools
  5. The next pointer points to the next location where objects can be stored in the current Pool, so that each time you add them, you can quickly find the storage location through 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;
};
Copy the code
  • AutoreleasePoolPage push function
  1. Push (): Determine whether to create a new page to process separately
  2. AutoreleaseNewPage (): Get the current hotPage. If it exists, go to autoreleaseFullPage(). If not, go to autoreleaseNoPage().
  3. AutoreleaseFullPage (): Finds an unfilled page through page->child, creates a new page, resets hotPage, and joins the page
  4. AutoreleaseNoPage (): Re-creates a page, sets the new page to hotPage, and adds POOL_BOUNDARY(sentinel) to the new page after some pre-judgment operation
  5. AutoreleaseFast (): If the current hotPage is not full, join the current page directly, if it is full, go to autoreleaseFullPage(), if the current hotPage is nil, go to autoreleaseNoPage().
  6. After a series of processes, an object is finally added to the AutoReleasePool
static inline void *push(a) {
    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(a);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(a); } 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(a);if(page && ! page->full()) {
        return page->add(obj);
    } else if (page) {
        return autoreleaseFullPage(obj, page);
    } else {
        return autoreleaseNoPage(obj); }}Copy the code
  • AutoreleasePoolPage’s pop function
  1. Pop (): get the current hotPage and the address of the current stop, call popPage()
  2. PopPage (): Call releaseUntil() to release the object until the stop flag, checking the current page’s child and releasing it if necessary
  3. ReleaseUntil (): Gets the current hotPage or, if nil, the parent node through the parent pointer; Once you have the hotPage, use the next pointer to get the latest object and release it until the current object is POOL_BOUNDARY(sentinel)
static inline void pop(void *token) {
    AutoreleasePoolPage *page;
    id *stop;
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        page = hotPage(a);if(! page) {return setHotPage(nil);
        }
        page = coldPage(a); token = page->begin(a); }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(a);setHotPage(parent);
    } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
        page->kill(a);setHotPage(nil);
    } else if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill(a); }else if (page->child->child) {
            page->child->child->kill(a); }}}void releaseUntil(id *stop)  {
    while (this->next ! = stop) { AutoreleasePoolPage *page =hotPage(a);while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }
        page->unprotect(a); id obj = *--page->next;memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect(a);if(obj ! = POOL_BOUNDARY) {objc_release(obj); }}setHotPage(this);

}
Copy the code
  • AutoReleasePool actual release time
  1. The AutoReleasePool release is closely related to the Runloop. Push () is performed when the Runloop is about to enter. When the runloop is about to sleep, it performs a pop() first and a push() to ensure that the push/pop pairs appear. Call pop() when runloop is about to exit
  2. We print the runloop of the main thread directly, and the core print looks like this:
    1. The first Observer listens for the kCFRunLoopEntry event and calls objc_autoreleasePoolPush().
    2. The second Observer listens for the kCFRunLoopBeforeWaiting event, Objc_autoreleasePoolPop () and objc_autoreleasePoolPush() are called, and kCFRunLoopBeforeExit is monitored. Objc_autoreleasePoolPop () is called.
CFRunLoop {

    current mode = kCFRunLoopDefaultMode
    common modes = {
        UITrackingRunLoopMode
        kCFRunLoopDefaultMode }

    common mode items = {

        // Ovserver // Entry Entry
        CFRunLoopObserver {order = - 2147483647., activities = 0x1, 
            callout = _wrapRunLoopWithAutoreleasePoolHandler}

       // BeforeWaiting | Exit 
        CFRunLoopObserver {order = 2147483647, activities = 0xa0, 
            callout = _wrapRunLoopWithAutoreleasePoolHandler}
    }
}
Copy the code