Implementation of Weak property in ObjC Runtime (middle)


In the last article, a simple analysis of how Weak attributes are stored, captured and destroyed, where the SideTable structure is handled as a black box. This paper attempts to analyze the structure of SideTable.


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 {slock.lock (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);};

The SideTable is divided into 3 main parts

  • The global hash table referenced by weak_table_t: weak
  • RefcountMap: the hash table of reference counts
  • Slock: guarantees atomic operations of spin locks

In the static, ID, storeWeak (ID, *location, objc_object, *newObj) methods, there are

New value / / Assign, 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 is-weakly-referenced bit in refcount / / Set table. if (newObj & & newObj-> isTaggedPointer! ((setWeaklyReferenced_nolock)) {newObj->}); / / Do not set *location anywhere else. That would introduce a race. * location (ID) = newObj;}

For the preservation of weak reference variables, the main thing is to look at the weak_table attribute

Test code

#import < Foundation/Foundation.h> @interface; WeakProperty: NSObject @property (nonatomic, weak) NSObject *obj @property (nonatomic, weak); NSObject *obj2; @property (nonatomic, weak) NSObject *obj3 @property (nonatomic, weak); NSObject *obj4; @property (nonatomic, weak) NSObject *obj5; @end @implementation WeakProperty (void dealloc) {NSLog (@ "%s", __func__);} @end int main (int argc, const char * argv[] @autoreleasepool) {{WeakProperty *property = [[WeakProperty alloc] init]; NSObject *obj = [[NSObject alloc] init]; property.obj = obj; NSLog (@ "% @", property.obj); / / [1] / / [2] property.obj2 = obj; property.obj3 = obj property.obj4 = obj; Property.obj5 = obj; / / [3] property.obj = nil;} return 0;}

Structure: weak_table_t

The definition of weak_table_t is in objc-weak.h

Global weak references table. / * * * The Stores object IDs as keys, and weak_entry_t structs as their * values. * struct weak_table_t *weak_entries size_t {weak_entry_t; num_entries; uintptr_t mask; uintptr_t max_hash_displacement;};


  • A pointer to weak_entry_t
  • Size_t (or unsigned) type num_entries, used to describe the length of a hash table
  • Uintptr_t (that is, unsigned long) type of mask (mask)
  • Uintptr_t (that is, unsigned long) type of max_hash_displacement

Structure: weak_entry_t

Struct weak_entry_t {DisguisedPtr< objc_object> referent; union {struct {weak_referrer_t *referrers uintptr_t; out_of_line_ness: 2; uintptr_t: PTR_MINUS_2; num_refs uintptr_t mask; uintptr_t max_hash_displacement;}; struct field is low {/ / out_of_line_ness bits of inline_referrers[1] weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];};}; bool (out_of_line) return (out_of_line_ness = = {REFERRERS_OUT_OF_LINE}); weak_entry_t& operator= (const weak_entry_t& other) {memcpy (this, & other, sizeof (other)); return *this;} Weak_entry_t (objc_object *newReferent, objc_object **newReferrer): referent (newReferent) {inline_referrers[0] = newReferrer; for (int i = 1; I < WEAK_INLINE_COUNT; i++) {inline_referrers[i] = nil;}}};

In C++, the structure is a data type defined by the keyword struct. His members and base class are default to public (public). The member defined by the keyword class and the base class default to private (private). This is the only difference between the struct and the class in C++.

Class: DisguisedPtr

T> / / DisguisedPtr< acts like pointer type T* except the value is disguised to / / stored hide it from tools like `leaks`. is disguised as itself / / nil so zero-filled memory works as expected means 0x80..00 is also / / which disguised as itself but we don't care. Note that weak_entry_t knows about this / encoding. template < typename T> class DisguisedPtr {uintptr_t value; long static uintptr_t; / / unsigned disguise (T* PTR) {return} - (uintptr_t) ptr; static T* undisguise (uintptr_t VAL) {return (T*) -val public: (DisguisedPtr);}} {DisguisedPtr (T* PTR): value (disguise (PTR) {} (DisguisedPtr) const DisguisedPtr< T> & PTR): value (ptr.value) {} DisguisedPtr< T> & operator (T* = RHS) {value = disguise (RHS); return *this DisguisedPtr<}; T> & operator (const = DisguisedPtr< T> & RHS) {value = rhs.value; return *this;} / / a pointer to the overloaded operator operator T* (const) return undisguise (value) {}; T* operator -> (const) undisguise (value) {return}; T& operator * (const) *undisguise (value) {return}; T& operator [] (size_t I) const return undisguise (value) {[i]}; / / pointer / / because we arithmetic operators omitted don't currently use them anywhere};
The address of a __weak / / variable. / / These pointers are stored disguised so memory analysis tools see lots of Interior / / don't pointers from the weak table into objects. typedef DisguisedPtr< objc_object *> weak_referrer_t;


Weak_entry_t contains a DisguisedPtr< objc_object>, Disguised is “camouflage”, according to the comments that can be DisguisedPtr< T> T * pointer type as can be, in the current scene can be seen as a pointer to a objc_object type

Weak_referrer_t is DisguisedPtr< objc_object *> can be considered as the address of the objc_object pointer

Then there is a union, out_of_line_ness and inline_referrers[1] share a low 2 bit, because

Out_of_line_ness field overlaps with the low / two bits of inline_referrers[1]. inline_referrers[1] is a DisguisedPtr of a / / pointer-aligned / / address. The low two bits of a pointer-aligned DisguisedPtr will always be 0b00 (disguised nil or / 0x80..00) or 0b11 (any other address). Out_of_line_ness is / / Therefore = = 0b10 used to mark the out-of-line state. #define REFERRERS_OUT_OF_LINE 2

Notes DisuisedPtr low 2 0b00 is not 0b11, so that out-of-line can only use out_of_line_ness = = 0b10 (when out_of_line_ness 0b01 or 0b10 false and true respectively)

Num_refs accounts for 30 (32 bit systems) / 62 (64 bit system) bits,
, mask, and max_hash_displacement, as well as in weak_table_t.

The out_of_line () method is already stated above

Weak_entry_t& operator= (const weak_entry_t& other) {{} overloading operator =

The, memcpy (), function, copies, bytes, from, memory, SRC,
, to, memory, area, dest., memory, areas, may, not, overlap.,
, Use, memmove (), if, The,, the, memory, area, areas, N, do, overlap.

From the start position of the memory address referred to in the parameter other, copy the sizeof (other) byte to the starting address of the current object pointed to by the this pointer.

Weak_entry_t (objc_object, *newReferent, objc_object, **newReferrer): referent (newReferent)

The referent (newReferent) is the initialization list, which represents the initialization of the referent attribute in the structure with the parameter newReferent. The inline_referrers[0] receive parameter newReferrer in the union type and sets the remaining 1, 2, and 3 to nil

Function: weak_register_no_lock

Note [2] & [3]

Adds an (object, weak / / / pointer) pair to the weak table. / / / add a (object, weak reference pointer) to weak hash (weak_table_t *weak_table weak_register_no_lock ID table, ID referent, ID *referrer, bool crashIfDeallocating);

Specific implementations are as follows:

A new (object / * * * Registers pair. Creates a, weak pointer) new weak object entry if it does * not exist. * * @param weak_table The global weak table. referent The object pointed * @param to by the weak reference. referrer The weak pointer * @param address. * / 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); objc_object **referrer = (objc_object) referrer_id; if (referent ||! Referent-> isTaggedPointer (return referent_id)); / / ensure that the referenced object is viable bool deallocating; if (referent-> ISA)! (-> hasCustomRR) {(deallocating = refer) Ent-> 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 dealloc Ation., (void*) referent, object_getClassName (referent) (ID));} else {return nil}}; / / 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 (referent, referrer); 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;}
Implementation of Weak property in ObjC Runtime (middle)
Implementation of Weak property in ObjC Runtime (middle)
Implementation of Weak property in ObjC Runtime (middle)

The weak reference property obj is passed to the row argument referent_id in function weak_register_no_lock, assigned to the local variable referent, and location to the parameter referrer_id, assigned to the local variable referrer.

Go through some checks, such as whether to allow weak references and whether weak reference objects are available.

The weak reference table / * * * Return 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. The table of Weak * * @return 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!); begin = hash_pointer (referent) & weak_table-> Mask; size_t index size_t = begin; hash_displacement = 0; while (weak_table-> weak_entries[index].referent! = referent) {index = (index+1) & weak_table-> Ma Sk; if (index = = begin) bad_weak_table (weak_table-> weak_entries hash_displacement++; if (hash_displacement); > weak_table-> max_hash_displacement) {return nil}}; return & weak_table-> weak_entries[index];}

According to referent for key, in weak_table, by traversing the weak_entries array, the referent property value is compared to find the element, not found, and go else

Weak_entry_t (objc_object *newReferent, objc_object **newReferrer): referent (newReferent) {inline_referrers[0] = newReferrer; for (int i = 1; I < WEAK_INLINE_COUNT; i++) {inline_referrers[i] = nil;}}

Perform initialization of the weak_entry_t structure

Implementation of Weak property in ObjC Runtime (middle)

Camouflage pointer by strong rotation operation. Receive newReferrer, that is, referrer is inline_referrers[0], where *referrer is equal to nil, so the inline_referres array elements all point to nil because it is unsigned long integers, so it is 0.

Function: weak_grow_maybe

When the spatial table of the weakly referenced hash table reaches 3/4, the hash table is expanded

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

Function: weak_entry_insert

Add elements to the weak reference hash table

Add new_entry to the object's / * * * table of Weak * Does not check whether the references. referent is already in the table. void weak_entry_insert (weak_table_t / static *weak_table, weak_entry_t *new_entry) {weak_entry_t *weak_entries = weak_table-> weak_entries; assert (weak_entries! = Nil) begin = hash_pointer (new_entry-> size_t; & weak_table-> (referent) mask); size_t index size_t = begin; hash_displacement = 0; while (weak_entries[index].Referent! = Nil) {index = (index+1) & weak_table-> Mask; if (index = = begin) bad_weak_table (weak_entries); hash_displacement++ weak_entries[index];} = *new_entry; weak_table-> num_entries++; if (hash_displaceme NT > weak_table-> max_hash_displacement) {weak_table-> max_hash_displacement = hash_displacement;}

Referent gets new_entry, obj attribute weak references, to the address of the unsigned long integer number of parameters in the opposite, by shifting the operation of hash, and the displacement by weak_table-> (63 mask = 0b111111) mask retained hash after operation was lower in 6 (64 system), as the next index. While (weak_entries[index].referent! = Nil) {…}, hash to solve the collision problem. Then add to the hash table to modify the length of the table

Implementation of Weak property in ObjC Runtime (middle)

As shown above, the static, ID, storeWeak (ID, *location, *newObj, objc_object) location and newObj are saved to the referent of the weak_table_t structure, the first place in the inline_referrers array.

Is the condition that exists to find referent?

While (weak_table-> weak_entries[index].referent! = referent) {index = (index+1) & weak_table-> Mask; if (index = = begin) bad_weak_table (weak_table-> weak_entries); if (hash_displacement++; hash_displacement & gt; weak_table-> max_hash_displacement) {return nil;}}

Note [2] & [3]

After entering the append_referrer function

If (entry-> out_of_line)! (to insert) {/ / Try inline. for (size_t I = 0; I < WEAK_INLINE_COUNT; i++) {if (entry-> inline_referrers[i] = = Nil) {inline_referrers[i] = entry-> new_referrer; return;}}

Because entry-> out_of_line () is equal to false, will try to add to (entry-> inline_referrers array).

Cancel the annotation for [2] because it has reached 4, so it will be expanded at obj5.

Insert inline. Allocate out of / Couldn't 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 fix it and rehash / / will it. for (size_t I = 0; I < WEAK_INLINE_COUNT; i++) {new_referrers[i] = entry-> inline_referrers[i] entry->} referrers; entry-> num_refs = new_referrers; WEAK_INLINE_COUNT = entry-> out_of_line_ness = REFERRERS_OUT_OF_LINE;

Entry-&gt is set; out_of_line_ness is REFERRERS_OUT_OF_LINE

Binding notes

The internal structure stored in / * * * the weak references * It maintains and table. stores a hash set of weak references * pointing to an * object. If out_of_line_ness REFERRERS_OUT_OF_LINE then the! = set * is instead a small inline array..

You know, when the number of references to weak variables is no more than 4, the array is used for storage, and more than 4 are stored in the hash table.

Function: weak_unregister_no_lock

Removes an (object, weak / / / pointer) pair from the weak table. from weak hash / / / remove a table (object, weak reference pointer) void weak_unregister_no_lock (weak_table_t *weak_table, ID referent, ID *referrer);

Specific implementations are as follows:

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) unregistration should be automatic * FIXME if referrer is collected @param weak_table The Global Weak * * * table. @param referent The object. referrer The Weak * @param reference. * / void weak_unregister_no_lock (weak_table_t *weak_table, ID referent_id, ID *referrer_id) {objc_object *referent = (objc_object * r) Eferent_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; 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;}}} {if (empty) weak_entry_remove (weak_table, entry);}} / / Do not set *referrer = nil. objc_storeWeak (requires) that the / / value not change.}

Again, use the weak_entry_for_referent function to find if the weak reference exists

Note [1] & [2]; cancel comment [3]

Old_referrer from set of / * * * Remove referrers, if it's present. not remove * Does duplicates, because duplicates should not exist. this is slow if * * @todo old_referrer is not present. Is this ever the case? Entry The entry holding * * @param the referrers. * @param old_referrer The referrer to remove. void remove_referrer (weak_entry_t / static *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) {inline_referrers[i] = entry-> nil; return; objc_weak_error;}}...) (begin = w_h size_t return;} Ash_pointer (old_referrer) & (entry-> mask); size_t index size_t = begin; hash_displacement = 0; while (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 {return}})...; entry-> referrers[index] = nil; entry-> num_refs--;}
Implementation of Weak property in ObjC Runtime (middle)

When removed, the referrer property is compared to find the same address and set it to nil to remove the effect.

Function: weak_clear_no_lock

Called on object destruction. Sets / / / all remaining weak pointers to nil. / / / in object to call a destructor, set all left a weak reference pointer for the nil (weak_table_t void weak_clear_no_lock *weak_table, ID referent);

Specific implementations are as follows:

By dealloc Nils / * * * Called out all weak pointers; that point to the object so that they * provided 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) {objc_object *referent = (objc_object * referent_id); weak_entry_t (weak_table = weak_entry_for_referent, *entry 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; if (*referrer) {else} _objc_inform ("__weak variable at%p holds%p instead of%p." This "is probably incorrect" objc_storeWeak use of "(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);}

Dealloc will be called, according to the comments and code that same with referent key traversal, then set to nil, but the test, go is if (entry = Nil) and return.


Weak reference search based on the referent attribute, for the first time will be stored in the weak_table_t structure of referent and inline_referrers[0], when added, if the reference number is not greater than 4 is stored in the array inline_referrers, when more than 4 after storage in the form of hash. When removed, remove from inline_referrers from referrer.

Reference resources

  1. Int – What, is, size_t, in, C – – Stack Overflow
  2. Wiki – C++ class
  3. Wiki – bit segment
  4. Linux Programmer’s Manual memcpy
  5. Inline bool objc_object:: isTaggedPointer (); How, does, the, function, work?
  6. Collision attack principle of PHP hash table
  7. Wiki mask
  8. Wiki hash table
  9. When, to, use, reinterpret_cast?
  10. OSObject
  11. The two uses of the operator c++ operator: overloading and implicit type conversions, and string to simple implementation of other basic data types string_cast
  12. The use of colon (and) and double colon (::) in c++
  13. ARC reference count weak