A list,

Category is a language feature added after Objective-C 2.0. The main purpose of a category is to add methods to an existing class. You can use a category to add methods to an existing class without knowing the source code of the class.

  • We can usecategorySeparating the implementation of a class into several different files reduces the size of a single file. Different functions can be organized into different onescategoryMake the function simple. It is possible for multiple developers to work on a class by creating its owncategoryCan. You can load what you want on demandcategory, such asSDWebImageUIImageView+WebCacheUIButton+WebCache, loading different according to different requirementscategory.
  • We can still be therecategoryDeclare private methods.

Ii. Comparison between Extension and Category

  • extensionIt’s determined at the compiler, it’s part of the class, at compile time and in the header file@interfaceAnd in the implementation file@implementationForm a complete class, which comes into being with the creation of the class and dies out with the extinction of the class.extensionGenerally used to hide the private information of a class, you must have the source of the class to add to a classextension. Cannot be added for system classesextension.
  • categoryIt’s determined at runtime,categoryYou can’t add instance variables,extensionIt can be added.

The nature of categories

3.1 Basic use of Category

Let’s start with the basic use of the following categories:

// Person+Eat.h

#import "Person.h"

@interface Person (Eat) <NSCopying, NSCoding>

- (void)eatBread;

+ (void)eatFruit;

@property (nonatomic, assign) int count;

@end

// Person+Eat.m

#import "Person+Eat.h"

@implementation Person (Eat)

- (void)eatBread {
    NSLog(@"eatBread");
}

+ (void)eatFruit {
    NSLog(@"eatFruit");
}

@end
Copy the code
  • Created aPersonClassification, specifically to achieve the function of eating
  • This classification complies with two protocols, namelyNSCopyingNSCoding
  • Two methods are declared, one instance method and one class method
  • To define acountattribute

3.2 Category at compile time

Let’s look at the clang compiler and see what the nature of this code is at compile time.

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc MyClass.m -o MyClass-arm64.cpp
Copy the code

After compiling, we can see that the essence of a category is the category_t structure. No matter how many categories we create, category_T will eventually be generated, and the methods, properties, and protocols in the category will be stored in this structure. That is, at compile time, members of a class are not merged with the class.

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
};
Copy the code
  • name: Class name
  • clsClass:
  • instanceMethodscategoryList of all instance methods added to the class in
  • classMethods:categoryA list of all class methods added to a class in
  • protocols:categoryA list of all protocols implemented in
  • instanceProperties:categoryAll attributes added to the

fromcategoryYou can add instance methods, you can add class methods, you can implement protocols, you can add properties.

Instance variables cannot be added

Let’s continue with the following compiled code:

static struct/ * _method_list_t* / {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1].
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"eatBread"."v16@0:8", (void *)_I_Person_Eat_eatBread}}
};

static struct/ * _method_list_t* / {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1].
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"eatFruit"."v16@0:8", (void *)_C_Person_Eat_eatFruit}}
};

static struct/ * _protocol_list_t* / {
	long protocol_count;  // Note, this is 32/64 bit
	struct _protocol_t *super_protocols[2].
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	2,
	&_OBJC_PROTOCOL_NSCopying,
	&_OBJC_PROTOCOL_NSCoding
};

static struct/ * _prop_list_t* / {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[1].
} _OBJC_$_PROP_LIST_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	1,
	{{"count"."Ti,N"}}};static struct _category_t _OBJC_The $_CATEGORY_Person_The $_Eat __attribute__ ((used.section(" __DATA, __objc_const"))) = 
{
	"Person".0.// &OBJC_CLASS_$_Person,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat,
	(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Eat,
};
Copy the code
  • So let’s take a look at that_OBJC_$_CATEGORY_Person_$_EatThe values in the struct variable are the correspondingcategory_tThe first member is the class name, because we declare instance methods, class methods, we follow the protocol, we define properties, so we have values for all of those in our structure variables.
  • _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_EatThe struct represents the list of instance methods that containeatBreadInstance methods
  • _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_EatThe structure containseatFruitClass method
  • _OBJC_CATEGORY_PROTOCOLS_$_Person_$_EatThe structure containsNSCopingNSCodingagreement
  • _OBJC_$_PROP_LIST_Person_$_EatThe structure containscountattribute

3.3 Category during runtime

After looking at compile-time categories, we move on to run-time categories

In the objc-Runtime-new.mm source code, we can finally figure out how to add the method list, property list, and protocol list of the category to the class.

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    /* * Only a few classes have more than 64 categories during launch. * This uses a little stack, and avoids malloc. * * Categories must be added in the proper order, which is back * to front. To do that with the chunking, we iterate cats_list * from front to back, build up the local buffers backwards, * and call attachLists on the chunks. attachLists prepends the * lists, so the final result is in the expected order. */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data() - >extAllocIfNeeded(a);for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if() {if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle(a); }property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0; } protolists[ATTACH_BUFSIZ - ++protocount] = protolist; }}if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
Copy the code
  • rwe->methods.attachLists(mlists, mcount);
  • rwe->protocols.attachLists(protolists, protocount);
  • rwe->properties.attachLists(proplists, propcount);

These three functions add a list of methods, attributes, and protocols from a category to a class.

Continue looking at the implementation of the attchLists function:

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    } 
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0])); }}Copy the code
  • In this source code, we focus on two functionsmemmovememcpy.
  • memmoveThe move () function moves memory back and the list of methods back
  • memcpyThe memory copy () function is used to copy memorycategoryThe list of methods in is copied to the location moved from the previous step.

If both the category and the original class have a method of the same name, but the methods in the category are placed first in the list of new methods, the methods are found in order at runtime. Once you find that method, you don’t look any further down, giving the illusion that the category overrides the method of the original class.

That’s why we always prefix methods in categories to avoid accidentally overwriting the class’s own methods.

  • If multiplecategoryThe compiler determines which method the runtime ultimately calls,The last method participating in the compilation will be called first.

The +load method

Next, we’ll look at the +load method in classes and classes, starting with the following code:

// Person.h @interface Person : NSObject + (void)test; @end // Person.m @implementation Person + (void)load { NSLog(@"Person +load"); } + (void)test { NSLog(@"Person +test"); } @end // Person+Test1.m @implementation Person (Test1) + (void)load { NSLog(@"Person (Test1) +load"); } + (void)test { NSLog(@"Person (Test1) +test"); } @end // Person+Test2.m @implementation Person (Test2) + (void)load { NSLog(@"Person (Test2) +load"); } + (void)test { NSLog(@"Person (Test2) +test"); } @end /// int main(int argc, const char * argv[]) { @autoreleasepool { [Person test]; } return 0; } // Print the result: Person + Load Person (Test1) + Load Person (Test2) + Load Person (Test2) +testCopy the code
  • And by printing the results,+loadMethod is called three times, andtestThe method will only be called once, and the result seems to be different from the above study. The previous study concluded that methods with the same name will only call the last compiled class of the class, which shows that+loadMethods andtestThe calling nature of methods is different. Why exactly? Only through the source code to explore

The call_load_methods function is used to load the +load method of the class:

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}
Copy the code
  • So from this function, we can see thisdo-whileThe cycle, first of all, is throughcall_class_loadsFunction to load the class+loadmethods
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if(! cls)continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
Copy the code
  • fromcall_class_loadsIn the function, you can find thatload_methodFunction pointer foundloadMethod and call directly

When the load method of the class is called, the load method of the call_category_loads class is called.

static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if(! cat)continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", cls->nameForLogging(), _category_getName(cat)); } (*load_method)(cls, @selector(load)); cats[i].cat = nil; }}// Compact detached list (order-preserving)
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;

    // Copy any new +load candidates from the new list to the detached list.
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }

    // Destroy the new list.
    if (loadable_categories) free(loadable_categories);

    // Reattach the (now augmented) detached list. 
    // But if there's nothing left to load, destroy the list.
    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {
        if (cats) free(cats);
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }

    if (PrintLoading) {
        if(loadable_categories_used ! =0) {
            _objc_inform("LOAD: %d categories still waiting for +load\n", loadable_categories_used); }}return new_categories_added;
}
Copy the code
  • This function also passesload_methodThe function pointer calls the class directlyloadMethods.

Through the above source code analysis, we can draw the following conclusions:

  • loadThe order in which methods are called, first of all, in the call classloadMethod and has nothing to do with the order in which it was compiled, followed by the call classloadMethod, classificationloadMethods are called in the order in which they were compiled
  • Explains why in the previous example, rightPersonClass and classificationloadWhy is the method called 3 times, whiletestMethod is called only once. becauseloadMethod makes a direct call through the function pointer to find the memory address of the function, while+testMethods byisaThe pointer eventually finds a call to a list of class methods in a metaclass object, and the nature of the call is different.

Next look at calling the load method with inheritance, create the Student class that inherits from the Person class, and create two classes

// Student.m @implementation Student + (void)load { NSLog(@"Student +load"); } @end // Student+Test1.m @implementation Student (Test1) + (void)load { NSLog(@"Student (Test1) +load"); } @end // Student+Test1.m @implementation Student (Test2) + (void)load { NSLog(@"Student (Test2) +load"); Person +load Student +load Student (Test2) +load Person (Test2) +load Student (Test1) +load Person (Test1) +loadCopy the code
  • The parent class is called first based on the printed resultloadMethod, and then call the subclass’sloadMethod, and then call the classloadmethods

Prepare_load_methods was called before call_category_loads was called from the runtime source

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if(! cls)continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods"); } realizeClassWithoutSwift(cls, nil); ASSERT(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); }}Copy the code

The schedule_class_load function is called iteratively based on the list of classes

static void schedule_class_load(Class cls)
{
    if(! cls)return;
    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
Copy the code
  • fromschedule_class_loadFunction is recursive call, first look up the parent class call, ensure that the parent classloadMethods. Through the source code to confirm our previous print results.
  • The classification ofloadInstead of calling the parent class first, method calls are compiled first and called first

If you create a new class that has no inheritance from the Person class, the load methods are called in compile order, compile first.

+Initialize

The initialize and load methods are very confusing. Let’s change the Person example above and change the load method to initailize to see the printed result:

// Person.m @implementation Person + (void)initialize { NSLog(@"Person +initialize"); } @end // Person+Test1.m @implementation Person (Test1) + (void)initialize { NSLog(@"Person (Test1) +initialize"); } @end // Person+Test2.m @implementation Person (Test2) + (void)initialize { NSLog(@"Person (Test2) +initialize"); } @end // Student.m @implementation Student + (void)initialize { NSLog(@"Student +initialize"); } @end // main function int main(int argc, const char * argv[]) {@autoreleasepool {[Student alloc]; } return 0; Person (Test1) +initialize Student +initializeCopy the code
  • inmainThe function we just calledStudentallocMethod to find that there are two printed results, if we comment outmainFunction,StudentallocMethod, we’ll see that the console doesn’t print anything. Here we come to a conclusion:initilizeMethod is called the first time a class receives a message.
  • The second phenomenon is that we only haveStudentSends a message and finds its parent classPersoninitilizeMethods are also called, and they are called in the classinitilizeMethods. This suggests thatinitilizeMethods are invoked through the messaging mechanism, that is, throughisaFind the class object/metaclass object to call the method, because the class exists so only the methods in the class will be called.

Let’s explore the underlying problem with the source code:

Class class_initialize(Class cls, id obj)
{
    runtimeLock.lock();
    return initializeAndMaybeRelock(cls, obj, runtimeLock, false);
}

static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();
    ASSERT(cls->isRealized());

    if (cls->isInitialized()) {
        if(! leaveLocked) lock.unlock();return cls;
    }

    // Find the non-meta class for cls, if it is not already one.
    // The +initialize message is sent to the non-meta class object.
    Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);

    // Realize the non-meta class if necessary.
    if (nonmeta->isRealized()) {
        // nonmeta is cls, which was already realized
        // OR nonmeta is distinct, but is already realized
        // - nothing else to do
        lock.unlock();
    } else {
        nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
        // runtimeLock is now unlocked
        // fixme Swift can't relocate the class today,
        // but someday it will:
        cls = object_getClass(nonmeta);
    }

    // runtimeLock is now unlocked, for +initialize dispatch
    ASSERT(nonmeta->isRealized());
    initializeNonMetaClass(nonmeta);

    if (leaveLocked) runtimeLock.lock();
    return cls;
}
Copy the code
  • class_initializeThe function calls thetainitializeAndMaybeRelockDelta function, which is concerned withinitializeNonMetaClassfunction
void initializeNonMetaClass(Class cls)
{ ASSERT(! cls->isMetaClass()); Class supercls;bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    
 			...
#endif{ callInitialize(cls); . }void callInitialize(Class cls)
{((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}
Copy the code
  • From the function above, we find thatinitilizeThe parent class is called recursivelyinitilizeFunction, called lastcallInitializeFunction, andcallInitializeThe function is called internallyobjc_msgSendThe function.

Through the source code, explained the initilize method is to call the parent class and then call the child class.

One final note: if the subclass does not implement the initilize method, the subclass initilize method will be called multiple times when it receives the message.

The reason is: When the initilize method of the parent class is called, but the subclass does not have the initilize method, the subclass will use the superclass pointer to search the initilize method in the parent class and find the existence of the parent class, then call the initilize method of the parent class, but it does not mean that the parent class is initialized twice.

Category and associated objects

Since there is no way to add instance variables to the category structure, we can use the Runtime’s associated object to do this.

If we just add properties to a category, by default, we only generate declarations of getter and setter methods, not implementations of getter and setter methods and member variables, so we want to use the defined properties to value and assign, It crashes because it can’t find a way to do it.

The function of value and assignment can be realized indirectly by associating objects:

// Person+Test.h 
@interface Person (Test)

@property (nonatomic, copy) NSString *name;

@end

// Person+Test.m
@implementation Person (Test)

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, @selector(name));
}

@end
Copy the code
  • You start by defining a property in the category declaration file, manually implementing getter and setter methods in the implementation file, and using associated objects inside the getter and setter methods.

  • The objc_setAssociatedObject function takes four arguments

    • The first argument is the object being associated
    • The second argument is that you need to pass in an address,const void *Type, the purpose of which is to map the values set by this address
    • The third parameter is the value to set
    • The fourth parameter is similar to the keyword that defines the attribute, and is mainly for memory management
  • The objc_getAssociatedObject function takes two arguments

    • The first argument is the object being associated
    • The second argument is passed in an address from which to get the value set earlier, so the sum is guaranteedobjc_setAssociatedObjectThe second parameter remains the same.

But where do the associated objects live? Does it exist in the object’s memory? What happens to the associated object when the object is destroyed?

The _object_get_associative_reference and _object_set_associative_reference functions can be found in the objc-references. Mm file. We can see that the associated objects are managed through the AssociationsManager.

In AssociationsManager, AssociationsHashMap is used to store all associated objects. The AssociationsHashMap key is the pointer address of the associated object, and the corresponding value is an ObjectAssociationMap. And ObjectAssociationMap’s key is a pointer to const void * and value is objcasSociety, The objcassociety contains two members: _policy and _value. At this point, we understand the nature of the associated object.

If we want to delete the value of an associated object, all we need to do is pass the value of the corresponding key to nil in objc_setAssociatedObject, and an associated object value will be erased.

Another puzzle is what happens to the associated object when the object is destroyed?

In the object destruction source: objc-Runtime-new.mm file

Determines whether the object contains an associated object, and if so, removes the associated object. So when an object is destroyed, its associated object is also destroyed.

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.
        if (cxx) object_cxxDestruct(obj);
      
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating(a); }return obj;
}
Copy the code