preface

The loading principle of OC Class (below) of iOS underlying principle has analyzed the process of classification loading, as well as the situation of main class and classification with loading. This paper will analyze the special classification – class extension and access of classification attributes – associated objects.

The preparatory work

  • Objc4-818.2 – the source code.

A:list_array_ttData structure analysis

Before we get started on today’s main topic, just a little bit.

Based on the previous analysis, we know that the underlying data structure of the method list is method_array_t. Let’s explore this data structure and the origin of the functions used in LLDB debugging.

Method_array_t inherits from the template class list_array_tt.

// Element = method_t, List = method_list_t, Ptr = method_list_t_authed_ptr
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
    struct array_t {
        uint32_t count;
        Ptr<List> lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize(a) {
            return byteSize(count); }}; .public:
    union { / / the mutex
        Ptr<List> list;
        uintptr_t arrayAndFlag;
    };

    bool hasArray(a) const {
        return arrayAndFlag & 1;
    }

    array_t *array(a) const {
        return (array_t *)(arrayAndFlag & ~1);
    }

    void setArray(array_t *array) {
        arrayAndFlag = (uintptr_t)array | 1; }...uint32_t count(a) const {
        uint32_t result = 0;
        for (auto lists = beginLists(), end = endLists(a); lists ! = end; ++lists) { result += (*lists)->count; }returnresult; }...const Ptr<List>* beginLists(a) const {
        if (hasArray()) {
            return array()->lists;
        } else {
            return&list; }}const Ptr<List>* endLists(a) const {
        if (hasArray()) {
            return array()->lists + array()->count;
        } else if (list) {
            return &list + 1;
        } else {
            return&list; }}// attachLists function is also here, omitted here, interested friends can look at the article
    void attachLists(List* const * addedLists, uint32_t addedCount) {... }Copy the code
  • list_array_ttClasses can be generated by passing in templatesmethod_array_t.property_array_t.protocol_array_tAnd so on.

The template Element = method_t, List = method_list_t, and Ptr = method_list_t_authed_ptr.

Method_list_t inherits from entsize_list_tt and can generate method_list_t, property_list_t, protocol_list_t, etc., depending on the template passed in.

  • According to theget(i)Function to obtainiThe location of themethod_t *.

Method_t ::pointer_modifier implementation.

struct method_t {.struct pointer_modifier {
        template <typename ListType>
        static method_t *modify(const ListType &list, method_t *ptr) {
            if (list.flags() & smallMethodListFlag)
                return (method_t((*)uintptr_t)ptr | 1);
            returnptr; }}; . }Copy the code

PointerModifierNop implementation.

struct PointerModifierNop {
    template <typename ListType, typename T>
    static T *modify(__unused const ListType &list, T *ptr) { returnptr; }};Copy the code

Let’s look at the data structure for method_T.

struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;

    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };

private:
    // Arm64 architecture (including M1 iMac), small end mode
    bool isSmall(a) const {
        return ((uintptr_t)this & 1) = =1;
    }
    // small stores three relative offsets
    struct small {
        RelativePointer<const void *> name;
        RelativePointer<const char *> types;
        RelativePointer<IMP> imp;
        
        bool inSharedCache(a) const {
            return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
                    objc::inSharedCache((uintptr_t)this)); }};// Get small information
    small &small(a) const {
        ASSERT(isSmall());
        return *(struct small *)((uintptr_t)this& ~ (uintptr_t)1); }...// Get big information
    big &big(a) const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }
    
    SEL name(a) const {
        if (isSmall()) {
            return (small().inSharedCache()? (SEL)small().name.get()
                    : *(SEL *)small().name.get());
        } else {
            return big().name; }}... }Copy the code
  • method_tIt has the ones we use all the timebig().small().name()Etc. Function.

The list_array_tt data structure and the functions we use to debug the LLDB are described here.

Two: class extension

1.1: category VS extension

category(Category/Category)

  • Used specifically to add new methods to a class.
  • You cannot add a member variable to a class, and even if you do, you cannot fetch it.
  • Can be achieved byruntimeAdd attributes to categories.
  • In the classification@propertyDefined properties, only properties will be generatedgetter, setterMethod declaration that does not generate method implementations and underlined member variables.

extension(Class extension)

  • It’s called a special category, also known as an anonymous category.
  • You can add member variables and attributes to a class, but they are private.
  • You can add methods to a class, which are also private.

1.2: extensionformat

1.2.1: .mIn the file

For us iOS developers, the most common extension is defined in a.m file:

⚠️ Extension must be written after the class declaration and before the implementation. The declaration part of the.h file is also expanded into the.m file at compile time.

1.2.2: Create files separately

You can also create a separate extension file:

This will only generate a.h file, because the implementation of the extension is also required in the.m file of the class, and the.h file must be imported into the.m file of the class, otherwise the class will not be able to access the properties and member variables defined in extension (and will not generate getters and setters for the properties). The extension will not be incorporated into the class’s RO when compiled (the method is implemented in the class, so it does not matter if it is not imported).

This is just a separate header for extension from the.m file.

1.3: extensionUnderlying principle analysis

Create the XJBoy class in main.m and add Extension.

@interface XJBoy : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) int age;

- (void)instanceMethod;

+ (void)classMethod;

@end

@interface XJBoy (a)

@property (nonatomic, copy) NSString *ext_name;

@property (nonatomic, assign) int ext_age;

- (void)ext_instanceMethod;

+ (void)ext_classMethod;

@end

@implementation XJBoy

- (void)instanceMethod{
    NSLog(@"%s",__func__);
}

+ (void)classMethod{
    NSLog(@"%s",__func__);
}

- (void)ext_instanceMethod{
    NSLog(@"%s",__func__);
}

+ (void)ext_classMethod{
    NSLog(@"%s",__func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        XJPerson * person = [XJPerson alloc];
        [person saySomething];
    }
    return 0;
}
Copy the code

Explore using clang to generate main. CPP files with main.m.

XJBoy class implementation:

XJBoy member variables, attributes, methods list:

You can see that the extension data is stored in the class at compile time.

Add +load to XJBoy and add breakpoint debugging to realizeClassWithoutSwift:

From the debugging results, you can see that the extension data is also in the RO, which is determined at compile time.

  • Category affects class loading because it has its own.m file and can implement its own +load method.

  • Extension does not affect the loading of the class because it does not have its own.m file, and no matter how many extensions there are, all implementations are in the.m file of the class.

Three: Associated objects

After adding attributes to a category, the corresponding warning will be reported:

  • Prompts developers to implement attributes themselvesgetter & setterMethods.

This is because classes that declare properties with @property only generate declarations of getter & setter methods, not the implementation of the method and underlined (_) member variables. Getter & setter methods store and value a member variable by its offset value. There is no member variable, so there is no implementation of getter & setter methods.

In this case, you need to add getter & setter methods to the class properties via the associated object.

- (void)setXja_name:(NSString *)xja_name
{
    objc_setAssociatedObject(self, "xja_name", xja_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)xja_name
{
    return objc_getAssociatedObject(self, "xja_name");
}
Copy the code

3.1: Associated objectssetter

3.1.1: objc_setAssociatedObject

Objc_setAssociatedObject objc_setAssociatedObject

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

In objC4-781 and earlier versions, the _base_objc_setAssociatedObject function is obtained by setassocook.get () and then the _object_set_associative_reference function is called.

3.1.2: _object_set_associative_reference

// The associated object store self xja_name value OBJC_ASSOCIATION_COPY_NONATOMIC
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    if(! object && ! value)return;
    // Whether to disable associated objects
    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
    // Package object into the unified type DisguisedPtr
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // Wrap {policy, value} to ObjcAssociation
    ObjcAssociation association{policy, value};

    // Operate on value according to policy
    // retain or copy value
    association.acquireValue(a);bool isFirstAssociation = false;
    {
        // The manager is not a singleton. The constructor is called to create the manager, which is locked internally
        AssociationsManager manager;
        // AssociationsHashMap is a singleton. The AssociationsHashMap is obtained from the AssociationsManager
        // It is initialized at 'map_images'.
        AssociationsHashMap &associations(manager.get());

        if (value) {/ / a value
            // Find or create an iterator in the global associated object hash table for inserting object
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {// Second is true the first time the bucket is inserted
                /* it's the first association we make */
                isFirstAssociation = true;
            }
            /* establish or replace the association */
            // Get the hash table associated with 'object'
            auto &refs = refs_result.first->second;
            // Insert association into the object table
            // Insert without value, insert without value
            auto result = refs.try_emplace(key, std::move(association));
            if(! result.second) {// If second is false, LookupBucketFor is found and has a value
                // result.first->second
                // Replace the table with new. Association changes to the old value
                association.swap(result.first->second); }}else {// There is no value.
            // Find the iterator of the hash table associated with object
            auto refs_it = associations.find(disguised);
            // Not the end tag
            if(refs_it ! = associations.end()) {
                // Get the hash table associated with object
                auto &refs = refs_it->second;
                // Find the association iterator corresponding to the key
                auto it = refs.find(key);
                // Not the end tag
                if(it ! = refs.end()) {
                    // it->second = association
                    // Change table value to nil, association to previous value
                    association.swap(it->second);
                    // Erases the association iterator corresponding to the key
                    refs.erase(it);
                    if (refs.size() = =0) {
                        // If the hash table corresponding to 'object' is empty, erase this table
                        associations.erase(refs_it); }}}}// Out of scope, the destructor of AssociationsManager is called
    // Unlock
    }
    // Only the first time the object's ISA is marked to see if it has an associated object
    if (isFirstAssociation)
        object->setHasAssociatedObjects(a);// Release the old values swapped in 'association'
    association.releaseHeldValue(a); }Copy the code
  • Return the object and value if both are null.

  • Object’s class disallows associated objects.

  • The object is uniformly wrapped as the DisguisedPtr type.

  • Wrap policy and value into object Society, and retain or copy values based on policy.

  • AssociationsManager Creates the AssociationsManager by calling the constructor of the AssociationsManager class. The constructor and destructor do nothing but lock and unlock internally. Manager is not a singleton (more on that later).

  • AssociationsHashMap & Associations (manager.get()) Obtain the total hash table of AssociationsHashMap from the Manager. This table is a singleton that is initialized in the MAP_images process (more on that later).

  • Value:

    • callassociations.try_emplaceFunction to find or create inserts in the global associated object total hash tableobjectIterator corresponding to the hash table of the associated object, returns the iterator and whether the associated object was added for the first timepair, i.e.,refs_result.
    • If the currentobjectInsert into the hash table for the first time,refs_result.secondfortrueThat will beisFirstAssociationMarked astrue.
    • refs_result.first->secondTo obtainobjectThe associated object hash table ofrefs.
    • callrefs.try_emplacewillpolicyandvaluePackaged into theassociationInsert the currentobjectReturns the current hash table of the associated objectobjectThe iterator of the associated object hash table and whether it already existspair, i.e.,result.
    • If it was there before,result.secondforfalseThrough theresult.first->secondGet beforeObjcAssociationClass object, calledassociation.swapThat will bepolicyandvalueThe new values are stored and the old values are swapped out in preparation for subsequent release.
  • If value is null:

    • callassociations.findFunction looks in the hash table of global associated objectsobjectIterator to the corresponding associated object hash tablerefs_it.
    • The returned iterator is notendFlag, then passrefs_it->secondTo obtainobjectHash table of the corresponding associated objectrefs.
    • callrefs.findinobjectLookup in the hash table of the corresponding associated objectkeyThe correspondingObjcAssociationClass iteratorit.
    • The returned iterator is notendMark, passit->secondGet beforeObjcAssociationClass object, calledassociation.swap, store {policy, nil}, and swap old values toassociationTo prepare for subsequent release.
    • callrefs.eraseeraseobjectThe corresponding associated object in the hash tablekeyThe correspondingObjcAssociationClass iterator.
    • ifobjectIf the hash table of the corresponding associated object is empty, erase the table.
  • Set whether the ISA of object has associated objects according to isFirstAssociation.

  • Release old values swapped in association.

3.1.3: lldbTo viewdisguisedandassociation

3.1.4: acquireValue

    inline void acquireValue(a) {
        if (_value) {
            // 0xFF = 0B 1111 1111
            switch (_policy & 0xFF) {
            // 0B 0000 0001 & 0B 1111 1111 = 0B 0000 0001
            case OBJC_ASSOCIATION_SETTER_RETAIN:
                _value = objc_retain(_value);
                break;
            // 0B 0000 0011 & 0B 1111 1111 = 0B 0000 0011
            case OBJC_ASSOCIATION_SETTER_COPY:
                _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
                break; }}}Copy the code
  • According to thepolicyThe value ofretainorcopyOperation.

3.1.5: AssociationsManagerwithAssociationsHashMap

// The hash table type associated with the object
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
// The total hash table type of the associated object. The two-layer hash table structure contains object and ObjectAssociationMap
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    // Total hash table of associated objects
    // Static variable, declared in AssociationsManager, can only be called by an instance of AssociationsManager class
    // is equivalent to a singleton
    static Storage _mapStorage;

public:
    // constructor, lock
    AssociationsManager()   { AssociationsManagerLock.lock(a); }// destructor to unlock
    ~AssociationsManager()  { AssociationsManagerLock.unlock(a); }// Get the total hash table of the associated object, global singleton
    // _mapStorage is a static variable, equivalent to a singleton
    AssociationsHashMap &get(a) {
        return _mapStorage.get(a); }// static represents the class method, called in the map_images process
    static void init(a) {
        _mapStorage.init();
    }
};

AssociationsManager::Storage AssociationsManager::_mapStorage;
Copy the code
  • _mapStorageIs a static variable declared inAssociationsManagerClass, can onlyAssociationsManagerClass, equivalent to a singleton.
  • AssociationsManagerConstructors and destructors perform add unlock operations.
  • AssociationsHashMapClass associated object total hash table, throughAssociationsManagerOf the classgetMethod.
  • The associated object total hash table is inAssociationsManagerClass method of classinitIs initialized in,initinmap_imagesProcess invocation.

3.1.5.1: AssociationsManagerVerification of structure and destruction imitation

  • Out of scope, the destructor is called.

To validate c++ constructors and destructors, change the.m file to.mm or change the Type of the file to Objc++:

3.1.5.2: AssociationsHashMapInitialization Process

Add a breakpoint to the AssociationsManager init function to view the function call stack:

Initialization process of the total hash table of associated objects:

  • map_images -> map_images_nolock -> arr_init -> _objc_associations_init -> AssociationsManager::init() -> _mapStorage.init().

Map_images:

Map_images_nolock:

Arr_init:

_objc_associations_init:

AssociationsManager: : init () :

3.1.5.3: AssociationsHashMapSingleton validation

Instantiate multiple instances, LLDB output address verification.

  • AssociationsManagerNot a singleton,AssociationsHashMapIs a singleton (global associated object total hash table, all objects associated object hash table exists in).

Validation requires temporary annotation of AssociationsManager class construction and destructor for add unlock operations, otherwise deadlock will occur:

3.1.6: try_emplace

// Backup (backup) Key = backup (object) Args = ObjectAssociationMap{}
Memory hash table key = key Args = association
  template <typename. Ts>std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    // Find the corresponding Bucket according to the key
    // TheBucket pointer is passed, which changes as the value inside changes
    if (LookupBucketFor(Key, TheBucket)) // The system already exists
      // Make the pair with make_pair
      / / second to false
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    // If there is no query to insert data into bucket, return bucket
    // The first of TheBucket is the key, and the second is Args
    // The Args can be obtained by XXX. First ->second
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...) ;// Make the pair with make_pair
    / / second to true
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }
Copy the code
  • try_emplaceinvalueIf there is a value, it will be called twice (the total hash table of the associated object is a two-layer hash table structure) :
    • The first is the global associated object total hash table call,keyisobjectPackaged into theDisguisedPtrClass object,ArgsisDisguisedPtrHash table of associated objects corresponding to class objects.
    • The second,DisguisedPtrClass object corresponding to the associated object hash table call,keyIs associated with the objectkey.ArgsIs wrapped with {policy, value}ObjcAssociationClass objects.
  • callLookupBucketForTo find thekeyThe correspondingTheBucket, the first time is the hash table of the associated object corresponding to the object, and the second time is the associated objectkeyThe correspondingObjcAssociationClass object (all wrapped accordingly).
  • If found, the iterator andfalseA team (make_pairReturn).
  • Call if not foundInsertIntoBucketwillkey & ArgsinsertTheBucketAnd generate iterators withtrueGroup returns (if there is an old value, it is returned without substitution, so that the old value can be released after a later swap).

Try_emplace LLDB debug try_emplace LLDB debug try_emplace LLDB debug try_emplace

Object wrapped into the DisguisedPtr class object corresponding to the associated object hash table calls try_emplace’s LLDB debugging (the second time) :

3.1.6.1: LookupBucketFor

LookupBucketFor has two different parameters in the source code, one with const modifier and one without. The buckett-type TheBucket passed by try_emplace has no const modifier, so let’s look at it first:

It is clear that a BucketT function without const calls a function with const. The BucketT parameter is also passed as a pointer.

  • According to thekeyTo obtainbucketThe address toConstFoundBucketThe pointer.
  • willConstFoundBucketThe address of the pointer assigned to&FoundBucketThe pointer looks like thisFoundBucketThe stored data is updated in real time.
  • Query toResultreturntrue.

Next look at LookupBucketFor with const BucketT type parameter:

  template<typename LookupKeyT>
  // BucketT is decorated with const
  bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    // Get buckets first address
    const BucketT *BucketsPtr = getBuckets(a);// Get buckets
    const unsigned NumBuckets = getNumBuckets(a);// If buckets is 0, return false and FoundBucket = nullptr
    if (NumBuckets == 0) {
      FoundBucket = nullptr;
      return false;
    }

    // FoundTombstone - Keep track of whether we find a tombstone while probing.
    const BucketT *FoundTombstone = nullptr;
    // Get the key of the empty bucket
    const KeyT EmptyKey = getEmptyKey(a);// Get the tombstone key
    const KeyT TombstoneKey = getTombstoneKey(a);assert(! KeyInfoT::isEqual(Val, EmptyKey) && ! KeyInfoT::isEqual(Val, TombstoneKey) &&
           "Empty/Tombstone value shouldn't be inserted into map!");
    // Compute the hash subscript
    // The hash value of key & (capacity -1) is similar to the mask of cache (find index)
    unsigned BucketNo = getHashValue(Val) & (NumBuckets- 1);
    unsigned ProbeAmt = 1;
    // Similar to cache insertion
    while (true) {
      ThisBucket = first address + number of bytes
      // Find the corresponding bucket according to the subscript
      const BucketT *ThisBucket = BucketsPtr + BucketNo;
      // Found Val's bucket? If so, return it.
      // LLVM_LIKELY 就是 fastpath
      // If a bucket is found, the bucket already exists.
      if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
        FoundBucket = ThisBucket;
        return true;
      }

      // If we found an empty bucket, the key doesn't exist in the set.
      // Insert it and return the default value.
      // An empty bucket can be inserted into the empty bucket
      if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
        // If we've already seen a tombstone while probing, fill it in instead 
        // of the empty bucket we eventually probed to.
        // If we already see the tombstone while exploring, please fill it instead of the empty bucket we eventually detected
        FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
        return false;
      }

      // If this is a tombstone, remember it. If Val ends up not in the map, we
      // prefer to return it than something that would require more probing.
      // Ditto for zero values.
      if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) && ! FoundTombstone) FoundTombstone = ThisBucket;// Remember the first tombstone found.
      if (ValueInfoT::isPurgeable(ThisBucket->getSecond() &&! FoundTombstone) FoundTombstone = ThisBucket;// Otherwise, it's a hash collision or a tombstone, continue quadratic
      // probing.
      if (ProbeAmt > NumBuckets) {
        FatalCorruptHashTables(BucketsPtr, NumBuckets);
      }
      BucketNo += ProbeAmt++;
      // compute the subscript
      BucketNo &= (NumBuckets- 1); }}Copy the code
  • The logic and method are cached herecacheVery similar, first of allHash of key & (capacity -1)Compute the hash subscript.
  • And then look it up by hash subscriptbucketIf there is a value, return itbucketPointer and returntrueIf not, return emptybucketorGrave markersAnd returnfalse.
  • Hash conflict, hash again, recalculate the hash index.

LLDB debugging verification:

Set FoundBucket = nullptr, return false, and then go through the InsertIntoBucket process.

The second time, after the initial space expansion or expansion, an empty bucket returns ready to insert data:

3.1.6.2: InsertIntoBucket

If no corresponding bucket is found, the insert process is entered.

  Btemplate <typename KeyArg, typename. ValueArgs>BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key, ValueArgs &&... Values) {
    // Get an empty bucket
    TheBucket =  InsertIntoBucketImpl(Key, Key, TheBucket);
    // the first value of TheBucket is Key and the second value is Values
    XXX. First ->second
    // values is the hash table associated with the object for the first time,
    // The second time is the 'objCassociety' class object wrapped with {policy, value}
    TheBucket->getFirst() = std::forward<KeyArg>(Key); : :new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...) ;return TheBucket;
  }
Copy the code
  • Get emptybucketIf the capacity is not enough, expand it.
  • Set up thebucketThe corresponding value,firstforKey.secondforValues.
    • For the first time,KeyisobjectPackaged into theDisguisedPtrClass object,ValuesisDisguisedPtrHash table of associated objects corresponding to class objects.
    • The second timeKeyIs associated with the objectkey.ValuesIs wrapped with {policy, value}ObjcAssociationClass objects.

LLDB debugging verification:

The first assignment:

The second assignment:

3.1.6.2.1: InsertIntoBucketImpl
  template <typename LookupKeyT>
  BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
                                BucketT *TheBucket) {
    // NewNumEntries indicate that a bucket will be inserted
    unsigned NewNumEntries = getNumEntries() + 1;
    // Get the total number of buckets
    unsigned NumBuckets = getNumBuckets(a);// If the number to be inserted is greater than or equal to 3/4 of the total number, double the capacity
    if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
      this->grow(NumBuckets * 2);// Bucket = 0; // Bucket = 0; // Bucket = 0
      // Retrieve an empty bucket
      LookupBucketFor(Lookup, TheBucket);
      NumBuckets = getNumBuckets(a); }else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
                             NumBuckets/8)) {
      // When the bucket marked with a tombstone reaches 7/8, the new bucket is re-opened and the non-tombstone marks are replaced with the new bucket
      this->grow(NumBuckets);
      LookupBucketFor(Lookup, TheBucket);
    }
    ASSERT(TheBucket);

    if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
      // Replacing an empty bucket.
      // Find an Empty bucket that is occupied by +1 and return that bucket
      // The number of buckets currently occupied + 1
      incrementNumEntries(a); }else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
      
      // Replacing a tombstone.
      // Find a bucket with a tombstone marker, the number of occupied +1, tombstone marker -1, and return the bucket
      incrementNumEntries(a);decrementNumTombstones(a); }else {
      // we should be purging a zero. No accounting changes.
      ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
      TheBucket->getSecond(). ~ValueT(a); }return TheBucket;
  }
Copy the code
  • Call in case of insufficient capacitygrowFunction to open or expand, the first open capacity is4, capacity expansion can be divided into two situations:
    • Load factor up toThree quarters ofWhen,twoTimes the capacity.
    • Occupied plus the number of tombstone markers7/8, re-open the space and replace the non-tombstone markers with new onesbucketIn the.
  • callLookupBucketForTo find thebucketTo obtain theempty bucketThe number of cases occupied+ 1, return thisbucket.A bucket marked with a tombstoneThe number of cases occupied+ 1, the number of tombstone markers- 1, return thisbucket.

LLDB debugging verification:

3.1.6.2.2: grow

When running out of space, space creation and expansion enter the grow function.

Start with the grow function of the DenseMapBase class.

void grow(unsigned AtLeast) {
    static_cast<DerivedT *>(this) - >grow(AtLeast);
}
Copy the code

Debugging continues with the DenseMap class’s grow function.

  void grow(unsigned AtLeast) {
    unsigned OldNumBuckets = NumBuckets;
    BucketT *OldBuckets = Buckets;
    // #define MIN_BUCKETS 4
    allocateBuckets(std::max<unsigned>(MIN_BUCKETS, static_cast<unsigned> (NextPowerOf2(AtLeast- 1))));
    ASSERT(Buckets);
    if(! OldBuckets) {this->BaseT::initEmpty(a);return;
    }

    this->moveFromOldBuckets(OldBuckets, OldBuckets+OldNumBuckets);

    // Free the old table.
    operator delete(OldBuckets);
  } 
Copy the code
  • throughmaxFunction to takeMIN_BUCKETSandNextPowerOf2(AtLeast-1)As the maximum value ofbucketThe number of.MIN_BUCKETSfor4The first development is4.
  • callallocateBucketsFunction opens the number calculated in the previous stepbucketType of memory space.
  • OldBucketsIf it is empty, the traversal will bebucketAll initialized to nullbucket(empty bucketthefirstfor1) and return.
  • callmoveFromOldBucketsThe function will be oldbucketsMove to newbucketsPhi, this sumcacheExpansion is different.
  • The release of the oldbuckets.
3.1.6.2.3: NextPowerOf2
/ / 32 bits
NextPowerOf2 - returns the NextPowerOf2 (32 bits)
/// strictly greater than A. Returns zero on overflow.
inline uint32_t NextPowerOf2(uint32_t A) {
  A |= (A >> 1);
  A |= (A >> 2);
  A |= (A >> 4);
  A |= (A >> 8);
  A |= (A >> 16);
  return A + 1;
}
/ / 64
NextPowerOf2 - returns the NextPowerOf2 (64 bits)
/// strictly greater than A. Returns zero on overflow.
inline uint64_t NextPowerOf2(uint64_t A) {
  A |= (A >> 1);
  A |= (A >> 2);
  A |= (A >> 4);
  A |= (A >> 8);
  A |= (A >> 16);
  A |= (A >> 32);
  return A + 1;
}
Copy the code
  • AIs an unsigned integer, first passed in- 1.- 1Converted to1The binary rules are:1The original code is0b0000000000000001Covariates,0b1111111111111110, add1is0b1111111111111111In order not to be so long, only to16Bit representation), then proceedA |= (A >> n)The operation, it turns out, is still equal to0b1111111111111111And thenA+1The overflow is equal to the0.
  • The second one that came in was7Because the first open for4), binary is0b0000000000000111,A |= (A >> n)Computation, better still0b0000000000000111And thenA+1Is equal to the8.

LLDB debugging verification:

3.1.6.2.4: initEmpty

Debugging continues into initEmpty:

 void initEmpty(a) {
    setNumEntries(0);
    setNumTombstones(0);

    ASSERT((getNumBuckets() & (getNumBuckets(a)- 1)) = =0 &&
           "# initial buckets must be a power of two!");
    // Set an empty key
    const KeyT EmptyKey = getEmptyKey(a);for (BucketT *B = getBuckets(), *E = getBucketsEnd(a); B ! = E; ++B) ::new (&B->getFirst()) KeyT(EmptyKey);
  }
Copy the code

To continue debugging, enter getEmptyKey:

Start with llvm-densemap. h getEmptyKey:

  static const KeyT getEmptyKey(a) {
    static_assert(std::is_base_of<DenseMapBase, DerivedT>::value,
                  "Must pass the derived type to this template!");
    return KeyInfoT::getEmptyKey(a); }Copy the code

Struct DenseMapInfo getEmptyKey = llvm-densemapinfo. h

  static inline DisguisedPtr<T> getEmptyKey(a) {
    return DisguisedPtr<T>((T*)(uintptr_t)- 1);
  }
Copy the code
  • DisguisedPtrYou should be familiar with this,objectIt’s also encapsulatedDisguisedPtrType.
   template* * (typename T>
   class DisguisedPtr {
    uintptr_t value;
    static uintptr_t disguise(T* ptr) {
        return- (uintptr_t)ptr; }... }Copy the code
  • ptr = (uintptr_t)-1Pass by again-(uintptr_t)ptrCalculation. Is equal to-(uintptr_t)((uintptr_t)-1) = 1, which means emptybucketthefirstis1.
3.1.6.2.5: moveFromOldBuckets
void moveFromOldBuckets(BucketT *OldBucketsBegin, BucketT *OldBucketsEnd) {
    initEmpty(a);// Insert all the old elements.
    const KeyT EmptyKey = getEmptyKey(a);const KeyT TombstoneKey = getTombstoneKey(a);for(BucketT *B = OldBucketsBegin, *E = OldBucketsEnd; B ! = E; ++B) {if (ValueInfoT::isPurgeable(B->getSecond())) {
        // Free the value.
        B->getSecond(). ~ValueT(a); }else if(! KeyInfoT::isEqual(B->getFirst(), EmptyKey) && ! KeyInfoT::isEqual(B->getFirst(), TombstoneKey)) {
        // Insert the key/value into the new table.
        BucketT *DestBucket;
        bool FoundVal = LookupBucketFor(B->getFirst(), DestBucket);
        (void)FoundVal; // silence warning.
        ASSERT(! FoundVal &&"Key already in new map?");
        // If a bucket's first is not EmptyKey && a new bucket is not TombstoneKey
        DestBucket->getFirst() = std::move(B->getFirst()); : :new (&DestBucket->getSecond()) ValueT(std::move(B->getSecond()));
        // Occupied number +1
        incrementNumEntries(a);// Free the value.
        B->getSecond(). ~ValueT(a); } B->getFirst(). ~KeyT();
    }
  }
Copy the code
  • Initialize the first of the new buckets as EmptyKey.

  • To walk through the OldBucket in OldBuckets:

    • OldBucketthesecondCan befree:
      • directlyfree.
    • Do notfree:
      • OldBucketthefirstDon’t forEmptyKeyAnd not forTombstoneKey, then insert the new onebucketsIn the.
      • Occupied quantity+ 1.
      • free OldBucketthesecond.
  • Free OldBucket’s first.

3.1.7 setHasAssociatedObjects

associations.try_emplace(disguised, ObjectAssociationMap{}) global associated object total hash table associations Try_emplace function is called to add the corresponding associated object hash table to the DisguisedPtr type object packaged by Object for the first time. The team (make_pair) returns the hash table of the corresponding associated object added and true. IsFirstAssociation is set to true. This is followed by whether the object has an associated object.

.if (isFirstAssociation)
        object->setHasAssociatedObjects()
  ...
Copy the code
inline void
objc_object::setHasAssociatedObjects(a)
{
    //Tagged Pointer returns directly
    if (isTaggedPointer()) return;
    // Pure pointer && has default release, retain, etc. && non-future class && non-metaclass
    if (slowpath(!hasNonpointerIsa() && ISA() - >hasCustomRR() &&!ISA() - >isFuture() &&!ISA() - >isMetaClass()) {
        // Get the _noteAssociatedObjects method
        void(*setAssoc)(id, SEL) = (void(*)(id, SEL)) object_getMethodImplementation((id)thisThe @selector(_noteAssociatedObjects));
        // Do not forward the message, that is, find the method.
        if((IMP)setAssoc ! = _objc_msgForward) {/ / call _noteAssociatedObjects
            (*setAssoc)((id)thisThe @selector(_noteAssociatedObjects)); }}// Set up a new ISA
    isa_t newisa, oldisa = LoadExclusive(&isa.bits);
    do {
        newisa = oldisa;
        // A pure pointer/already has an associated object tag
        if(! newisa.nonpointer || newisa.has_assoc) {ClearExclusive(&isa.bits);
            return;
        }
        // isa associated object flag
        newisa.has_assoc = true;
    } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
}
Copy the code
  • isaPure pointer call_noteAssociatedObjectsFunction, which the system determines whether it has been implemented.
  • isaImpure Pointers willhas_assocSet totrue.

The _noteAssociatedObjects function system is not implemented and should be provided for implementation. But methods that begin with an _ should be private. We don’t normally use it.

3.1.8: Associated object setting process

  1. Wrap Object into the DisguisedPtr class object.

  2. Generate objcasSociety class objects with policies and values, and process (retain or copy) the values in the objcasSociety based on the policy.

  3. Get AssociationsHashMap from AssociationsManager class.

  4. Check that the associated object value to be set exists:

    • 4.1. If yes, go to the insert process
      • To obtainDisguisedPtrHash table of the corresponding associated objectObjectAssociationMapIf not, insert emptyObjectAssociationMapAnd marks the first time an associated object is added to the object (isFirstAssociation).
      • The queryObjectAssociationMapIf therekeyThe correspondingObjcAssociation, if no, insert, if yes, return, and2In theObjcAssociationInteractive data, number one6Step to release.
    • 4.2. Not present, insert null value (insert null value, equivalent to clear)
      • To obtainDisguisedPtrHash table of the corresponding associated objectObjectAssociationMap.
      • To obtainkeyThe correspondingObjcAssociation.
      • Empty values are swapped in (that is, erased) and then erasedObjcAssociation.
      • ifDisguisedPtrThe correspondingObjectAssociationMapIf it’s empty, erase it.
  5. Set whether an object has an associated object based on isFirstAssociation.

  6. Release the old values swapped out in 4.1.

3.2: Associated objectsgetter

The associated object values call objc_getAssociatedObject.

id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}
Copy the code
  • Directly called_object_get_associative_referenceFunction.

3.2.1: _object_get_associative_reference

id
_object_get_associative_reference(id object, const void *key)
{   
    // Create an empty ObjcAssociation object
    ObjcAssociation association{};

    {   
    // AssociationsManager manager calls the constructor to lock
        AssociationsManager manager;
        // Get the hash table of the global associated object
        AssociationsHashMap &associations(manager.get());
        // Get the iterator for the hash table of the associated object
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        // Not the end tag
        if(i ! = associations.end()) {
            // Get the hash table of the associated object
            ObjectAssociationMap &refs = i->second;
            // Get the iterator of the 'objcassociety' class corresponding to the associated object 'key'
            ObjectAssociationMap::iterator j = refs.find(key);
            // Not the end tag
            if(j ! = refs.end()) {
                // Get an ObjcAssociation object
                association = j->second; 
                // Retain value is required according to the policy
                association.retainReturnedValue();
            }
        }
    // Out of scope, the destructor of AssociationsManager is called
    // Unlock
    }
    // Set value to autorelease as required by the policy
    return association.autoreleaseReturnedValue(a); }Copy the code
  • Gets the total hash table of global associated objectsassociations.
  • packagingobjectforDisguisedPtrType objects fromassociationsGets an iterator for the hash table of the associated object corresponding to the object.
  • throughkeyGetting associated ObjectskeyThe correspondingObjcAssociationClass object iterator.
  • fromObjcAssociationType objectvalueTo return.

3.2.2: find

iterator find(const_arg_type_t<KeyT> Val) {
    BucketT *TheBucket;
    if (LookupBucketFor(Val, TheBucket))
      return makeIterator(TheBucket, getBucketsEnd(), true);
    return end(a); }Copy the code
  • callLookupBucketForFunction to find theTheBucket, returns the correlation iterator if it exists, and returns the correlation iterator if it does notendThe tag.

LLDB debugging check:

3.2.3: Associated object value process

  1. According to theAssociationsManagerClass to get the global associated object total hash tableAssociationsHashMap.
  2. Based on the currentobjectencapsulatedDisguisedPtrinAssociationsHashMapTo get the corresponding iterator.
  3. This iterator is notendTag to retrieve the hash table of an associated objectObjectAssociationMap.
  4. According to thekeyinObjectAssociationMapTo get the corresponding iterator.
  5. This iterator is notendTag, extract the correspondingObjcAssociation({policy, value}).
  6. Process according to policy requirementsObjcAssociationIn thevalueAnd then return.

3.3: Associated objectsremove

Apple provides the objc_removeAssociatedObjects function to remove associated objects.

void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object, /*deallocating*/false); }}Copy the code
  • judgeobjectIs there an associated object? If so, call_object_remove_assocationsThe function removes the associated object.

3.3.1: hasAssociatedObjects

Determine whether an object has an associated object:

inline bool
objc_object::hasAssociatedObjects(a)
{
    if (isTaggedPointer()) return true;
    if (isa.nonpointer) return isa.has_assoc;
    return true;
}
Copy the code
  • The default returntrue.

3.3.2 rainfall distribution on 10-12:_object_remove_assocations

void
_object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};

    {
    // AssociationsManager manager calls the AssociationsManager constructor and locks it
        AssociationsManager manager;
        // Get the hash table of the global associated object
        AssociationsHashMap &associations(manager.get());
        // Get the iterator for the hash table of the associated object
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        // Not the end tag
        if(i ! = associations.end()) {
            // Interact the object's associated object hash table data with refs
            refs.swap(i->second);
            
            // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            bool didReInsert = false;
            if(! deallocating) {// The object is not freed
                // Iterate over all iterators of the 'Object Society' class in the hash table of the corresponding associated object
                for (auto &ref: refs) {
                    // ref.second是ObjcAssociation类型对象{policy, value}
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        // Re-insert the associated object of OBJC_ASSOCIATION_SYSTEM_OBJECT
                        i->second.insert(ref);
                        didReInsert = true; }}}if(! didReInsert)// If there is nothing to reinsert, erase the iterator of the corresponding associated object hash table
                associations.erase(i); }}// Associations to be released after the normal ones.
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // release everything (outside of the lock).
    // Iterate over all iterators of the 'Object Society' class in the hash table of the corresponding associated object
    for (auto &i: refs) {
        // i.second是ObjcAssociation类型对象{policy, value}
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don't get released.
            if (deallocating)
                // When dealloc, the associated object of OBJC_ASSOCIATION_SYSTEM_OBJECT will be put into laterRefs and released later. Otherwise, it will not be processed.
                laterRefs.append(&i.second);
        } else {
            // Release non-objc_association_system_object associated objects
            i.second.releaseHeldValue(a); }// Out of scope, call AssociationsManager destructor to unlock
    }
    for (auto *later: laterRefs) {
        // dealloc releases the associated object of OBJC_ASSOCIATION_SYSTEM_OBJECT
        later->releaseHeldValue();
    }
}
Copy the code
  • According to the second parameterdeallocatingJudge whether or notdeallocIs called when.
  • Create a temporaryObjectAssociationMapobjectrefs.
  • Gets the total hash table of global associated objectsassociations.
  • Get the hash table of the associated object (ObjectAssociationMap)i.
  • Correspond objects toObjectAssociationMapData interaction torefsIn, the originaliEmpty.
  • nondeallocatingIn the case (that is, call yourself), the system’s associated object (throughpolicy & OBJC_ASSOCIATION_SYSTEM_OBJECTJudge) re-insertiAnd markdidReInsertfortrue.
  • If there is no need to reinsert, the corresponding object is erasedObjectAssociationMapThe iteratori.
  • createlaterRefsTo recorddeallocThe case requires whether the systemObjcAssociation.
  • cyclerefsThe non-system associated objects are directly released, and the associated objects of the system determine whether to releasedeallocating.deallocatingIn the case of joininglaterRefs.
  • Release cyclelaterRefsIn theObjcAssociation, which is the associated object of the system (deallocatingIn the case of).

3.3.3: Associated object removal process

  1. Create a temporaryObjectAssociationMapobjectrefs.
    1. According to theAssociationsManagerClass to get the global associated object total hash tableAssociationsHashMap.
  2. Based on the currentobjectencapsulatedDisguisedPtrinAssociationsHashMapTo get the corresponding iterator.
  3. This iterator is notendTags that correspond to objectsObjectAssociationMapData interaction torefsIn, the original iterator is empty for subsequent storage of system associated objects that need to be reinserted.
  4. nondeallocatingIn the case of loop acquisitionObjcAssociation, re-inserts the associated objects of the system, and erases the empty iterators if there is nothing to re-insert.
  5. Loop gainObjcAssociation, if it is a system associated object,deallocatingIn the case of temporary joinVectorIn, the non-system is released directly.
  6. If the system is associated with objectsVectorThere is data, traversal release (system associated objects indeallocatingIn case of release).

Four:dealloc

In the actual development process, we generally do not actively call the API to remove the associated object, so the object to release, must remove the associated object, the following check dealloc process, see if there is a corresponding operation.

- (void)dealloc {
    _objc_rootDealloc(self);
}
Copy the code
void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);
    
    obj->rootDealloc(a); }Copy the code

Call the process dealloc -> _objc_rootDealloc -> obj->rootDealloc, rootDealloc function has the associated object related process.

4.1: rootDealloc

inline void
objc_object::rootDealloc(a)
{
    // Tagged Pointer
    if (isTaggedPointer()) return// fixme necessary?
    
    // isa is an impure pointer
    // No weak references
    // There is no associated object
    // no c++ destructor
    // No reference count table
    // If all 5 conditions are met, please call object_dispose
    if (fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc &&#if ISA_HAS_CXX_DTOR_BIT! isa.has_cxx_dtor &&#else! isa.getClass(false) - >hasCxxDtor() &&
#endif! isa.has_sidetable_rc)) {assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this); }}Copy the code
  • isaThe pure pointer&&No weak references&&No associated object&&There is noc++The destructor&&There is no reference count table that simultaneously satisfies this5One condition directfree, otherwise callobject_disposeFunction.

4.2: object_dispose

id 
object_dispose(id obj)
{
    if(! obj)return nil;
    // Destroy objects
    objc_destructInstance(obj);    
    free(obj);

    return nil;
}
Copy the code
  • callobjc_destructInstanceThe function destroys the object and thenfree.

Obviously the core logic for removing associated objects is in the objc_destructInstance function.

4.3: objc_destructInstance

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor(a);bool assoc = obj->hasAssociatedObjects(a);// This order is important.
        // call the C++ destructor
        if (cxx) object_cxxDestruct(obj);
        // Remove the associated object
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
        // Clear the weak reference table and reference count table corresponding to 'object'
        obj->clearDeallocating(a); }return obj;
}
Copy the code
  • If you havec++Destructor, call.
  • If there are associated objects, remove them.
  • removeobjectCorresponding weak reference table and reference count table.

4.4: clearDeallocating

inline void 
objc_object::clearDeallocating(a)
{
    // Two methods do not call the same reason is
    SIDE_TABLE_WEAKLY_REFERENCED
    // Nonpointer can be determined by the ISA field
    if (slowpath(! isa.nonpointer)) {/ / pointer to pure
        // Slow path for raw pointer isa.
        // Clear the hash table
        sidetable_clearDeallocating(a); }else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {// Impure Pointers have weak reference tables or reference count tables
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow(a); }assert(!sidetable_present());
}
Copy the code
  • According to theisaWhether or not a pure pointer is divided into two logic, pure pointer callsidetable_clearDeallocatingFunction, impure pointer callclearDeallocating_slowFunction.

4.4.1: sidetable_clearDeallocatingandclearDeallocating_slow

Sidetable_clearDeallocating:

void 
objc_object::sidetable_clearDeallocating(a)
{
    SideTable& table = SideTables(to)this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock(a);// Find your reference count table iterator in the hash table
    RefcountMap::iterator it = table.refcnts.find(this);
    // No end tag
    if(it ! = table.refcnts.end()) {
        // Determine if there are weak references
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            // Clear your own weak reference table
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        // Erase your own reference count table
        table.refcnts.erase(it);
    }
    table.unlock(a); }Copy the code
  • Get the hash table.
  • Find your own reference count table iterator in the hash table.
  • According to theSIDE_TABLE_WEAKLY_REFERENCEDCheck if there are weak references and clear them.
  • Erase your own reference counter table.

ClearDeallocating_slow:

NEVER_INLINE void
objc_object::clearDeallocating_slow(a)
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables(to)this];
    table.lock(a);if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock(a); }Copy the code
  • Get the hash table.
  • throughisa.weakly_referencedCheck if there are weak references and clear them.
  • throughisa.has_sidetable_rcCheck if there is a reference count table and erase it if there is.

4.4.2: weak_clear_no_lock

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    // The object to be cleared
    objc_object *referent = (objc_object *)referent_id;
    // Find the corresponding weak_entry_t
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); ...// Zero out References weak object reference table
    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) {
                // weak reference set to nil
                *referrer = nil;
            }
            else if(* referrer) {...objc_weak_error(a); }}}// Remove entry from the weak reference table
    weak_entry_remove(weak_table, entry);
}
Copy the code
  • Find your own in the weak reference count tableweak_entry_t.
  • According to theout_of_lineGets a weak reference pointer to an objectreferrerswithinline_referrers.
  • The loop weak reference pointer array sets the weak reference pointer tonil.
  • Will a weak referenceweak_entry_tDelete from the weak reference table.

4.5: deallocCalling process

  • Conditions for direct release (isaThe pure pointer&&No weak references&&No associated object&&There is noc++The destructor&&No reference count table) :
    • directlyfree.
  • Conditions that cannot be directly released:
    • If you havec++Destructor, call.
    • If there are associated objects, remove them.
    • Clears the weak reference table (traverses the array of weak reference Pointers, all set tonil).
    • Erases its own data from the reference count table.
    • free.

It is known that in ARC mode dealloc method no longer need to explicitly call [super Dealloc], analysis of dealloc process in objC source code also did not see [super Dealloc] call (objC part is already run time), then the relevant processing must be compiled.

Use Hopper disassembly to view pseudocode:

  • It is added at compile time[super dealloc]Related processing.

Code compilation related content, later have the opportunity to send a separate document analysis.

Five: associated object implementationweakattribute

The objc_AssociationPolicy for adding associated objects to a category is as follows:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0./**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1./**< Specifies a strong reference to the associated object. * The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3./**< Specifies that the associated object is copied. * The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401./**< Specifies a strong reference to the associated object. * The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied. * The association is made atomically. */
};
Copy the code

This enumeration uses OBJC_ASSOCIATION_ASSIGN to create an association_weak object, but weak and assign are different:

Weak: Modifies OBJC objects. It does not hold the pointer modifiers, and the reference count for the same object is not increased. When the pointer is released, the weak object is set to nil.

Because heap memory is dynamic, when an object at an address is released, all Pointers to it should be null. Weak is used to avoid circular references and can be left empty when the object is released.

Assign: Modifies the basic data type in stack memory. When an OBJC object is assigned, it will not be set to nil when the object to which it is assigned is released. This may result in wild Pointers.

In order to avoid the occurrence of wild Pointers, we can only use another way: to forcibly reference an intermediate, and let the intermediate hold the weak attribute. The main implementation is to use blocks: strongly reference blocks that hold weak attributes.

@property (nonatomic, weak) id weak_obj;

- (void)setWeak_obj:(id)weak_obj {
    id __weak weakObject = weak_obj;
    // Declare the implementation of block, executing block returns weakObject
    id (^block)(void) = ^ {return weakObject; };
    // Store blocks, copy policy
    return objc_setAssociatedObject(self, "weak_obj", block, OBJC_ASSOCIATION_COPY);
}

- (id)weak_obj {
    / / get block
    id (^block)(void) = objc_getAssociatedObject(self, "weak_obj");
    // Execute block to get weakObject returned by block
    id weakObject = (block ? block() : nil);
    return weakObject;
}
Copy the code

The object is still a strong reference, but the block holds a weak reference to the property inside. After weak_OBj is released, block cannot obtain Weak_OBj.

When can associated objects cause memory leaks?

An associative object can be understood as holding an object, and if it is strongly held and the object strongly holds the class, it will result in circular references.

conclusion

The storage structure of the associated object is a two-layer hash map, which requires two layers of processing for saving, fetching and removing.

Diagram of two – level hash structure of associated object

Schematic diagram of associated object setting process

Schematic diagram of the associated object value process

Schematic diagram of associated object removal process