A, Category details

The compiled Category
  1. _category_t structure
struct _category_t {
    const char *name;   / / the main class name
    struct _class_t *cls; / / a concrete class
    const struct _method_list_t *instance_methods; // List of instance methods
    const struct _method_list_t *class_methods; // List of class methods
    const struct _protocol_list_t *protocols; // Protocol list
    const struct _prop_list_t *properties; // Attribute list
};
Copy the code
  1. The construction of a category
static struct _category_t _OBJC_The $_CATEGORY_NSString_The $_Test __attribute__ ((used.section(" __DATA, __objc_const"))) = 
{
        "NSString".// name
        0.// &OBJC_CLASS_$_NSString, // cls
        0.// instance_methods
        0.// class_methods
        (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_NSString_$_Test, // protocols
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NSString_$_Test, // properties
};
Copy the code
Category related source code

1. Category_t structure, you can see that the source code is not different from compiled

struct category_t {
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
    WrappedPtr<method_list_t, PtrauthStrip> classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t* _classProperties;
    If it is a class, return the instance method. If it is a metaclass, return the class method
    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    // Attribute list
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    // Protocol list
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else returnprotocols; }};Copy the code
  1. How are categories loaded?

Category is dynamically loaded into memory at Runtime with the following core code:

  • The attachCategories function is used to concatenate all categories
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{

    / /... Other code
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    // Declare 64 length method array, property array, protocol array (all two-dimensional arrays)
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];
    
    // Each quantity is initialized to 0
    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    
    /** class_rw_t -> extAllocIfNeeded */
    // rwe is the class_rw_ext_t member of the current class
    auto rwe = cls->data()->extAllocIfNeeded();

    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];

        // category_t -> methodsForMeta
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {  // If this class has a list of methods, add the list of methods to declared mlists
            if (mcount == ATTACH_BUFSIZ) { // If there are more than 64 entries, then the mlist should be fixed
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            // This sentence indicates that the original method is placed in the lower position of mlists,
            // Mlists are two-dimensional array, store directly mlist
            // The list traversed first is placed after the mlists, i.e. the first compiled categories are placed after
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        // Same with proplist
        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;
        }
        // Same with protolist
        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) {
        // Prepare the list of methods
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        // Concatenate the list of methods
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        / /... Other code
    }

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

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
Copy the code
  • The attachLists function is mainly used to join the methods in each category
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;
        
        if (hasArray()) {
            // many lists -> many lists
            // oldCount is the number of methods in the class
            uint32_t oldCount = array()->count;
            // newCount is a combination of the original and the number of categories
            uint32_t newCount = oldCount + addedCount;
            // Create a new space and copy count to newCount
            array_t *newArray = (array_t *)malloc(array_t: :byteSize(newCount));
            newArray->count = newCount;  
            array()->count = newCount;
       
            // Iterate through newArray in reverse order, putting the methods of this class in
            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];

            // In the positive order traversal newArray, put the classification method into
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            // Free up space and assign newArray to itself
            free(array());
            setArray(newArray);
            validate(a); }else if(! list && addedCount ==1) {
            / /... Other code
        } 
        else {
           / /... Other code}}Copy the code

After concatenation, the structure of the method array of this class becomes [[class method list 1], [class method list 2],….. [class method list]].

Objc_msgSend () does not overwrite the original method, but returns it after finding the method

Can I add member variables to a Category?

This is a classic interview question. Category_t does not have ivAS member variables, so we cannot add member variables to Category directly

Category_t has a list of properties, so you can add properties to a property, but what’s the difference?

The compiler automatically generates setters and getters for properties in this class, but it doesn’t automatically generate setters and getters for properties in a Category, so when we declare a property in a Category, when we assign a value to that property, we’re going to say unrecognized selector

So, it’s up to us to manually add setters and getters to the properties of the categories ourselves. But classes can’t add member variables, so who does our setter method assign to? Who does the getter return?

3. Associated object technology

We can indirectly add something like a member variable to a Category by associating objects. There are three main functions for associated object correlation

Key: the unique key of the associated value value: the specific value of the associated value Policy: the associated policy (similar to the parameter used to declare properties) */
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

/** Obtain an associated object of an object. Object: The associated source object. Key: The unique key of the associated value
id objc_getAssociatedObject(id object, const void *key)

/** Remove the associated object of the object object: the source object to be removed */
void objc_removeAssociatedObjects(id object) 
Copy the code

Using the associative object technique, we can assign a value to cName in the setter method and get the value of cName in the getter method

Four, the implementation principle of the associated object

  1. Through the source code of the associated object, you can see that the principle of the associated object is mainly composed of four parts
  • AssociationsManager: Manages an AssociationsHashMap in the Manager
  • AssociationsHashMap: This map uses the Object passed in by setXXX as the Key of the map after some other operations and uses an ObjectAssociationMap as the value
  • ObjectAssociationMap: This map uses the Key passed in by setXXX as the Key and the Objcassociety consisting of the value and policy passed in by setXXX as the value

Such design idea, in AssociationsHashMap, for the same Object, Object as the Key to find the ObjectAssociationMap, there are all its associated objects {Key: Objcasassociation}, for different Objcet, different ObjectAssociationMaps can be found by Key, and all associationShashMaps are managed by AssociationsManager. The schematic diagram is as follows:

  1. Associated object related source
  • Objc_setAssociatedObject () function
// objc_setAssociatedObject() directly calls the _object_set_associative_reference() function
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

// Implement it
void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    / /... Abnormal judgment

    // Do some processing on the object to generate a Key
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // Create ObjcAssociation based on policy and value
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();

    bool isFirstAssociation = false;
    {   
        / / declare AssociationsManager
        AssociationsManager manager;
        // Get AssociationsHashMap from manager
        AssociationsHashMap &associations(manager.get());

        if (value) {  // If value is not nil, add the associated object
            // Associations: Obtain the ObjectAssociationMap based on the key processed with object
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                isFirstAssociation = true;
            }
            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            // Set association by key in ObjectAssociationMap
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else { // Delete the associated object if value is nil
            / / get ObjectAssociationMap
            auto refs_it = associations.find(disguised);
            if(refs_it ! = associations.end()) {auto &refs = refs_it->second;
                / / for association
                auto it = refs.find(key);
                if(it ! = refs.end()) { association.swap(it->second);/ / erase association
                    refs.erase(it);
                    if (refs.size() == 0) {
                        // If there are no other associated objects, erase the ObjectAssociationMap
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }

    / / /... Other code
}
Copy the code
  • Objc_getAssociatedObject () function
// objc_getAssociatedObject() directly calls the _object_get_associative_reference() function
id objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}

id _object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        / / get the manager
        AssociationsManager manager;
        / / get AssociationsHashMap
        AssociationsHashMap &associations(manager.get());
        // Associations: Obtain the ObjectAssociationMap based on the key processed with object
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if(i ! = associations.end()) { ObjectAssociationMap &refs = i->second;// Obtain the Association by key
            ObjectAssociationMap::iterator j = refs.find(key);
            if(j ! = refs.end()) {// Assign association if there is oneassociation = j->second; association.retainReturnedValue(); }}}return association.autoreleaseReturnedValue();
}
Copy the code