I will not say more about weak and will directly start our source code analysis tour
__weak
id __week obj1 = obj;Copy the code
Compiler simulation code
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);Copy the code
The objc_storeWeak function takes the address of the assignment object of the second argument as the key value and registers the address of the __weak variable of the first argument into the weak table. If the second argument is 0, the address of the variable is removed from the weak table. The realization of the initWeak
id objc_initWeak(id *object, id value)
{
*object = 0;
return objc_storeWeak(object, value);
}Copy the code
StoreWeak is an open source part of Objective-C so let’s see how storeWeak is actually implemented, okay
objc_storeWeak
StoreWeak source official English notes quite full, you can directly understand ~
// Clean up old value, if any.
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (HaveNew) {
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
(id)newObj, location,
CrashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
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.
}Copy the code
Let’s look at the implementation of storeWeak to get oldObj/newObj
if (HaveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}Copy the code
The weak pointer points to the old object:
oldObj = *location;Copy the code
Then we get the SideTable object associated with the old and new objects:
oldTable = &SideTables()[oldObj];
newTable = &SideTables()[newObj];Copy the code
&SideTables()[oldObj] StripedMap implements a class that overloads the [] operator (c++: see me everywhere 233)
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}Copy the code
The following is to remove the pointing information from the old object’s weak table and establish the association information in the new object’s weak table:
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (HaveNew) {
newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
// weak_register_no_lock returns NULL if weak store should be rejected
}Copy the code
Next let the weak reference pointer point to the new object:
*location = newObj;Copy the code
This new object is returned:
return newObj;Copy the code
Above, we can find that weak management is actually related to weak_table. Next, we will analyze weak_table!
weakTable
Close the source code or summarize… I’ve been thinking a lot… …)
- Weak table is a weak reference table, implemented as a Weak_table_t structure, which stores all weak reference information related to all objects
- 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.
- Referrers in Weak_entry_t stores all variables pointing to the weak object
Here’s a picture to get a feel for it:
Let’s begin our analysis of these structures:
- SideTable is a C++ class that is defined in nsobject.mm
class SideTable { private: static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE]; public: RefcountMap refcnts;// Reference count table weak_table_t weak_table;// Weak reference table. }Copy the code
The weak table is structurally defined as:
/** * The global weak references table. Stores object ids as keys, * and weak_entry_t structs as their values. */
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};Copy the code
Weak_entry_t = weak_entry_t; weak_entry_t = weak_entry_t; weak_entry_t = weak_entry_t; weak_entry_t = weak_entry_t; weak_entry_t = weak_entry_t;
Let’s look at the structure of Weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line : 1;
uintptr_t num_refs : PTR_MINUS_1;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};Copy the code
Referrers are all variables that refer to the weak object referent is an in-memory weak object
Now what conclusions can we draw
- Weak reference variables in OC are managed using the weak table (Hash table)
- Weak_entries in the weak table manage variables pointing to weak objects
The weak object was released
What does the program do when it frees an object, while discarding an object that no one is holding?
- objc_release
- Because the reference count is 0, dealloc is executed
- objc rootDealloc
- objc dispose
- objc destructInstance
- objc clear dealloctaing
The objc_clear_deallocating function, the last one called when the object was abandoned, does the following: (1) Retrieving the address of the abandoned object as a key from the weak table. (2) Delete from the weak table all addresses contained in the record with the __weak modifier variable assigned to nil (3) delete the record from the weak table. (4) Delete the address of the discarded object from the reference count table.
It can be seen from this that if used in large quantities, there will beWeak modifier, which consumes the corresponding CPU resources. A good strategy is to use it only when you need to avoid circular referencesWeak modifier.
Release objects immediately
{
id __weak obj = [[NSObject alloc] init];
}Copy the code
Because the source code assigns a generated and held object to a variable with an __weak modifier, it cannot hold the object itself, which is released and deprecated.
Using a variable with an __weak modifier is using an object registered in Autoreleasepool
{
id __weak obj1 = obj;
NSLog(@"% @",obj1);
}Copy the code
Code that the compiler emulates
id obj1;
objc_initWeak(&obj,obj);
id tmp = objc_loadWeakRetained(&obj);
objc_autorelease(tmp);
NSLog(@"% @", tmp);
objc_destoryWeak(&obj1);Copy the code
Notice these two lines of code
id tmp = objc_loadWeakRetained(&obj);
objc_autorelease(tmp);Copy the code
Compared to assignment, we added calls to objc_loadWeakRetained and objc_autorelease with an __weak modifier attached. Retained (1) The objc_loadWeakRetained function retrieves the object referenced with the __weak modifier variable and retains (2) the objc_autorelease function registers the object with Autoreleasepool.
Note: Each time you use the weak modifier variable, the object referenced by the variable is registered with autoReleasepool. To avoid this, assign a variable with the weak modifier to a variable with the __strong modifier and use it again.
id __weak o = obj;
id tmp = o;Copy the code
allowWeakReference/retainWeakReference
When allowsWeakReference/retainWeakReference instance methods (with NO written NSObject interface documentation) to return to the NO.
- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;Copy the code
If the allowsWeakReference method returns NO when assigning a variable to the __weak modifier, the program terminates abnormally. When the object is retained, if the retainWeakReference method returns NO, this variable will be used nil
Specific source code analysis
The above about weak
- weak_register_no_lock
- weak_unregister_no_lock
- . Many implementations are missing… I posted the code I read with comments… Those interested can take a look at the implementation… Feel the charm of source code implementation
Open source part of ObjC
Objc_object **referrer
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;Copy the code
This is understood in conjunction with the remove_referrer function
remove_referrer
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return; }}Copy the code
So we’re going to get the referrer and we’re going to compare that value to the pointer in the entry list, and if it’s found, it’s nil and you might have confused TaggedPoint in the source code
(The pointer made me dizzy… Admire C/C ++ system engineers) (Reading the source code is really an interesting thing lol)
#include "objc-private.h"
#include "objc-weak.h"
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include <libkern/OSAtomic.h>
#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer);
BREAKPOINT_FUNCTION(
void objc_weak_error(void));/** * Unique hash function for object pointers only. * * @param key The object pointer * * @return Size unrestricted hash of pointer. */
static inline uintptr_t hash_pointer(objc_object *key) {
return ptr_hash((uintptr_t)key);
}
/** * Unique hash function for weak object pointers only. * * @param key The weak object pointer. * * @return Size unrestricted hash of pointer. */
static inline uintptr_t w_hash_pointer(objc_object **key) {
return ptr_hash((uintptr_t)key);
}
/** * Grow the entry's hash table of referrers. Rehashes each * of the referrers. * * @param entry Weak pointer hash set for a particular object. */
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry,
objc_object **new_referrer)
{
assert(entry->out_of_line);
size_t old_size = TABLE_SIZE(entry);
size_t new_size = old_size ? old_size * 2 : 8;
size_t num_refs = entry->num_refs;
weak_referrer_t *old_refs = entry->referrers;
entry->mask = new_size - 1;
entry->referrers = (weak_referrer_t *)
calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
entry->num_refs = 0;
entry->max_hash_displacement = 0;
for (size_t i = 0; i < old_size && num_refs > 0; i++) {
if (old_refs[i] != nil) {
append_referrer(entry, old_refs[i]);
num_refs--;
}
}
// Insert
append_referrer(entry, new_referrer);
if (old_refs) free(old_refs);
}
/** * Add the given referrer to set of weak pointers in this entry. * Does not perform duplicate checking (b/c weak pointers are never * added to a set twice). * * @param entry The entry holding the set of weak pointers. * @param new_referrer The new weak pointer to be added. */
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
// if is Array implementation
if (! entry->out_of_line) {
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return; }}// Couldn't insert inline. Allocate out of line.
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line = 1;
entry->mask = WEAK_INLINE_COUNT- 1;
entry->max_hash_displacement = 0;
}
assert(entry->out_of_line);
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
//find a place to insert ref
//weak_entry_remove() may bzero() some place
size_t index = w_hash_pointer(new_referrer) & (entry->mask);
size_t hash_displacement = 0;
while(entry->referrers[index] ! =NULL) {
index = (index+1) & entry->mask;
hash_displacement++;
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
/** * Remove old_referrer from set of referrers, if it's present. * Does not remove duplicates, because duplicates should not exist. * * @todo this is slow if old_referrer is not present. Is this ever the case? * * @param entry The entry holding the referrers. * @param old_referrer The referrer to remove. */
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line) {
for (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 index = w_hash_pointer(old_referrer) & (entry->mask);
size_t hash_displacement = 0;
while(entry->referrers[index] ! = old_referrer) { index = (index+1) & entry->mask;
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;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}
/** * Add new_entry to the object's table of weak references. * Does not check whether the referent is already in the table. */
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);//mask may keep entry in array
size_t index = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t hash_displacement = 0;
/ / the hash index processing
while(weak_entries[index].referent ! = nil) { index = (index+1) & weak_table->mask;
hash_displacement++;
}
weak_entries[index] = *new_entry;
weak_table->num_entries++;
//update max_hash_displacement
if(hash_displacement > weak_table->max_hash_displacement) { weak_table->max_hash_displacement = hash_displacement; }}static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
size_t old_size = TABLE_SIZE(weak_table);
weak_entry_t *old_entries = weak_table->weak_entries;
weak_entry_t *new_entries = (weak_entry_t *)
calloc(new_size, sizeof(weak_entry_t));
weak_table->mask = new_size - 1;
//new
weak_table->weak_entries = new_entries;
weak_table->max_hash_displacement = 0;
weak_table->num_entries = 0; // restored by weak_entry_insert below
//use pointer
if (old_entries) {
weak_entry_t *entry;
weak_entry_t *end = old_entries + old_size;
for (entry = old_entries; entry < end; entry++) {
if(entry->referent) { weak_entry_insert(weak_table, entry); }}free(old_entries); }}// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
// Grow if at least 3/4 full.
if (weak_table->num_entries >= old_size * 3 / 4) {
weak_resize(weak_table, old_size ? old_size*2 : 64); }}// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
// Shrink if larger than 1024 buckets and at most 1/16 full.
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full}}/** * Remove entry from the zone's table of weak references. */
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
if (entry->out_of_line) free(entry->referrers);
The bzero() function places n zeros in the region pointed to by s.
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
//maybe resize weak_table
weak_compact_maybe(weak_table);
}
/** * Return the weak reference table entry for the given referent. * If there is no entry for referent, return NULL. * Performs a lookup. * * @param weak_table * @param referent The object. Must not be nil. * * @return The table of weak referrers to this object. */
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)return nil;
size_t index = hash_pointer(referent) & weak_table->mask;
size_t hash_displacement = 0;
while(weak_table->weak_entries[index].referent ! = referent) { index = (index+1) & weak_table->mask;
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
returnnil; }}return &weak_table->weak_entries[index];
}
/** * Unregister an already-registered weak reference. * This is used when referrer's storage is about to go away, but referent * isn't dead yet. (Otherwise, zeroing referrer later would be a * bad memory access.) * Does nothing if referent/referrer is not a currently active weak reference. * Does not zero referrer. * * FIXME currently requires old referent value to be passed in (lame) * FIXME unregistration should be automatic if referrer is collected * * @param weak_table The global weak table. * @param referent The object. * @param referrer The weak reference. */
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if(! referent)return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
bool empty = true;
//after unregister the entry's referrers is empty?
// Hash implementation
if(entry->out_of_line && entry->num_refs ! =0) {
empty = false;
}
// Array implementation
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break; }}}// if entry.references empty
if(empty) { weak_entry_remove(weak_table, entry); }}// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
/** * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param weak_table The global weak table. * @param referent The object pointed to by the weak reference. * @param referrer The weak pointer address. */
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
//object
objc_object *referent = (objc_object *)referent_id;
//The Point which point the object
objc_object **referrer = (objc_object **)referrer_id;
if(! referent || referent->isTaggedPointer())return referent_id;
// ensure that the referenced object is viable
// judge is Allows Weak Reference
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) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
if (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; }}// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry;
new_entry.referent = referent;
new_entry.out_of_line = 0;
new_entry.inline_referrers[0] = referrer;
for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
new_entry.inline_referrers[i] = nil;
}
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
#if DEBUG
bool
weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id)
{
return weak_entry_for_referent(weak_table, (objc_object *)referent_id);
}
#endif
/** * Called by dealloc; nils out all weak pointers that point to the * provided object so that they can no longer be used. * * @param weak_table * @param referent The object being deallocated. */
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
//referent objc
objc_object *referent = (objc_object *)referent_id;
//referent objc entry(which save many referents)
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;
}
//entry->referrers all nil
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);
}
/** * This function gets called when the value of a weak pointer is being * used in an expression. Called by objc_loadWeakRetained() which is * ultimately called by objc_loadWeak(). The objective is to assert that * there is in fact a weak pointer(s) entry for this particular object being * stored in the weak-table, and to retain that object so it is not deallocated * during the weak pointer's usage. * * @param weak_table * @param referrer The weak pointer address. */
/* Once upon a time we eagerly cleared *referrer if we saw the referent was deallocating. This confuses code like NSPointerFunctions which tries to pre-flight the raw storage and assumes if the storage is zero then the weak system is done interfering. That is false: the weak system is still going to check and clear the storage later. This can cause objc_weak_error complaints and crashes. So we now don't touch the storage until deallocation completes. */
id
weak_read_no_lock(weak_table_t *weak_table, id *referrer_id)
{
objc_object **referrer = (objc_object **)referrer_id;
objc_object *referent = *referrer;
//Detection Tagged Pointer
if (referent->isTaggedPointer()) return (id)referent;
weak_entry_t *entry;
// referent == nil or entry == nil
if(referent == nil || ! (entry = weak_entry_for_referent(weak_table, referent))) {return nil;
}
//Custom RR denotes a custom retain-release implementation
//
if (! referent->ISA()->hasCustomRR()) {
/ /??? question
if (! referent->rootTryRetain()) {
returnnil; }}//has isa
else {
BOOL (*tryRetain)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_retainWeakReference);
//IMP ! = _objc_magForward
if ((IMP)tryRetain == _objc_msgForward) {
return nil;
}
//IMP ! = nil
if (! (*tryRetain)(referent, SEL_retainWeakReference)) {
returnnil; }}return (id)referent;
}Copy the code
“Gold digging Technology Essay” juejin.cn/post/684490…