ARC is a reference count management technology in iOS, which helps iOS to implement automatic garbage collection. The principle of the implementation is managed by the compiler, and the runtime library assists the compiler to complete. It mainly involves Clang (LLVM compiler) and objC4 runtime libraries.
The main content of this paper is extended by modifiers __strong, __weak and __autoRelease, which respectively extend the implementation principles of reference counting, weak reference table and automatic release pool. Here are some questions to ask before you read:
-
How do I store reference counts under ARC?
-
Such as
[NSDictionary dictionary]
Method to create objects that are different in ARC. -
Weak reference table data structure.
-
Explain Hot and Cold pages in the auto-release pool.
If you already know the above, this article may be of limited help to you, but if you still have questions about these questions, I believe this article will answer your questions.
A, Clang
In Objective-C, the reference relationship of an object is determined by reference modifiers such as __strong, __weak, __autorelease, and so on, and the compiler generates code with different logic to manage memory based on these modifiers.
To see where Clang comes in, we can convert Objective-C code into LLVM intermediate code at the command line:
// Switch to your file pathcdPath // Use main.m to generate the intermediate code file main.ll clang-s - fobjc-arc-emit - LLVM main.m -o main.llCopy the code
I added the defaultFunction method to the main.m file and converted it to intermediate code using the command line command:
void defaultFunction() {
id obj = [NSObject new];
}
Copy the code
After you type the command on the command line, you’ll find main.ll under the folder, which will look like this:
define void @defaultFunction() # 0 {
%1 = alloca i8*, align 8
%2 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_The $_", align 8 %3 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, ! invariant.load ! 8 %4 = bitcast %struct._class_t* %2 to i8* %5 = call i8* bitcast (i8* (i8*, i8*, ...) * @objc_msgSend to i8* (i8*, i8*)*)(i8* %4, i8* %3) %6 = bitcast i8* %5 to %0* %7 = bitcast %0* %6 to i8* store i8* %7, i8** %1, align 8 call void @objc_storeStrong(i8** %1, i8* null)# 4
ret void
}
Copy the code
Although the content is a bit much, but careful analysis down roughly the following content:
void defaultFunction() {
id obj = obj_msgSend(NSObject, @selector(new));
objc_storeStrong(obj, null);
}
Copy the code
Obj_msgSend (NSObject, @selector(new)) is a new object, and objc_storeStrong is a method in objC4 library. The logic is as follows:
void objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
Copy the code
The above code does the following four things in order:
- Check that the obj address entered is the same as the address pointed to by the pointer.
- Hold object, reference count + 1.
- Pointer to obj.
- The reference count of the original object is -1.
Objc_retain and objc_release are also methods in the OBJC4 library, which will be discussed later in this article.
Second, the isa
Before you can analyze the ARC source code, you need to be familiar with ISA, which stores some very important information. Here is how ISA is structured:
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.
struct objc_object {
isa_t isa;
};
Copy the code
As you can see from the following code, objc_object isa wrapper on top of isa.
struct objc_class : objc_object { isa_t isa; // Inherit from objc_object Class superclass; cache_t cache; // Implement caching and vtable class_datA_bits_t bits; // class_rw_t * plus custom rr/alloc flags };Copy the code
Objc_class inherits objc_Object with the following structure:
isa
Objc_object refers to the class, objc_class refers to the metaclass.superclass
: points to the parent class.cache
: Stores the method cache and Vtable optimized for user message forwarding.bits
: class_rw_t and class_ro_t, which hold lists of methods, protocols, attributes, and other flags.
The __strong modifier
In the MRC era, the Retain modifier increases the reference count of the referenced object by + 1. In ARC, the __strong modifier replaces the Retain modifier. We can use Clang to convert Objective-C code into LLVM to see how this works.
3.1 The middle code of the __strong modifier
Moving on to converting objective-C code into LLVM intermediate code, this time we’ll try the __strong modifier:
void strongFunction() {
id obj = [NSObject new];
__strong id obj1 = obj;
}
Copy the code
Intermediate code (the following intermediate code is removed for easier understanding) :
void defaultFunction() {
id obj = obj_msgSend(NSObject, @selector(new));
id obj1 = objc_retain(obj)
objc_storeStrong(obj, null);
objc_storeStrong(obj1, null);
}
Copy the code
Create an object, reference count + 1, release the object separately, and embed the logic inside objc_storeStrong:
void defaultFunction() {
id obj = obj_msgSend(NSObject, @selector(new));
id obj1 = objc_retain(obj)
objc_release(obj);
objc_release(obj1);
}
Copy the code
3.2 objc_retain
Next, let’s look at the internal logic of objC_retain and objc_release by analyzing the objC4 library source code. Take a look at the objc_retain implementation:
id objc_retain(id obj) {
if(! obj)return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
Copy the code
Continue to see the final location to the objc_Object ::rootRetain method:
ALWAYS_INLINE ID objC_object ::rootRetain(bool tryRetain, bool handleOverflow) {// If TaggedPointer is returnedif (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false; Isa oldisa = LoadExclusive(&ISa.bits); newisa = oldisa;if(slowpath(! Newisa.nonpointer)) {// Unoptimized ISA part ClearExclusive(& ISa.bits);if(! tryRetain && sideTableLocked) sidetable_unlock(); //if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else returnsidetable_retain(); } // Donot check newisa.fast_rr; we already called any RR overridesif (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if(! tryRetain && sideTableLocked) sidetable_unlock();returnnil; } // the extra_rc does not overflow reference count ++ uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); Extra_rc ++ // extra_rc overflowif (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if(! handleOverflow) { ClearExclusive(&isa.bits); // Call this function again with argument handleOverflow astrue
returnrootRetain_overflow(tryRetain); } // Keep 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)) {// copy the other half to sidetable side table.sideTable_addextrarc_nolock (RC_HALF); }if(slowpath(! tryRetain && sideTableLocked)) sidetable_unlock();return (id)this;
}
Copy the code
The above code is divided into three small branches:
- TaggedPointer: A value stored in a pointer is returned directly.
- ! Newisa. nonpointer: unoptimized ISA, used
sidetable_retain()
. - Newisa.nonpointer: Optimized ISA, extra_RC overflowed and unoverflowed.
- When no overflow occurs,
isa.extra_rc
Plus 1 is done. - Overflow when will
isa.extra_rc
The median value is transferred tosidetable
In, and then willisa.has_sidetable_rc
Set totrue
Is usedsidetable
To count the number of references.
- When no overflow occurs,
3.3 objc_release
Continue with the objc_Release implementation and finally locate the objc_Object ::rootRelease method:
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)) {// Unoptimized ISA ClearExclusive(& ISa.bits);if(sideTableLocked) sidetable_unlock(); // Whether to execute the Dealloc function, if yestrueExecute SEL_deallocreturn sidetable_release(performDealloc);
}
// extra_rc --
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 sidetable_rc is usedif (slowpath(newisa.has_sidetable_rc)) {
if(! HandleUnderflow) {// Call this function to handle overflow ClearExclusive(& ISa.bits);returnrootRelease_underflow(performDealloc); } // Borrow a reference count from sideTable to extra_rc size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);ifNewisa. extra_rc = digital-1; newISA. extra_rc = digital-1; newisA. extra_rc = digital-1; // redo the original decrement too bool stored = StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits); // Save failed. Restore the site and try againif(! 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); }}} // If the save fails, return to side tableif(! stored) { sidetable_addExtraRC_nolock(borrowed); goto retry; } sidetable_unlock();return false;
}
else{// Side table is empty after all. fall-through to the dealloc path.}} // Sidetable_rc is not used, Or sidetable_rc count == 0 will be released directly // If it is already released, throw an overrelease errorif (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return} // 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
This is a long list of code that looks like rootRetain’s logic when broken down:
- TaggedPointer: Returns false directly.
- ! Nonpointer: Non-optimized ISA executes sidetable_release.
- Nonpointer: An optimized ISA can be overflowed or not overflowed.
- Unspilled: extra_rc–.
- Underflow: Borrows from sidetable to extra_RC half full, failing which means the reference count has gone to zero and needs to be freed. Where borrowing may fail to save and retry continuously.
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.
3.4 rootRetainCount
The objc_Object ::rootRetainCount method is used to calculate the reference count. As you can see from the previous rootRetain and rootRelease source code analysis, reference counts exist in Isa. extra_RC and sidetable, respectively. This is also reflected in the rootRetainCount method.
inline uintptr_t objc_object::rootRetainCount() {// TaggedPointer returnsif (isTaggedPointer()) return(uintptr_t)this; sidetable_lock(); Isa ISA_t bits = LoadExclusive(&ISa.bits); ClearExclusive(&isa.bits); // Optimized ISA requires sidetable + bits.extra_rc + 1if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
returnrc; } // Return sidetable_retainCount sidetable_unlock();return sidetable_retainCount();
}
Copy the code
3.5 objc_autoreleaseReturnValue and objc_retainAutoreleasedReturnValue
In the era of MRC, there is a phrase called “create who release”. This means that objects created by developers using methods such as alloc, new, copy, and mutableCopy need to be released manually. Objects created and returned by other methods do not need to be released after being returned to the user. Such objects as arrays created by the [NSMutableArray Array] method are managed by automatic release pools by default. In the ARC era, the compiler also does some special processing for returned objects, which is explained below.
First, convert the code below to intermediate code:
id strongArrayInitFunction() {
return [[NSMutableArray alloc] init];
}
void strongArrayFunction() {
__strong id obj = [NSMutableArray array];
}
Copy the code
After the intermediate code is refined, the following code is obtained:
strongArrayInitFunction() {
id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_autoreleaseReturnValue(obj);
return obj;
}
strongArrayFunction() {
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj)
objc_release(obj);
}
Copy the code
Relative to the user to create objects, [NSMutableArray array] method to create a more the object returned after converting objc_retainAutoreleasedReturnValue method. This involves an optimization process:
- To save an object register to
autoreleasePool
The operation is being executedobjc_autoreleaseReturnValue
According to check whether the list of methods to be called later containsobjc_retainAutoreleasedReturnValue
Method to determine whether to optimize the flow. - In the implementation
objc_autoreleaseReturnValue
, the optimization process stores a flag bit in TLS (Thread Local Storage) and returns the object directly. - Execute subsequent methods
objc_retainAutoreleasedReturnValue
Check the flag bit of TLS to determine whether it is in the optimization process. If it is in the optimization process, the object is directly returned and the TLS state is restored.
The objc_autoreleaseReturnValue implementation is as follows:
Id objc_autoreleaseReturnValue(id obj) {// Return the object directly if the optimizer is usedif (prepareOptimizedReturn(ReturnAtPlus1)) returnobj; // Otherwise, go to the automatic release poolreturnobjc_autorelease(obj); } static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) { assert(getReturnDisposition() == ReturnAtPlus0); / / check to use the method of the function or the caller calls list, if then perform objc_retainAutoreleasedReturnValue, will not be registered in the autoreleasePoolif (callerAcceptsOptimizedReturn(__builtin_return_address(0)) {// Set the flag ReturnAtPlus1if (disposition) setReturnDisposition(disposition);
return true;
}
return false; } // Store ReturnAtPlus1 or ReturnAtPlus0 to TLS static ALWAYS_INLINE voidsetReturnDisposition(ReturnDisposition disposition) { tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition); } // Fetch the tag static ALWAYS_INLINE ReturnDispositiongetReturnDisposition() {
return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}
Copy the code
The objc_autoreleaseReturnValue code logic is roughly divided into:
- Check whether a call is immediately followed in the caller’s method
objc_retainAutoreleasedReturnValue
. - Save ReturnAtPlus1 to TLS.
- Returns the object if optimization is used, otherwise it is added to the automatic release pool.
Below is the objc_retainAutoreleasedReturnValue source code analysis:
Obj id objc_retainAutoreleasedReturnValue (id) {/ / if the TLS in tag using the optimization program, is returned directlyif (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
return objc_retain(obj);
}
static ALWAYS_INLINE ReturnDisposition acceptOptimizedReturn() {// Return ReturnDisposition disposition = getReturnDisposition(); // Restore to an unoptimized statesetReturnDisposition(ReturnAtPlus0); // reset to the unoptimized state
return disposition;
}
Copy the code
Objc_retainAutoreleasedReturnValue code logic roughly divided into:
- Retrieve the TLS tag.
- Reset TLS markup to ReturnAtPlus0.
- Return object if optimization is used, otherwise reference count + 1.
Through the analysis of the source code can be learned that the following code optimization process and not optimization process is quite a big difference:
strongArrayFunction() {
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj)
objc_release(obj);
}
Copy the code
The final optimization process is equivalent to:
id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_release(obj);
Copy the code
The unoptimized process is equivalent to:
id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_autorelease(obj);
objc_retain(obj);
objc_release(obj);
Copy the code
The __weak modifier
As we all know, weak indicates a weak reference and the reference count does not increase. After the original object is released, the weak reference variable will also be removed.
4.1 Middle code for the __weak modifier
First convert the following code to intermediate code:
void weakFunction() {
__weak id obj = [NSObject new];
}
void weak1Function() {
id obj = [NSObject new];
__weak id obj1 = obj;
}
void weak2Function() {
id obj = [NSObject new];
__weak id obj1 = obj;
NSLog(@"% @",obj1);
}
Copy the code
Below is the intermediate code after transformation and refining:
weakFunction() {
id temp = objc_msgSend(NSObject, @selector(new));
objc_initWeak(&obj, temp);
objc_release(temp);
objc_destroyWeak(obj);
}
weak1Function() {
id obj = objc_msgSend(NSObject, @selector(new));
objc_initWeak(&obj1, obj);
objc_destroyWeak(obj1);
objc_storeStrong(obj, null);
}
weak2Function() {
id obj = objc_msgSend(NSObject, @selector(new));
objc_initWeak(obj1, obj);
id temp = objc_loadWeakRetained(obj1);
NSLog(@"% @",temp);
objc_release(temp);
objc_destroyWeak(obj1);
objc_storeStrong(obj, null);
}
Copy the code
-
WeakFunction: After declaring __weak object in this method, it is not used, so after objc_initWeak, it immediately releases and calls objc_Release and objc_destroyWeak methods.
-
Weak1Function: in this method, OBj is a strong reference, obj1 is a weak reference, objc_initWeak and objc_destroyWeak are called in pairs successively, corresponding to the initialization and release methods of weak reference variables.
-
Weak2Function: Different from weak1Function is that weak-reference variable obj1 is used. Before weak-reference variable is used, the compiler creates a temporary strong-reference object, which is released immediately after it is used up.
4.2 objc_initWeak and objc_destroyWeak
2 objc_initWeak and objc_destroyWeak
Here are the objc_initWeak and objc_destroyWeak code implementations:
id objc_initWeak(id *location, id newObj) {
if(! newObj) { *location = nil;returnnil; } // This address has no value and is being assigned a new valuereturnstoreWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); } void objc_destroyWeak(id *location) {// This address has a value, no new value, Crash (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> (location, nil); }Copy the code
In the end, storeWeak is used to implement the respective logic. Before we look at storeWeak, we should first understand the meaning of its template parameters:
storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj);
Copy the code
DontHaveOld, DoHaveNew, and DoCrashIfDeallocating are template parameters, which are as follows:
enum HaveOld { DontHaveOld = false, DoHaveOld = true}; // No value enum HaveNew {DontHaveNew =false, DoHaveNew = true}; // New value enum CrashIfDeallocating {DontCrashIfDeallocating =false, DoCrashIfDeallocating = true}; // Whether the object being released crashesCopy the code
4.2.2 storeWeak
Continue with the implementation of storeWeak:
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if(! haveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; Retry: // Remove the SideTable from SideTables that stores weak-referenced tables (weak_table_t)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 the value pointed to by location changes, the oldObj is retrieved againif(haveOld && *location ! = oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); goto retry; } // If there is a new valueifClass CLS = newObj->getIsa();if(cls ! = previouslyInitializedClass && ! ((objc_class *) CLS)->isInitialized()) {// Create a non-metaclass and initialize it, SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); previouslyInitializedClass = cls; goto retry; }} // If there are old values, clear the weak reference table corresponding to the old valuesif(haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // If a new value is assigned, register the weak reference table corresponding to the new valueif(haveNew) { newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating); // Set the ISA flag bitweakly_referenced totrue
if(newObj && ! newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
Copy the code
This code does a few things:
- From the global hash table
SideTables
, using the address of the object itself to carry out bit operation to get the corresponding subscript, obtain the weak reference table of the object.SideTables
Is a hash table of 64 elements, and when a collision occurs, maybe oneSideTable
There are multiple objects sharing a weak reference table in. - If a new value is allocated, the class corresponding to the new value is checked to see if it has been initialized. If not, the class is initialized in place.
- If location points to another old value, the weak reference table corresponding to the old value is unregistered.
- If a new value is assigned, the new value is registered in the corresponding weak reference table. will
isa.weakly_referenced
Set totrue
, indicating that the object has weak reference variables and the weak reference table should be cleared when the object is released.
Holdings weak_register_no_lock and weak_unregister_no_lock
Weak_register_no_lock and weak_unregister_NO_lock are used in the above code to register and unregister weak reference tables. Continue to check the implementation of these two methods:
id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) { objc_object *referent = (objc_object *)referent_id; // Referrer = (objc_object **)referrer_id; // weak reference variableif(! referent || referent->isTaggedPointer())returnreferent_id; // Check that the object is not locating; bool dealLocating;if(! referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); }else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
returnnil; } deallocating = ! (*allowsWeakReference)(referent, SEL_allowsWeakReference); } // If releasing, determine whether to trigger crash based on crashIfDeallocatingif (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
returnnil; } } weak_entry_t *entry; // Add the weak reference record to the weak table if the weak reference record exists in the current tableif ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
elseWeak_entry_t new_entry(referent, referrer); weak_entry_t new_entry(referent, referrer); Weak_grow_maybe (weak_table); weak_grow_maybe(weak_table); Weak_entry_insert (weak_table, &new_entry); weak_entry_insert(weak_table, &new_entry); }return referent_id;
}
Copy the code
The main logic of this code is:
- Check whether it is being released, and if so, according to
crashIfDeallocating
Check whether a crash is triggered. - check
weak_table
Is there a corresponding to the referenced object inentry
If so, add the address of the weak reference variable pointer directly to itentry
In the. - if
weak_table
I didn’t find a matchentry
, create a new oneentry
And add the weak reference variable pointer addressentry
In the. At the same time checkweak_table
Whether to expand the capacity.
Weak_unregister_no_lock code is implemented below:
void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id) { objc_object *referent = (objc_object *)referent_id; // Referrer = (objc_object **)referrer_id; // Weak_entry_t *entry;if(! referent)return;
if((entry = weak_entry_for_referent(weak_table, referent)) {weak_entry_for_referent(weak_table, referent)) { Remove the reference from the record remove_referrer(entry, referrer); // Check if the reference record is empty bool empty =true;
if(entry->out_of_line() && entry->num_refs ! = 0) { empty =false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break; }}} // Remove the record if the current record is emptyif(empty) { weak_entry_remove(weak_table, entry); }}}Copy the code
The main logic of this code is:
- from
weak_table
According to find the corresponding of the referenced objectentry
, and then will weakly reference the variable pointerreferrer
fromentry
Removed. - Removes a weakly referenced variable pointer
referrer
After that, checkentry
Whether it is empty, and if it is, remove it fromweak_table
Removed.
4.2.4 weak_table
The above code uses weak_table to save the entry of the referenced object, and the following continues to analyze the specific implementation of the addition and deletion function of Weak_table:
static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if(! weak_entries)returnnil; Size_t begin = hash_pointer(referent) & Weak_table ->mask; size_t index = begin; size_t hash_displacement = 0; // Linear probe, if the subscript stores other objects, move down until the correct subscript is found.while(weak_table->weak_entries[index].referent ! = referent) { index = (index+1) & weak_table->mask; // Cannot exceed the maximum length limit of weak_table // Return to the initial subscript, abnormal error is reportedif(index == begin) bad_weak_table(weak_table->weak_entries); // Every time the conflict moves down hash_displacement+ 1, the current displacement does not exceed the maximum recorded displacement hash_displacement++;if (hash_displacement > weak_table->max_hash_displacement) {
returnnil; }}return &weak_table->weak_entries[index];
}
Copy the code
The above code is the process of weak_table searching for entry, and also the hash table addressing process, using the method of linear detection to solve the problem of hash conflict:
- Obtain the hash table subscript by calculating the address of the referenced object.
- Check if the corresponding subscript is stored and we want to find the address, if so return that address.
- If not, keep looking until you find it. In the process of moving down, the subscript cannot be exceeded
weak_table
Maximum length, at the same timehash_displacement
Can’t exceed the recordmax_hash_displacement
Maximum hash displacement.max_hash_displacement
Is the maximum hash displacement recorded for all insert operations, and if it exceeds that, there must be an error.
The following is the code implementation of weak_table inserting entry:
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry) { weak_entry_t *weak_entries = weak_table->weak_entries; assert(weak_entries ! = nil); Size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask); // Use the hash algorithm to get the subscript size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask); size_t index = begin; size_t hash_displacement = 0; // Determine whether the current subscript is empty, if not continue to address spacewhile(weak_entries[index].referent ! = nil) { index = (index+1) & weak_table->mask;if(index == begin) bad_weak_table(weak_entries); hash_displacement++; } // Store weak_entries[index] = *new_entry; weak_table->num_entries++; // Update the maximum hash shift valueif(hash_displacement > weak_table->max_hash_displacement) { weak_table->max_hash_displacement = hash_displacement; }}Copy the code
Similar to the checking process, the steps for weak_table to insert entry are as follows:
- Obtain the hash table subscript by calculating the address of the referenced object.
- Check whether the corresponding subscript is empty. If not, continue searching until a space is found.
- The weak reference variable pointer is put into the void and updated at the same time
weak_table
The current number of members ofnum_entries
And the maximum hash displacementmax_hash_displacement
.
The following is the code implementation of weak_table to remove entry:
Static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) {// Release all weak references in the entryif(entry->out_of_line()) free(entry->referrers); // void pointer bzero(entry, sizeof(*entry)); Weak_table ->num_entries--; // Update the number of weak_table objects and check whether the table size can be reduced. weak_compact_maybe(weak_table); }Copy the code
The steps for removing entry from weak_table are as follows:
- The release of
entry
And weak reference variables in it. - Update the number of Weak_table objects and check whether the table size can be reduced
4.2.5 entry and referrer
In the weak reference table entry corresponds to the object being referenced, and the referrer represents the weak reference variable. Each time it is weakly referenced, the weak reference variable pointer referrer is added to the entry, and when the original object is released, the entry is emptied and removed.
Let’s look at the implementation of adding referrer to entry:
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if(! Entry ->out_of_line()) {// Inline_referrers are added directly to the inline_referrers when inline_referrers are not exceededfor (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return; } // If inline_referrers exceeds WEAK_INLINE_COUNT, Weak_referrer_t *new_referrers = (Weak_referrer_t *) calloc(WEAK_INLINE_COUNT, sizeof(Weak_referrer_t)); // Transfer the inline_referrers reference to new_referrers onlyfor(size_t i = 0; i < WEAK_INLINE_COUNT; i++) { new_referrers[i] = entry->inline_referrers[i]; } // referrers = new_referrers; entry->num_refs = WEAK_INLINE_COUNT; entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; entry->mask = WEAK_INLINE_COUNT-1; entry->max_hash_displacement = 0; } assert(entry->out_of_line()); // Perform capacity expansion when the load factor is too highif (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
returngrow_refs_and_insert(entry, new_referrer); } // Calculate the subscript size_t begin = w_hash_pointer(new_referrer) & (entry->mask); size_t index = begin; size_t hash_displacement = 0; // The following table position is not empty, occurshashThe collision,while(entry->referrers[index] ! = nil) {// move hash_displacement++; index = (index+1) & entry->mask;if(index == begin) bad_weak_table(entry); } // Record the maximum displacementif(hash_displacement > entry->max_hash_displacement) { entry->max_hash_displacement = hash_displacement; Weak_referrer_t &ref = entry-> Referrers [index]; weak_referrer_t &ref = entry-> Referrers [index]; ref = new_referrer; entry->num_refs++; }Copy the code
The structure of Entry is similar to that of Weak_table, which uses hash table and linear detection method to find corresponding positions. On this basis there is a bit of a difference:
entry
There’s a flag bitout_of_line
Initially, the flag bit isfalse
.entry
I’m using an ordered arrayinline_referrers
The storage structure of.- when
inline_referrers
The number of members exceedsWEAK_INLINE_COUNT
.out_of_line
Flag bit becomestrue
To start using hash table storage structures. Capacity expansion occurs whenever the hash table load exceeds 3/4.
Moving on to the concrete implementation of removing the referrer from Entry:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if(! Entry ->out_of_line()) {// If inline_referrers is not exceeded, the corresponding position will be cleared directlyfor (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return; Size_t begin = w_hash_pointer(old_referrer) & (entry->mask); size_t index = begin; size_t hash_displacement = 0; // A hash conflict occurswhile(entry->referrers[index] ! = old_referrer) { index = (index+1) & entry->mask;if (index == begin) bad_weak_table(entry);
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return; } // referrers[index] = nil; entry->num_refs--; }Copy the code
Steps to remove the referrer from Entry:
out_of_line
forfalse
When from an ordered arrayinline_referrers
To find and remove.out_of_line
fortrue
From the hash table and remove.
4.2.6 dealloc
When the referenced object is released, the ISA.Weakly_referenced flag bit is checked, and each weakly_referenced flag bit is true.
- (void)dealloc {
_objc_rootDealloc(self);
}
Copy the code
Follow the dealloc method logic down to clearDeallocating_slow:
NEVER_INLINE void objc_object::clearDeallocating_slow() { assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); Weak_table SideTable& table = SideTables()[this]; table.lock(); // Determine if there is weak reference to clear the corresponding entry of the objectif(isa.weakly_referenced) { weak_clear_no_lock(&table.weak_table, (id)this); } // Clear the reference count for this object stored in sideTableif (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
Copy the code
As can be seen from the above code, when the object release executes the dealloc function, it checks the ISA.WeakLY_referenced flag bit and then decides whether to clean the entry in the Weak_table.
4.3 objc_loadWeakRetained
As you can see from the intermediate code analysis above, before using weak reference variables, the compiler creates a temporary strong reference object to ensure that it will not cause errors when using weak reference variables. It will be released immediately after use.
Let’s see how we can strongly reference a weak reference pointer to an object:
id objc_loadWeakRetained(id *location) { id obj; id result; Class cls; SideTable *table; Retry: // Get a weak reference pointer to the object obj = *location;if(! obj)return nil;
if (obj->isTaggedPointer()) returnobj; Weak_table table = &SideTables()[obj]; // Retry table->lock() if the referenced object changes in the meantime;if(*location ! = obj) { table->unlock(); goto retry; } result = obj; cls = obj->ISA();if(! CLS ->hasCustomRR()) {// Classes and superclasses are not customized Retain/release/autorelease/retainCount / _tryRetain / _isDeallocating/retainWeakReference/allowsWeakReference method assert(cls->isInitialized()); / / try to retainif(! obj->rootTryRetain()) { result = nil; }}else {
if(CLS - > isInitialized () | | _thisThreadIsInitializingClass (CLS)) {/ / to get custom BOOL SEL_retainWeakReference method (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL)) class_getMethodImplementation(cls, SEL_retainWeakReference);if((IMP)tryRetain == _objc_msgForward) { result = nil; } // Call a custom functionelse if(! (*tryRetain)(obj, SEL_retainWeakReference)) { result = nil; }}else{// class is not initialized, then retry table->unlock(); _class_initialize(cls); goto retry; } } table->unlock();return result;
}
Copy the code
The main logic of the above code is:
- The weak reference table is obtained from the object to which the weak reference points and locked to prevent it from being purged in the meantime.
- Check whether custom is included
retain
Method, if not, use the defaultrootTryRetain
Method to make the reference count + 1. - If custom is used
retain
Method, the custom method will be called. Before calling, it will determine whether the class of the object has been initialized. If not, it will be initialized first and then called.
The __autorelease modifier
In an ARC environment, the __autoRelease modifier adds objects to the autorelease pool, which manages the release.
5.1 Intermediate code for the __autorelease modifier
Convert the following code to intermediate code to see what the compiler does:
void autoReleasingFunction() { @autoreleasepool { __autoreleasing id obj = [NSObject new]; }}Copy the code
Intermediate code after conversion:
void autoReleasingFunction() {
id token = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(new));
objc_autorelease(obj);
objc_autoreleasePoolPop(token);
}
Copy the code
The above code can be analyzed as follows:
@autoreleasepool{}
The keyword is converted by the compiler toobjc_autoreleasePoolPush
andobjc_autoreleasePoolPop
This pair of methods.- The __autoreleasing modifier is converted to
objc_autorelease
That will beobj
Add to automatic release pool.
As can be seen from the above analysis, the compiler’s processing logic for automatic release pool can be roughly divided into:
- by
objc_autoreleasePoolPush
As the first function to automatically release the pool scope. - use
objc_autorelease
Adds objects to the automatic release pool. - by
objc_autoreleasePoolPop
As the last function to automatically release the pool scope.
5.2 Automatic release pool preparation
Before we go any further, we need to know a few things about auto-release pools:
Each autoreleasepoolpool consists of one or more AutoreleasepoolPages, 4096 bytes in SIZE, that form a bidirectional linked list using parent and child Pointers.
-
HotPage: the current page in use. Operations are performed on the hotPage, usually at the end of the list or the second to last. Stored in TLS, it can be understood as a linked list of auto-release pools shared by each thread.
-
ColdPage: The page at the head of the list, possibly also hotPage.
-
POOL_BOUNDARY: nil macro definition that replaces the previous sentinel object POOL_SENTINEL to be pushed into the auto-release pool in objc_autoreleasePoolPush when the auto-release pool is created. When calling objc_autoreleasePoolPop, objects in the pool are released sequentially until the last POOL_BOUNDARY is reached.
-
EMPTY_POOL_PLACEHOLDER: Pushing a POOL_BOUNDARY when no object has been pushed into the autofree pool stores EMPTY_POOL_PLACEHOLDER in TLS as the identifier and POOL_BOUNDARY is not pushed into the pool at this time. The next time an object is pushed into the automatic release pool, check to retrieve the identifier in TLS and then push POOL_BOUNDARY.
-
Next: pointer to AutoreleasePoolPage A pointer to a slot at the top of the stack that moves up each time a new element is added.
5.3 objc_autoreleasePoolPush
Objc_autoreleasePoolPush method code implementation:
static inline void *push()
{
id *dest;
if(DebugPoolAllocation) {// In test state, a new page is created for each automatic release pool. Dest = autoreleaseNewPage(POOL_BOUNDARY); }else{// push a POOL_BOUNDARY onto the stack, dest = autoreleaseFast(POOL_BOUNDARY); } assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);return dest;
}
Copy the code
The objc_autoreleasePoolPush method essentially pushes a POOL_BOUNDARY into the automatic releasepool as the starting point for the AutoReleasepool. The specific logic of the autoreleaseFast method will be examined later when the AutoRelease method is examined.
5.4 autorelease
Here’s how to add the autorelease method to the pool:
static inline id autorelease(id obj) {
id *dest __unused = autoreleaseFast(obj);
return obj;
}
Copy the code
Read on:
static inline id *autoreleaseFast(id obj) {
AutoreleasePoolPage *page = hotPage();
if(page && ! Page ->full()) {// Add a hotPage to the stackreturn page->add(obj);
} else if(page) {// hotPage is full. Create a page and add it to a new pagereturn autoreleaseFullPage(obj, page);
} else{// Create a new Page without hotPage and add it to the PagereturnautoreleaseNoPage(obj); }}Copy the code
The above code logic:
- if
hotPage
If it exists and is not full, push it directlyhotPage
. - if
hotPage
Exists and is full, callautoreleaseFullPage
. - if
hotPage
Not present, callautoreleaseNoPage
.
The implementation of autoreleaseFullPage continues below:
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { assert(page == hotPage()); assert(page->full() || DebugPoolAllocation); // If no page is found, create a new page and set to hotPagedo {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
Copy the code
This method is executed when the hotPage is full. The logic is as follows:
- To view
hotPage
Is there a successor node? If so, use the successor node directly. - If there is no successor node, create one
AutoreleasePoolPage
. - Adds an object to the obtained
page
And set it tohotPage
, is stored in TLS to share.
Let’s start with the autoreleaseNoPage method code implementation:
Id *autoreleaseNoPage(id obj) {// No page indicates that there is No release pool yet, or that there is an empty placeholder pool, but the object assert(! hotPage()); bool pushExtraBoundary =false;
if(haveEmptyPoolPlaceholder()) {// If it is an empty placeholder pool, add a free pool boundary to pushExtraBoundary =true;
}
else if(obj ! = POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no poolin place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
returnnil; } // If POOL_BOUNDARY is passed, the empty pool placeholder is setelse if(obj == POOL_BOUNDARY && ! DebugPoolAllocation) {return setEmptyPoolPlaceholder(); } // We are pushing an object or a non-placeholder D pool. Set hotPage AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);setHotPage(page); // Insert the release pool boundaryif(pushExtraBoundary) { page->add(POOL_BOUNDARY); } // Objects are added to the release poolreturn page->add(obj);
}
Copy the code
AutoreleaseNoPage is only called when the autoreleaseNoPage has no page. The main logic is:
- If the current auto release pool is pushed into a sentry
POOL_BOUNDARY
When willEmptyPoolPlaceholder
Store in TLS. - If TLS is stored
EmptyPoolPlaceholder
When creating wellpage
After that, one will be pushed in firstPOOL_BOUNDARY
, and then push in the objects that join the auto-release pool.
5.5 objc_autoreleasePoolPop
When the scope of the automatic release pool ends, objc_autoreleasePoolPop is called torelease the objects in the automatic release pool.
static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; // If it is an empty pool placeholder, empty the entire autofree poolif (token == (void*)EMPTY_POOL_PLACEHOLDER) {
ifPop pop(coldPage()->begin()); }else{// Unused release pool, empty hotPage stored in TLSsetHotPage(nil);
}
return;
}
page = pageForPointer(token);
stop = (id *)token;
if(*stop ! = POOL_BOUNDARY) {coldPage()->begin();if(stop == page->begin() && ! page->parent) { }else{// if POOL_BOUNDARY is not coldPage()->begin(returnbadPop(token); }}if (PrintPoolHiwat) printHiwat(); // release all objects after stop page->releaseUntil(stop); // Clear subsequent node pagesif(page->child) {// If the current page is not half full, all subsequent pages are killedif (page->lessThanHalfFull()) {
page->child->kill(a); } // If the current page reaches more than half full, keep the next pageelse if (page->child->child) {
page->child->child->kill(a); }}}Copy the code
The above code logic:
- Check if the input parameter is an empty pool placeholder
EMPTY_POOL_PLACEHOLDER
If yes, continue to determine whetherhotPage
Exists, ifhotPage
Existence changes the endpoint of release tocoldPage()->begin()
If thehotPage
If the TLS storage does not exist, the TLS storage is emptyhotPage
. - check
stop
neitherPOOL_BOUNDARY
Is notcoldPage()->begin()
The case will report an error. - Clear the automatic release pool
stop
All objects after that. - Determine the current
page
If it is not half full, all subsequent pages are killed, if it is more than half full, only the next page is keptpage
.
5.6 the add and releaseUntil
The entire AutoreleasePoolPage is a stack, and the AutoreleasePoolPage method adds objects to the automatic release pool:
id *add(id obj) { assert(! full()); unprotect(); id *ret = next; // copy pointer *next++ = obj; // Next moves back to the next empty address protect() after storing obj to the memory address pointed to by next;return ret;
}
Copy the code
The logic is very simple: the add method stores the newly added object to the address pointed to by the pointer next at the top of the stack, and then points to the next location.
AutoreleasePoolPage unstack implementation
Void releaseUntil(id *stop) {// If next and stop do not point to the same memory address, the execution continueswhile(this->next ! = stop) { AutoreleasePoolPage *page = hotPage(); // If hotPage is empty, set the previous page to hotPagewhile (page->empty()) {
page = page->parent;
setHotPage(page); } page->unprotect(); id obj = *--page->next; Memset ((void*)page->next, SCRIBBLE, sizeof(*page->next)); // Clear page->protect();if(obj ! = POOL_BOUNDARY) { objc_release(obj); // top element reference count - 1}}setHotPage(this);
}
Copy the code
The code looks like this:
- Determine the top of the stack pointer
next
andstop
If it is not to the same memory address, continue to unstack. - Determine the current
page
If it is cleared, it proceeds to clean up the previous listpage
. - When out of the stack, the top pointer moves down to clear the top memory.
- If the current stack is not
POOL_BOUNDARY
The callobjc_release
Reference count – 1.
experience
By reading the objC4 source code, I was able to connect my previous ARC knowledge with a better understanding of how the details work.
If you think this article is good, you can go to the original article “Understanding ARC implementation principles” to ✨.
reference
- Objective-c advanced programming