The last article covered the structural modeling part of runtime issues; In this article, we will cover memory management in runtime.

Memory management for Runtime related issues

The basic contents include:

  • How does weak work? What is the structure of SideTable
  • Application of associated objects? How does the system implement the associative object
  • How are associated objects managed in memory? How does an associated object implement the weak property
  • How does Autoreleasepool work? What data structures are used
  • How does ARC work? What optimizations have been made for retain and release under ARC
  • What can cause a memory leak in ARC

How does weak work? What is the structure of SideTable

First the conclusion:

  • Weak tableIt’s actually a hash table.KeyIs the address of the object to which it refers,ValueisweakAn array of addresses for Pointers. The principle is to store weak objects separately in the old and new tables by updating PointersSideTableIn theweak_table_t(type)weak_tableTable, by functionobjc_initWeak()->storeWeak()Old and new in functionsSideTable(structure) table to implement
  • SideTableIs a structure with two main members of the reference count table and weak reference table. In fact, the memory stores the address of the object and the address of the reference count and weak variable, rather than the data of the object itself. Its structure is as follows
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);
};
Copy the code

Weak implementation principle

The realization principle can be summarized into three opportunities

  • 1. Initialization
  • 2. Add a reference
  • Release 3.

1. During initialization

The Runtime calls the objc_initWeak function and initializes a new weak pointer to the address of the object.

Let’s introduce a test code

NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
Copy the code

When we initialize a weak variable, the Runtime calls the objc_initWeak() function in nsobject.mm. This function is declared in Clang as follows:

id objc_initWeak(id *location, id newObj) { if (! NewObj) {// See if the object instance is valid. return nil; } // We pass three bool values old, new, crash. Return storeWeak<DontHaveOld, DoHaveNew, DontCrashIfDeallocating> (location, (objc_object*)newObj); }Copy the code

As you can see, this function is just a call entry to a deeper function. In general, entry functions make some simple judgments (such as the cache judgment in objc_msgSend), which determines whether the class object to which the pointer points is valid. Otherwise, object is registered as an __weak object pointing to value. The objc_storeWeak function should do this.

Note: The objc_initWeak function has one prerequisite: object must be a valid pointer that is not registered as an __weak object. Value can either be null or point to a valid object.

2. Add a reference

The objc_initWeak function calls the objc_storeWeak() function, and the objc_storeWeak() function calls storeWeak(). StoreWeak () updates the pointer pointer and creates the corresponding weak reference table

The template

// HaveOld: true - variable has value,false - needs to be cleaned up in time, current value may be nil // HaveNew: // CrashIfDeallocating: true - A new value to be assigned, the current value might be nil, false - no new value to be assigned True - newObj has been freed or newObj does not support weak references. This process needs to pause, false - use nil alternative storage template < HaveOld HaveOld, HaveNew HaveNew, CrashIfDeallocating CrashIfDeallocating >Copy the code

Weak This procedure is used to update the pointing of a weak reference pointer.

static id storeWeak(id *location, objc_object *newObj) { ASSERT(haveOld || haveNew); if (! haveNew) ASSERT(newObj == nil); / / initialize previouslyInitializedClass pointer. Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *oldTable; SideTable *newTable; If (haveOld) {oldObj = *location; if (haveOld) {oldObj = *location; OldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (haveNew) { newTable = &SideTables()[newObj]; } else {newTable = nil; SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); If (haveOld && *location!) {// If (haveOld && *location! = oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); goto retry; Class CLS = newObj->getIsa(); if (haveNew && newObj) {Class CLS = newObj->getIsa(); If (CLS! = previouslyInitializedClass && ! ((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); / unlock class_initialize(CLS, (id)newObj); If +initialize is in a thread, for example, +initialize is calling storeWeak and needs to manually add a protection policy to it. Mark previouslyInitializedClass = CLS and set previouslyInitializedClass pointer; goto retry; Weak_unregister_no_lock (&oldTable->weak_table, oldObj, location); if (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); If (haveNew) {newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating); // The weak_register_no_lock method returns nil if the weak reference is released, setting the if reference flag bit if (newObj &&! NewObj ->setWeaklyReferenced_nolock(); weakLyReferenced_nolock (); weakLyReferenced_nolock (); *location = (id)newObj; *location = (id)newObj; SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); return (id)newObj; }Copy the code
SideTable

SideTable is a structure that contains the weak reference count table and the weak reference count table. It stores the address of the object and the address of the reference count and weak variable.

Mainly used to manage the reference count and weak table of the object.

Let’s look at the graph

Each SideTable maintains a RefcountMap reference count table, where the key is the address of the object. Value is the reference count for this object

struct SideTable { spinlock_t slock; RefcountMap refcnts; // Reference count hash table weak_table_t weak_table; //weak references the global hash table... };Copy the code
  • Slock prevents contending spinlocks
  • Refcnts assisted the ISA pointer of the objectextra_rcCommon reference count variables
Weak table

Weak-reference hash table, a structure of type Weak_table_t that stores all weak-reference information related to an instance object. The definition is as follows:

struct weak_table_t { weak_entry_t *weak_entries; // Save all weak Pointers to the specified object size_t num_entries; // Uintptr_t mask; // Uintptr_t max_hash_displacement; // Maximum offset of hash key};Copy the code

This is a globally weak reference hash table. Weak_entry_t type structure object is used as value, where weak_entries member is weak reference table entry.

Weak_entry_t is an internal structure stored in the weak-reference table, which is responsible for maintaining and storing all weak-reference hash tables pointing to an object. Its definition is as follows:

typedef DisguisedPtr<objc_object *> weak_referrer_t; struct weak_entry_t { DisguisedPtr<objc_object> referent; union { struct { weak_referrer_t *referrers; uintptr_t out_of_line_ness : 2; uintptr_t num_refs : PTR_MINUS_2; uintptr_t mask; uintptr_t max_hash_displacement; }; struct { // out_of_line_ness field is low bits of inline_referrers[1] weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }; . };Copy the code

The REFERent variable of the DisguisedPtr type is the encapsulation of the pointer of the generic object, through which the memory leak problem is solved.

There is an important out_OF_line member in the comment, which represents the lowest significant bit. When it is 0, the Weak_REFERrer_t member will expand to a multi-line static Hask table.

Where weak_referrer_t is a two-dimensional objc_object alias (typedef), through a two-dimensional pointer address offset, using subscript as hash key, made a weak reference hash.

So what is the role of out_OF_line, num_refs, mask and max_hash_displacement in weak_entry_T when the effective bit is not in effect?

  • out_of_line: indicates the least significant bit and the flag bit. When flag bit 0, increase the latitude of the reference table pointer.
  • num_refs: Quotes a value. This is where the valid numbers referenced in the weak-reference table are recorded, because weak-reference tables use static hash structures, so you need to use variables to record the number.
  • mask: Count auxiliary quantity.
  • max_hash_displacement:hashElement upper threshold.

In fact, the out_of_line value is usually equal to zero, so the weak reference table is always a two-dimensional array of objC_objective Pointers. A one-dimensional objc_objective pointer can form a weak-reference hash table. Multiple hash tables are realized through the third latitude, and the number of tables is WEAK_INLINE_COUNT.

The above is the implementation principle of the weak table.

Release 3.

When you release, call the clearDeallocating function. The clearDeallocating function first fetches an array of weak pointer addresses based on the object’s address, then iterates through the array to set it to nil, deletes the entry from the Weak table, and clears the object’s record.

What happens to the weak pointer when the object to which the weak reference points is released? When an object is released, the basic flow is as follows:
  • 1. Callobjc_release
  • 2. Execute because the reference count of the object is 0dealloc
  • 3. In dealloc, call_objc_rootDeallocfunction
  • In 4._objc_rootDeallocIs calledobject_disposefunction
  • 5. Callobjc_destructInstance
  • 6. Last callobjc_clear_deallocating

Focus on the objc_clear_deallocating function that is called when the object is released. This function is implemented as follows:

void objc_clear_deallocating(id obj)  
{
    ASSERT(obj);
    if (obj->isTaggedPointer()) return;
    obj->clearDeallocating();
}
Copy the code

Locating () was called, locating clearDeallocating(), and clicking the source to track it down, it ended up using an iterator to get the weak table value, then calling Weak_clear_no_lock () to find the corresponding value and empty the weak pointer.

Weak_clear_no_lock () function is implemented as follows:

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) { /// XXX shouldn't happen, but does with mismatched CF/objc //printf("XXX no entry for clear deallocating %p\n", referent); return; } // zero out references 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); }Copy the code

Objc_clear_deallocating () This function does the following:

  • 1. Obtain the address of the abandoned object as the key value from the weak table
  • 2. Assign nil to all addresses contained in the record with the weak modifier variable
  • 3. Delete the record from the weak table
  • 4. Delete the records of discarded objects whose addresses are keys from the reference count table

You can refer to the implementation principle of weak parsing in iOS

Application of associated objects? How does the system implement the associative object

Application of associated objects?

It is commonly used to add attribute associations to the current class in a category, because you cannot add member variables directly, but you can add member variables indirectly through the Runtime.

When we declare the following code in the category:

@interface ClassA : NSObject (Category)
@property (nonatomic, strong) NSString *property;
@end
Copy the code

The @property keyword that’s built into the objC library actually helps us implement setters and getters, but it doesn’t help us declare the member variable property in a category and we need to add it dynamically indirectly through the TWO C function apis provided by the Runtime Member variable property.

  • objc_setAssociatedObject()
  • objc_getAssociatedObject()
#import "ClassA+Category.h"
#import <objc/runtime.h>

@implementation ClassA (Category)

- (NSString *) property {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setProperty:(NSString *)categoryProperty {
    objc_setAssociatedObject(self, @selector(property), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
Copy the code

Looking at the correlation methods above, let’s take a closer look at the following frequently used correlation property-related apis

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);
Copy the code
  1. objc_setAssociatedObject()Add an associated object as a key-value pair
  2. objc_getAssociatedObject()Gets the associated object based on the key
  3. objc_removeAssociatedObjects()Remove all associated objects

Objc_setAssociatedObject () call stack

void objc_setAssociatedObject(id object, const void *key, id value, ├ ─ imp (), └─ imp (), ├ ─ imp () The policy) └ ─ ─ void _object_set_associative_reference (id object, void * key, id value, uintptr_t policy)Copy the code

The _object_set_associative_reference() function in the call stack above actually does the job of setting the associated object:

void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) { if (! object && ! value) return; if (object->getIsa()->forbidsAssociatedObjects()) _objc_fatal("objc_setAssociatedObject called on instance (%p) of class  %s which does not allow associated objects", object, object_getClassName(object)); DisguisedPtr<objc_object> disguised{(objc_object *)object}; ObjcAssociation association{policy, value}; association.acquireValue(); { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); if (value) { auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); if (refs_result.second) { object->setHasAssociatedObjects(); } auto &refs = refs_result.first->second; auto result = refs.try_emplace(key, std::move(association)); if (! result.second) { association.swap(result.first->second); } } else { ... } } association.releaseHeldValue(); }Copy the code

Omitted a lot of code, the above code is the application scenario, the above call class AssociationsManager is how we want to talk about the system below how to achieve the principle of associated objects.

How the system realizes the associated object (the associated object realization principle)

The core objects that implement the associative object technique are as follows:

  1. AssociationsManager
  2. AssociationsHashMap
  3. ObjectAssociationMap
  4. ObjcAssociation

Map is similar to the dictionary we use. The value is stored in the key-value format.

Here we go through the source code to find out

objc_setAssociatedObject()function

The runtime source

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}
Copy the code

Source code call process hook function, a little long, here I simplify, directly call the core function

Look at the code implementation of the _object_set_associative_reference() function

void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) { if (object->getIsa()->forbidsAssociatedObjects()) _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object)); DisguisedPtr<objc_object> disguised{(objc_object *)object}; ObjcAssociation association{policy, value}; ObjcAssociation association.acquireValue(); { AssociationsManager manager; AssociationsManager AssociationsHashMap & Associations (manager.get()); //2. We list AssociationsHashMap if (value) {auto refs_result = associations. Try_emplace (backup, ObjectAssociationMap{}); ObjectAssociationMap if (refs_result.second) {object->setHasAssociatedObjects(); } auto &refs = refs_result.first->second; auto result = refs.try_emplace(key, std::move(association)); if (! result.second) { association.swap(result.first->second); } } else { auto refs_it = associations.find(disguised); if (refs_it ! = associations.end()) { auto &refs = refs_it->second; auto it = refs.find(key); if (it ! = refs.end()) { association.swap(it->second); refs.erase(it); if (refs.size() == 0) { associations.erase(refs_it); } } } } } association.releaseHeldValue(); }Copy the code

The code above finds the core object that implements our associative object technique. Let’s take a look at the internal implementations of several core objects.

AssociationsManager
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap; typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap; class AssociationsManager { using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>; static Storage _mapStorage; public: AssociationsManager() { AssociationsManagerLock.lock(); } ~AssociationsManager() { AssociationsManagerLock.unlock(); } AssociationsHashMap &get() { return _mapStorage.get(); } static void init() { _mapStorage.init(); }};Copy the code

There is a get() function inside the AssociationsManager that returns an AssociationsHashMap object

AssociationsHashMap

AssociationsHashMap is a typedef of DenseMap except that it is defined as a DenseMap type that meets the conditions of some tuple

Actually AssociationsHashMap uses and to store the mapping from the object’s DISGUised_ptr_T to ObjectAssociationMap. This data structure holds all the associated objects corresponding to the current object

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
Copy the code

Here the ObjectAssociationMap is a typedef of another type, which holds the key and value forms of Pointers to objects of type Objcassociety.

ObjcAssociation is a C++ class object. The key ObjcAssociation contains policy and value.

class ObjcAssociation { uintptr_t _policy; id _value; public: ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {} ObjcAssociation() : _policy(0), _value(nil) {} ObjcAssociation(const ObjcAssociation &other) = default; ObjcAssociation &operator=(const ObjcAssociation &other) = default; ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() { swap(other); } inline void swap(ObjcAssociation &other) { std::swap(_policy, other._policy); std::swap(_value, other._value); } inline uintptr_t policy() const { return _policy; } inline id value() const { return _value; }... };Copy the code
In what form are associated objects stored in memory?

The sample code

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [NSObject new];
        objc_setAssociatedObject(obj, @selector(hello), @"Hello", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return 0;
}
Copy the code

The call function objc_setAssociatedObject(OBJC_ASSOCIATION_RETAIN_NONATOMIC, @”Hello”) has this storage structure in memory

objc_setAssociatedObject()

Let’s go back and break down the actual implementation of the objc_setAssociatedObject() function,_object_set_associative_reference()

Uintptr_t policy (uintptr_t policy, uintptr_t policy)

Let’s break it down into 2 steps

  1. value ! = nilSets or updates the value of the associated object
  2. value == nilDeletes an associated object.

The following is the specific code explanation notice to see the code comments!!

Void _object_set_associative_reference(id object, const void *key, ID value, uintptr_t policy) object && ! value) return; // Determine whether objects of this class can be associated with other objects. If (object->getIsa()->forbidsAssociatedObjects()) _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object)); DisguisedPtr encapsulates the associated object into DisguisedPtr for easier management in the later hash table. Its function is just like a pointer DisguisedPtr<objc_object> backup {(objc_object *)object}; ObjcAssociation {policy, value}; Association.acquirevalue (); // Handle the retain and copy modifier with policy, association.acquireValue(); {// Get the AssociationsManager object; AssociationsHashMap & Associations (manager.get()); // Obtain HashMap AssociationsHashMap & Associations (manager.get()); // If (value) {// Replace the backup if it exists in the ObjectAssociationMap(), or initialize it if it doesn't. // The associated object relationships exist in the ObjectAssociationMap, and // There are multiple objectAssociationMaps, so this step is the management of ObjectAssociationMap, and the following step is the operation of the object we want to associate auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); If (refs_result.second) {// Change the has_assoc field in isa_t to mark its associated state object->setHasAssociatedObjects(); } auto &refs = refs_result.first->second; Auto result = refs.try_emplace(key, STD ::move(association)); // If there is no second one, we should exchange it. if (! result.second) { association.swap(result.first->second); Auto refs_it = associations. Find (backup); // backup (backup); auto refs_it = associations. if (refs_it ! = associations.end()) { auto &refs = refs_it->second; auto it = refs.find(key); if (it ! = refs.end()) { association.swap(it->second); refs.erase(it); if (refs.size() == 0) { associations.erase(refs_it); } } } } } // release the old value (outside of the lock). association.releaseHeldValue(); }Copy the code
objc_setAssociatedObject()What does the function do?
inline void objc_object::setHasAssociatedObjects() { if (isTaggedPointer()) return; retry: isa_t oldisa = LoadExclusive(&isa.bits); isa_t newisa = oldisa; if (! newisa.nonpointer || newisa.has_assoc) { ClearExclusive(&isa.bits); return; } newisa.has_assoc = true; if (! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; }Copy the code

It marks the tag bit has_ASsoc in the ISA structure as true, indicating that the current object has an associated object.

objc_getAssociatedObject()

The call stack for this function is as follows

├ ─ sci-scied (id, const void *key) ├ ─ sci-scied (id, const void *key) const void *key);Copy the code

This is a fairly simple function to understand

id _object_get_associative_reference(id object, const void *key) { ObjcAssociation association{}; { AssociationsManager manager; //1 AssociationsHashMap &associations(manager.get()); //1 AssociationsHashMap::iterator i = associations.find((objc_object *)object); //2 if (i ! = associations.end()) { ObjectAssociationMap &refs = i->second; ObjectAssociationMap::iterator j = refs.find(key); if (j ! = refs.end()) { association = j->second; association.retainReturnedValue(); } } } return association.autoreleaseReturnedValue(); }Copy the code
  1. throughAssociationsManagergetAssociationsHashMapOf the table
  2. Find associated objects through hashi tables
  3. All that is left is to update the object to see if it was first created and then return the object
objc_removeAssociatedObjects()

The call stack is as follows:

├ ─ 055 ├ ─ 055 ├ ─ 055 ├ ─ 055 ├ ─ 055Copy the code

Code implementation

void objc_removeAssociatedObjects(id object) { if (object && object->hasAssociatedObjects()) { _object_remove_assocations(object); }}Copy the code

Whether the check object is nil and whether the associated object exists

And then call the implementation similar to get above

void
_object_remove_assocations(id object)
{
    ObjectAssociationMap refs{};
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);
            associations.erase(i);
        }
    }
    // release everything (outside of the lock).
    for (auto &i: refs) {
        i.second.releaseHeldValue();
    }
}
Copy the code

AssociationsManager -> AssociationsHashMap -> Object exists and if so erases.- > releaseHeldValue() Object exists

summary

The application of associated objects and how the system implements associated objects are generally in the following order: AssociationsManager Associated object manager ->AssociationsHashMap Hash map ->ObjectAssociationMap Associated object pointer ->ObjcAssociation Associated object

How are associated objects managed in memory? How does an associated object implement the weak property?

How are associated objects managed in memory?

When I call objc_setAssociatedObject(), I call the following function:

_object_set_associative_reference(id object, const void *key, ID value, uintptr_t policy

ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
Copy the code

The policy here is to use retain or some other relevant memory enumeration for absolute memory.

enum {
    OBJC_ASSOCIATION_SETTER_ASSIGN      = 0,
    OBJC_ASSOCIATION_SETTER_RETAIN      = 1,
    OBJC_ASSOCIATION_SETTER_COPY        = 3,            // NOTE:  both bits are set, so we can simply test 1 bit in releaseValue below.
    OBJC_ASSOCIATION_GETTER_READ        = (0 << 8),
    OBJC_ASSOCIATION_GETTER_RETAIN      = (1 << 8),
    OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
};
Copy the code

Use the acquireValue() function to determine which memory key to use.

inline void acquireValue() { if (_value) { switch (_policy & 0xFF) { case OBJC_ASSOCIATION_SETTER_RETAIN: _value = objc_retain(_value); break; case OBJC_ASSOCIATION_SETTER_COPY: _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy)); break; }}}Copy the code

How does an associated object implement the weak property?

First of all, this is a very technical question that tests iOS developers’ understanding of the basics.

You can specify the following dependencies when binding an associated object to NSObject:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, // weak reference OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // strong reference, non-atomic operation OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_COPY = 01403 // copy = 01403 // copy = 01403 // copy = 01403 // copy = 01403;Copy the code

An odd problem with the enumeration above is that there is no OBJC_ASSOCIATION_WEAK option in the enumeration.

Based on the above code introduction, we know that Objective-C uses AssociationsManager to centrally manage the associated objects of each object. The corresponding associated object is then accessed via the static key(usually a fixed value). We then call the association.erase () function at dealloc to unreference these associative objects:

Dealloc object_dispose objc_destructInstance _object_remove_assocations // Remove the necessary associated objectsCopy the code

That is, no variables are allocated to associated objects in the memory space of the NSObject object.

The difference between weak and assign is that objective-C automatically sets nil when the weak object is destroyed, whereas assign does not.

How does this logic work?

Weak_table_t ‘ ‘weak_tabl’). When a weak pointer is assigned and the address of a valid object is assigned, the address of the object and the address of the weak pointer are registered in the weak table. The object address is the key. When an object is deprecated, all weak Pointers to it can be quickly found based on the address of the object. These weak Pointers are assigned 0(nil) and removed from the ‘weak’ table.

So, to implement weak references (rather than assign references), there is a __weak pointer to the address of the referenced object. Only then, when the object is destroyed, can the pointer be found by the Runtime and set to nil. NSObject and its associated object relationship, there is no intermediary such as Pointers, so only the OBJC_ASSOCIATION_ASSIGN option, but not the OBJC_ASSOCIATION_WEAK option.

So how do we solve for the weak attribute for the associated object?

We can declare a class that holds a weak member variable in a curve-saving fashion, and then instantiate our custom class instance as an associated object.

Declare a class that encapsulates the Weak object

@interface WeakAssociatedObjectWrapper : NSObject
@property (nonatomic, weak) id object;
@end

@implementation WeakAssociatedObjectWrapper
@end
Copy the code

call

@interface UIView (ViewController) @property (nonatomic, weak) UIViewController *vc; @end @implementation UIView (ViewController) - (void)setVc:(UIViewController *)vc { WeakAssociatedObjectWrapper *wrapper  = [WeakAssociatedObjectWrapper new]; wrapper.object = vc; objc_setAssociatedObject(self, @selector(vc), wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (UIViewController *)vc { WeakAssociatedObjectWrapper *wrapper = objc_getAssociatedObject(self, _cmd); return wrapper.object; } @endCopy the code

See what I mean? The curve saves the country. The code is imported from the Weak Associated Object

Associated Object Reference

How does Autoreleasepool work? What data structures are used?

In ARC we use the @Autoreleasepool {} keyword to circle the code blocks that need to be managed automatically. This process is using an Autoreleasepool

@autoreleasepool {<#statements#> // code block}Copy the code

The above code compiler will eventually rewrite it to look like this

void *context = objc_autoreleasePoolPush();
Copy the code

Objc_autoreleasePoolPop (context);

  • objc_autoreleasePoolPush()
  • objc_autoreleasePoolPop()

Both of these functions encapsulate AutoreleasePoolPage, the class that is at the heart of the auto-release mechanism

AutoreleasePoolPage

AutoreleasePoolPage is a C++ class

  • AutoreleasePoolThere is no single structure, there are severalAutoreleasePoolPageIn order toTwo-way linked listAs can be seen from the figure above, the bidirectional list hasPrecursor parentandThe subsequent child.
  • AutoreleasePoolIs in accordance with thethreadOne-to-one (Thread member variables)
  • AutoreleasePoolPageAutomatically release each Page of the pool storage object’s data structure4KBMemory, itself occupied by member variables56Bytes, leaving space for the callautoreleaseMethod, and inserts a sentinel into the Page, which is essentially an empty address
  • When a page is full, a new page is createdAutoreleasePoolPageObject and insert the sentinel token 

class AutoreleasePoolPage {
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)
#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};
Copy the code
  • magicCheck variables for verification integrity
  • nextPoint to the newly added AutoRelease object
  • threadPage current thread, AutoreleasePool is thread-to-thread (thread pointer in structure points to current thread)
  • parentThe parent node points to the previous page
  • childThe child node points to the next page
  • depthThe depth of the list, the number of nodes
  • hiwatHigh Water Mark An upper limit for data capacity
  • EMPTY_POOL_PLACEHOLDERAn empty placeholder pool
  • POOL_BOUNDARYIs a boundary object nil, the original source variable name isPOOL_SENTINELSentinel object that distinguishes each page from each AutoreleasePoolPage boundary
  • PAGE_MAX_SIZE= 4096. Why 4096? Virtual memory is 4096 bytes per sector,4K aligned.
  • COUNTNumber of objects in a page

Let’s take a look at the working mechanism diagram

This picture comes from kuaishou colleague Zhou Xueyun. If the big guy sees this picture, he hopes to be allowed to use it.

As we can see from the diagram above, AutoreleasePoolPage exists as a stack, and internal objects correspond to objc_autoreleasePoolPush and objc_autoreleasePoolPop by going on and off the stack

If nested AutoreleasePool is identified by a sentinel object, the next and precursor of the linked list are updated each time the table is created and destroyed.

When we send an AutoRelease message to an object, we are actually adding the object to the position pointed to by the next pointer at the top of the AutoreleasePoolPage stack

Here is only one page as an example.

summary

  • There are N autorelease poolsAutoreleasePoolPageAutoreleasePoolPage is a c++ class. AutoreleasePoolPage is a bidirectional linked list that forms an automatic release pool
  • When an object calls the AutoRelease method, it is added to the stack of AutoreleasePoolPage
  • Pop is passing in a boundary object (a sentinel object) and then sending a release message to the object in the page

Underlying implementation principles of AutoreleasePool

How does ARC work? What optimizations have been made for retain and release under ARC

ARC automatic reference counting is a mechanism introduced by Apple objC4 for the compiler to help instance objects automatically retain the latter release in place.

Its implementation principle is to insert relevant code at the compilation level to help complete the MRC era requires developers to manually fill in and manage the object of the method of memory operations.

To explain how this works, I found an article with code examples where the compiler does a lot of optimization from the code to assembly. Update information about isa Pointers.

Understand ARC implementation principles

There is one point that needs to be mentioned. In the previous section we talked about SlideTable, but there are still some things we don’t understand. Now let’s connect them through ISA

The composition of the isa

union isa_t { Class cls; uintptr_t bits; struct { uintptr_t nonpointer : 1; Uintptr_t has_assoc: 1; Uintptr_t has_cxx_dtor: 1; Uintptr_t shiftcls: 33; // MACH_VM_MAX_ADDRESS 0x1000000000 -> Uintptr_t Weakly_referenced: 1; Uintptr_t dealLocating: 1; Uintptr_t has_sidetable_rc: 1; //1 uintptr_t extra_rc: 19; //1 uintptr_t extra_rc: 19 //-> store reference count}; };Copy the code

Nonpointer, Weakly_referenced, has_sidetable_rc and extra_rc are all member variables that are directly related to ARC, and most other variables are also involved.

What are the optimizations for retain release

It can be roughly divided into the following

  • TaggedPointer pointer optimization
  • ! Newisa. nonpointer: retain or release from an unoptimized ISA
  • Newisa.nonpointer: optimized ISA, which is distinguished by extra_rc overflow. I put the relevant code below and output the results.
Memory operations objc-retain objc-release
TaggedPointer The value is stored in a pointer and returned directly Return false.
! nonpointer Not optimizedisa, the use ofsidetable_retain() Not optimizedisaperformsidetable_release
nonpointer Has been optimizedisa, which is divided intoextra_rcOverflow and non-overflow Has been optimizedisa, divided into underflow and not underflow two cases
Nonpointer has optimized EXTRA_RC for ISA objc_retain objc_release
When not overflow isa.extra_rc+ 1 NA
When the overflow willisa.extra_rcThe median value is transferred tosidetableIn, and then willisa.has_sidetable_rcSet totrueIs usedsidetableTo count the number of references NA
No underflow NA Extra_rc –
underflow NA fromsidetableA borrow toextra_rcIf it reaches half full, if it cannot borrow, it indicates that the reference count returns to zero and needs to be released

NA -> non available Unavailable

Take a look at the RETAIN source code

ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { if (isTaggedPointer()) return (id)this; // Return bool sideTableLocked = false; bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; do { transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); Isa newisa = oldisa; if (slowpath(! newisa.nonpointer)) { ClearExclusive(&isa.bits); // Unoptimized part of isa if (! tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(); } if (slowpath(tryRetain && newisa.deallocating) {// ClearExclusive(&isa.bits) is being released; if (! tryRetain && sideTableLocked) sidetable_unlock(); return nil; } // the extra_rc does not overflow reference count ++ uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ // extra_rc spills if (slowpath(carry)) {// newisa.extra_rc++ overflows if (! handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); // Save half the reference count, ready to copy the other half to side table.if (! tryRetain && ! sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } // update isa value} while (slowpath(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(transcribeToSideTable)) { sidetable_addExtraRC_nolock(RC_HALF); // copy the other half of the slowpath to side table side table. tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; }Copy the code

Release the source code

ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { if (isTaggedPointer()) return false; bool sideTableLocked = false; isa_t oldisa; isa_t newisa; retry: do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(! newisa.nonpointer)) { ClearExclusive(&isa.bits); // isa if (sideTableLocked) sidetable_unlock(); return sidetable_release(performDealloc); SEL_dealloc} newISa.bits = subc(newISa.bits, RC_ONE, 0, &carry); // extra_rc-- if (slowpath(carry)) { // donot ClearExclusive() goto underflow; } // update isa value} while (slowpath(! StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(sideTableLocked)) sidetable_unlock(); return false; Underflow: // Handle underflow, borrow from side table or release newisa = oldisa; If (slowpath(newisa.has_sidetable_rc)) {// Sidetable_rc if (! handleUnderflow) { ClearExclusive(&isa.bits); RootRelease_underflow (performDealloc); rootRelease_underflow(performDealloc); } size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); Newisa.extra_rc = serial-1; newISa.extra_rc = serial-1; newISA.extra_rc = serial-1; newISA.extra_RC = serial-1; // redo the original decrement too bool stored = StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits); // Save failed, restore the scene, try again if (! stored) { isa_t oldisa2 = LoadExclusive(&isa.bits); isa_t newisa2 = oldisa2; if (newisa2.nonpointer) { uintptr_t overflow; newisa2.bits = addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow); if (! overflow) { stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, newisa2.bits); }}} side table if (! stored) { sidetable_addExtraRC_nolock(borrowed); goto retry; } sidetable_unlock(); return false; } else {// Side table is empty after all. fall-through to the dealloc path.} If (slowpath(newisa.deallocating)) {ClearExclusive(&ISa.bits); if (sideTableLocked) sidetable_unlock(); return overrelease_error(); } // Update the ISA state newISa.dealLocating = true; if (! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; if (slowpath(sideTableLocked)) sidetable_unlock(); // Execute the SEL_dealloc event __sync_synchronize(); if (performDealloc) { ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return true; }Copy the code

summary

The reference count is stored in ISa.extra_rc and sidetable, respectively. When ISA.EXTRA_RC overflows, half of the count is moved to sidetable, and when it overflows, the count is rolled back. When both are empty, the release process is performed

What can cause a memory leak in ARC

  • A circular reference in a block
  • Circular reference to NSTimer
  • Circular reference to addObserver
  • Strong reference to delegate
  • Large number of cycles memory burst
  • Memory handling of non-OC objects (manual release required)

conclusion

This is the part where we discuss the memory management of the interview questions. We will finish the rest of the questions in the next part. Thank you for your support

More:IOS interview questions

Since the | address