In the last article, we looked at several ways to manage memory. SideTables mainly introduced reference counting tables. Let’s continue with SideTables

1.SideTables

SideTables can be understood as a global hash array that stores data of type SideTable with a length of 64 bytes. SideTablse is not a defined datatype. It is a global static function that returns a value of StripedMap.

static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;

static StripedMap<SideTable>& SideTables() {

    return SideTablesMap.get();

}

Copy the code

Continue to look at

It’s a paradigm, and the number of hash tables in the real case is zero8, the simulator is64SideTable = sideTable; sideTable = sideTable;

  1. If the hash table only has one table, meaning that all global objects are stored in a table and unlocked (lock is to lock the read and write of the entire table). When unlocking, since all data is in a table, it meansData insecurity
  2. ifEach object opens a tableWill,Cost performanceSo you can’t have an infinite number of tables.
  3. There is a requirement for type T (the ability to lock). And in theSideTablesWhere T isSideTableType.
  • SideTable

SideTableThe definition of has three members:

  • spinlock_t slock: spin lock, used for locking/unlockingSideTable.
  • RefcountMap refcntsTo:DisguisedPtr<objc_object>Isa hash table for key, used to store reference counts for OC objects (only used when isa optimization is not enabled or when isa_t reference counts overflow under isa optimization).
  • weak_table_t weak_table: a hash table that stores weak reference Pointers to objects. isOC weakThe core data structure for functionality implementation.

2. retainCount

Our last article looked at the retain, release operations of extra_rc and reference counts in hashtables in ISA. So let’s look at its retainCount flow.

NSObject *objc = [NSObject alloc];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
Copy the code

We know that the print is 1, so let’s look at the implementation

  • Enter theretainCount -> _objc_rootRetainCount -> rootRetainCountSource code, the front is jump we lookrootRetainCountThe implementation is as follows
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;// Get the number of extra_rc in ISA

        if (bits.has_sidetable_rc) {

            rc += sidetable_getExtraRC_nolock();

        }

        sidetable_unlock();

        return rc;

    }

    sidetable_unlock();

    return sidetable_retainCount();

}

Copy the code

We break the point here to indicate that it’s already one when we initialize it

We in the previous version of the source is inTo obtainWhen the reference count +1

The latest version is the objectInitialize thewhenBinding the isaWhen definingextra_rcfor1.

We decorate the __weak object and print its reference count, which prints 1, 2.

NSObject *objc = [NSObject alloc];

        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));

        __weak typeof(NSObject) *weakObjc = objc;

        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));

        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)weakObjc));
Copy the code

  • Indicates that the reference count itself does not change when the __weak modifier is used, so objc’s reference count remains 1. The __weak modified object has a pointer copy.

  • When we use an object such as NSlog, the ISA of a weak reference object is retained, Objc_loadWeak ->objc_loadWeakRetained->rootTryRetain->rootRetain->newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry) so we print weakObjc reference count of 2.

  • When we use it, we make the returned retainCount + 1.

  • Holding or not holding an object depends on whether it causes the object’s retainCount + 1; Not to see if he points to the address.

  • We use this to cause the reference count to +1, but inobjc_autoreleaseWhen we get outscopeAfter, the reference count is the same as before.

  • We know that the hash table contains oneA weak reference tableFor storageWeakly referenced object.

3. weak_table_t

  • weak_tableIs the structure of a hash table according toweakThe hash value is calculated for the address of the object to which the pointer points. Objects with the same hash value are searched for the available location in the form of subscript +1, which is a typical closed hash algorithm
struct weak_table_t { weak_entry_t *weak_entries; // Header pointer to contiguous address Spaces, array size_t num_entries; // Uintptr_t mask; // Uintptr_t max_hash_displacement; // Maximum hash offset};Copy the code

weak_entry_t

  • Let’s look at the flow of modifying an object by weak reference

1. If the. M file uses the weak keyword in the main file, Cannot create _ weak reference because the current deployment target does not support weak references. We are so decorated that MAC OSX-11.2.3 is your native SDK version. Clang-rewrite-objc-fobjc-arc-fobjc-runtime =macosx-11.2.3 main.m

We searchobjc_ownership, not found in the source code. So just like we started with alloc, LLVM has converted the method toobjc_initWeak 2. storeWeak

  • For the first time,__weakweakProperty to modify the object, nonehaveOld.haveNewforTrueFetch hash table about thisObject hash table
  • Object is not implemented,Initialize theoperation
  • If there is a new value, the new value is assigned.weak_register_no_lock
  • Not small or not nilsetWeaklyReferenced_nolockSet the reference count.
  • Callback after unlockingcallSetWeaklyReferenced
  1. weak_register_no_lock

  • First determine whethertaggedPointerIf yes, it will not be processed
  • Make sure the referenced object is viable and determine if the current object is inThe destruction, the tagstate.
  • Class by objectweak_entry_tThrough theweak_entry_for_referentmethods
  • To the weak reference tableweak_entry_tThe type of.
  1. weak_entry_for_referent

Weak_entry_t in Weak_entries in weak reference table is found by hashing algorithm. 5.append_referrer

  • If more thanInline boundary, if not, try to insert each table at most4Find a,Empty insert
    • If not, create a new line of sizeFour weak_referrer_t, to insert
  • Over the inline boundary 2 words to occupy size overThree-quarters of the table.Expansion into
  • Otherwise, it goes through the hash algorithmNormal insert.

  • 1: First of all, we know that there is a really cool guy named sideTable
  • 2: Obtain the weakTable weak reference table of sideTable
  • 3: Create a Weak_entry_t
  • 4: Add referent to weak_entry_t array inline_Referrers
  • 5: Expand the weak_table
  • 6: Add new_entry to weak_table

Weak modificationThe whole process of the object, we do not involve in the developmentA circular referenceIf you reduce the use of weak references, the process is time consuming and performance consuming.

4. Strong reference

4.1 Strong references

We use Runloop to add timers in our daily development

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code

We know that the timer is still running when we leave the page because it can’t be released due to strong holding, and we usually have to manually destroy the timer. So why is it impossible to release? The official documentation says that the timer makes a strong reference to the holder until it expires.

Their holding relationship is as follows:

[NSRunLoop currentRunLoop]/self->timer->selfThe circular reference cannot be released. What if we use the weak modifier self?

 __weak typeof(self) weakSelf = self;

self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:weakSelf.timer forMode:NSRunLoopCommonModes];
Copy the code

[NSRunLoop currentRunLoop]/self-> Timer ->weakSelf->self. The Runloop has a longer lifetime than the current page ViewController, so it holds the timer, and the timer holds self, so self cannot be freed.

4.2 Solution

4.2.1 Manual Destruction

In our development, we manage the timer manually for example, we call timer destruction manually when we leave the page

- (void)viewWillDisappear:(BOOL)animated{

    [super viewWillDisappear:animated];

    // push to the next layer and return it!!

    [self.timer invalidate];

    self.timer = nil;

    NSLog(@"The timer go");

}
Copy the code

Destroy the core timer so strong hold-loop references don’t exist

4.2.2 Block Timer

__weak typeof(self) weakSelf = self;

    self.timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {

        [weakSelf fireHome];

    }];

    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code

So this is initialized by a block, and we know blocks from the block sectionInternal parametersWill not result in aStrong reference, soTimer is not going to hold self strongly, we just need to avoid blocksA circular referenceCan,__weakModify self, just like the usual block notice.

4.2.3 Intermediary model

We can change the holder to break the loop by telling the timer to execute the method we want.

    self.target = [[NSObject alloc] init];

    class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc,"v@ : ");

    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];

    void fireHomeObjc(id obj){

    NSLog(@"%s -- %@",__func__,obj);

}
Copy the code

Create a timer and use theThe default mode schedules runloop when it is currently running. run

The current page is not strongly referenced and can be destroyed, buttargetbeTimerStrong reference, continue to execute the timer. We are indeallocDestroy the timer.

4.2.4 Customizing the Timer

In the last method we proposed the middle layer, so we define a timer based on the middle layer

- (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; // vc

        self.aSelector  = aSelector; // method -- vc release

        

        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);// Convert the method to targetself.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo]; }}return self;

}


// Keep running runloop

void fireHomeWapper(LGTimerWapper *warpper){

    

    if (warpper.target) { // vc - dealloc

        void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;

         lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer);

    }else{ // warpper.target[warpper.timer invalidate]; warpper.timer = nil; }} - (void)lg_invalidate{

    [self.timer invalidate];

    self.timer = nil;

}

Copy the code
  • We give the current classAdd methods, equivalent to converting our tagert Sel to the method of the current class
  • The method implementations of the current class areTarget sends a message to Target“Without explanationThe target is destroyedAnd weShut downThe timer.

Summary: We convert target’s SEL to the sel of the current class, where the sel sends a message to Target

4.2.5 Customizing NSProxy

  • OC is a single-inheritance language, but it is based on the run-time mechanism, so NSProxy can be used to implement pseudo-multiple inheritance, filling the gap of multiple inheritance

  • NSProxy and NSObject are sibling classes, or virtual classes, that implement the protocol of NSObject

  • NSProxy is an abstract class that encapsulates message redirection. It is like a proxy, middleware, that can inherit it and rewrite the following two methods to implement message forwarding to another instance

@interface LGProxy()

@property (nonatomic, weak) id object;

@end


@implementation LGProxy

+ (instancetype)proxyWithTransformObject:(id)object{

    LGProxy *proxy = [LGProxy alloc];

    proxy.object = object;

    return proxy;

}


In order to ensure that the middleware can respond to events from the external self, the message forwarding mechanism needs to make the actual response target to the external self. This step is crucial, mainly involving the Message mechanism of the Runtime.

/ / transfer

// Strong reference -> message forwarding


//-(id)forwardingTargetForSelector:(SEL)aSelector {

// return self.object;

/ /}


//// sel - imp -

//// Message forwarding self.object

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{

    if (self.object) {

    }else{

        NSLog(@"Please collect stack111");

    }

    return [self.object methodSignatureForSelector:sel];

}


- (void)forwardInvocation:(NSInvocation *)invocation{


    if (self.object) {

        [invocation invokeWithTarget:self.object];

    }else{

        NSLog(@"Please collect stack");

    }

}
*********vc*******


- (void)dealloc{

    [self.timer invalidate];

    self.timer = nil;

    NSLog(@"%s",__func__);

}
Copy the code

We destroy the timer in the current controller dealloc. The timer holds the custom proxy, while the proxy holds self as weak and does not increase the reference count. Therefore, VC can be released, and the timer and proxy held by VC can also be released. Self ->timer->proxy-> WeakSelf

5. To summarize

I gained a deeper understanding of weak attributes and __weak modified objects, and learned about retainCount changes and the role of scopes by exploring the weak reference table and adding weak reference objects. By solving the problem of timer’s strong reference, combined with the previous problem of block circular reference, we feel the release and management of object memory.