• Author: Walking boy lang
  • Links to this article: www.jianshu.com/p/b08bbe361…



This article introduces the underlying principles of categories in the Runtime in iOS development. Through this article, you will learn:

  1. Introduction to Category
  2. The essence of the Category
  3. The Category loading process
  4. The +load method for Category and Class
  5. Category and associated object

Example code in this article is: bujige/ysc-category -Demo

1. Introduction to Category

1.1 What is a Category?

Categories are objective-C 2.0 language features that add methods to existing classes. A Category can extend or separate a class by adding new methods to an existing class without subclassing or invading the source code of the class. In daily development, we often use categories to extend functionality to existing classes.

While inheritance can also add new methods to existing classes and directly add attributes, inheritance adds unnecessary code complexity and is indistinguishable from the parent class’s original methods at run time. So we can give priority to using custom categories.

Generally, categories are used in the following scenarios:

  • Separate class implementations into separate files.
  • Declare private methods.
  • Simulate multiple inheritance.
  • Expose framework private methods.

1.2 Category and Extension

A Category looks similar to an Extension. An Extension is sometimes called an anonymous classification. But they are fundamentally different things. Extension is compiled at the same time as the class at compile time and is part of the class. And the methods declared in Extension can only be implemented in the @implementation of that class, which means you can’t use Extension on system classes like NSString classes.

And unlike Category, Extension can declare not only methods but also member variables, which Category cannot.

Why can’t a Category add member variables like an Extension?

Because an Extension is compiled at the same time as the class, it is part of the class. Since they are part of a class and are compiled at the same time as the class, member variables can be added to the class at compile time.

Categories, on the other hand, have the ability to dynamically add new behavior to existing classes at run time. Categories are determined during run time. The memory layout of member variables is already determined at compile time, and adding member variables at run time would destroy the memory layout of the original class, with dire consequences, so categories cannot add member variables.

2. The essence of categories

2.1 Introduction to Category structure

In the first chapter of iOS development: “Runtime” in detail (1), we learned the basics: Objc-runtimenew.h objc-runtimenew.h objc-runtimenew.h objc-runtimenew.h Category is defined as category_T structure. The category_T structure has the following data structure:

typedef struct category_t *Category;

struct category_t {
    const char *name;                                / / the name of the class
    classref_t cls;                                  // Class, which corresponds to the class object at runtime via clasee_name (class name)
    struct method_list_t *instanceMethods;           // List of all added object methods in the Category
    struct method_list_t *classMethods;              // List of all added class methods in the Category
    struct protocol_list_t *protocols;               // List of all protocols implemented in Category
    struct property_list_t *instanceProperties;      // All attributes added to the Category
};
Copy the code

You can also see from the structure definition of a Category that a Category can add object methods, class methods, protocols, and attributes to a class. At the same time, you can also see that categories cannot add member variables.

2.2 Category C++ source code

To understand the nature of categories, we need the C++ source code for them. First, we need to write a Person class that inherits from NSObject, and we need to write a category for Person+Additon. Add object methods, class methods, properties, and proxies to the classification.

For example, in the following code:

/ * * * * * * * * * * * * * * * * * * * * * Person + Addition. H file * * * * * * * * * * * * * * * * * * * * * /

#import "Person.h"

/ / PersonProtocol agent
@protocol PersonProtocol <NSObject>

- (void)PersonProtocolMethod;

+ (void)PersonProtocolClassMethod;

@end

@interface Person (Addition) <PersonProtocol>

/* name attribute */
@property (nonatomic.copy) NSString *personName;

/ / class methods
+ (void)printClassName;

// Object method
- (void)printName;

@end

/ * * * * * * * * * * * * * * * * * * * * * Person + Addition, m file * * * * * * * * * * * * * * * * * * * * * /

#import "Person+Addition.h"

@implementation Person (Addition)

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

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

#pragma mark - <PersonProtocol>methods

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

+ (void)PersonProtocolClassMethod {
    NSLog(@"PersonProtocolClassMethod");
}
Copy the code

OC to C++ source code method as follows:

  1. Add the Person class files Person.h and Person.m to the project. The Person class inherits from NSObject.
  2. Add the Category files Person+ addition. h and Person+ addition. m for the Person class to the project, and add related object methods, class methods, properties, and proxies to the Category.
  3. Open Terminal and runcd XXX/XXXCommand, whereXXX/XXXIs the directory where the Category file resides.
  4. Continue execution on the terminalclang -rewrite-objc Person+Addition.m
  5. After executing the command, a Person+ addition. CPP file will be generated in the Person+ addition. m directory, which is the C++ source code for the Category we need.

When we get the Person+ addition.cpp file, it’s a magical thing: it’s 3.7 meters in size, with nearly 10W lines of code.

Don’t panic. The C++ source for the Category is at the bottom of the file. If we remove all the extraneous code and just keep the Category code, we’re left with something like 200 lines of code. Now let’s explain the Category structure by module according to its different structure.

2.2.1 “Category Structure”

// Category structure of the Person class
struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};

// Assign the Category structure of the Person class
static struct _category_t _OBJC_The $_CATEGORY_Person_The $_Addition __attribute__ ((used.section(" __DATA, __objc_const"))) = 
{
    "Person".0.// &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Addition,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Addition,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Addition,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Addition,
};

// Category array. If Person has multiple categories, there are multiple categories in the Category array
static struct _category_t *L_OBJC_LABEL_CATEGORY_$[1] __attribute__((used.section(" __DATA, __objc_catlist.regular.no_dead_strip"))) = {
    &_OBJC_$_CATEGORY_Person_$_Addition,
};
Copy the code

From the “Category structure” source we can see:

  1. Categor structure.
  2. The assignment statement of the Category structure.
  3. Array of Category structures.

The first Categor structure corresponds in essence to the Categor structures described in the 2.1 Category introduction. You can view it as the same structure. The third Category structure array holds the related categories of the Person class, or the corresponding number of Category structures if there are more than one Category.

2.2.2 “Object Method List Structure” in Category

// - (void)printName; Object method implementation
static void _I_Person_Addition_printName(Person * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ct_0dyw1pvj6k16t5z8t0j0_ghw0000gn_T_Person_Addition_405207_mi_1);
}

// - (void)personProtocolMethod; Method implementation
static void _I_Person_Addition_personProtocolMethod(Person * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ct_0dyw1pvj6k16t5z8t0j0_ghw0000gn_T_Person_Addition_f09f6a_mi_2);
}

// Object method list structure added to the Person category
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2{{(struct objc_selector *)"printName"."v16@0:8", (void *)_I_Person_Addition_printName},
    {(struct objc_selector *)"personProtocolMethod"."v16@0:8", (void *)_I_Person_Addition_personProtocolMethod}}
};
Copy the code

From the “object method list structure” source we can see:

  1. - (void)printName;Object method implementation.
  2. - (void)personProtocolMethod;Method implementation.
  3. Object method list structure.

Any object method implemented in a Category (including object methods in a proxy). Are added to the object method list structure _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Addition. If only defined in person.h and not implemented, it will not be added.

2.2.3 “Class Method List Structure” in Category

// + (void)printClassName; Class method implementation
static void _C_Person_Addition_printClassName(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ct_0dyw1pvj6k16t5z8t0j0_ghw0000gn_T_Person_Addition_c2e684_mi_0);
}

// + (void)personProtocolClassMethod; Method implementation
static void _C_Person_Addition_personProtocolClassMethod(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ct_0dyw1pvj6k16t5z8t0j0_ghw0000gn_T_Person_Addition_c2e684_mi_3);
}

// Class method list structure added to the Person category
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2{{(struct objc_selector *)"printClassName"."v16@0:8", (void *)_C_Person_Addition_printClassName},
    {(struct objc_selector *)"personProtocolClassMethod"."v16@0:8", (void *)_C_Person_Addition_personProtocolClassMethod}}
};
Copy the code

From the “class method list structure” source we can see:

  1. + (void)printClassName;Class method implementation.
  2. + (void)personProtocolClassMethod;Class method implementation.
  3. Class method list structure.

Any class method implemented in a Category (including the class method in a proxy). Will be added to the class method list structure _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Addition. If only defined in person.h and not implemented, it will not be added.

2.2.4 Protocol List Structure in Category

// Protocol list structure added to the Person category
static struct/ * _protocol_list_t* / {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1].
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_PersonProtocol
};

// Protocol list object Method list structure
static struct/ * _method_list_t* / {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1].
} _OBJC_PROTOCOL_INSTANCE_METHODS_PersonProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"personProtocolMethod"."v16@0:8".0}}};// Protocol list class method list structure
static struct/ * _method_list_t* / {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1].
} _OBJC_PROTOCOL_CLASS_METHODS_PersonProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"personProtocolClassMethod"."v16@0:8".0}}};// Assign to the PersonProtocol structure
struct _protocol_t _OBJC_PROTOCOL_PersonProtocol __attribute__ ((used)) = {
    0."PersonProtocol",
    (const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_PersonProtocol,
    (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_PersonProtocol,
    (const struct method_list_t *)&_OBJC_PROTOCOL_CLASS_METHODS_PersonProtocol,
    0.0.0.sizeof(_protocol_t),
    0,
    (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_PersonProtocol
};
struct _protocol_t* _OBJC_LABEL_PROTOCOL_The $_PersonProtocol= & _OBJC_PROTOCOL_PersonProtocol;
Copy the code

From the “protocol list structure” source we can see:

  1. Protocol list structure.
  2. Protocol list object Method list structure.
  3. Protocol list class method list structure.
  4. PersonProtocol Protocol structure assignment statement.

2.2.5 “Attribute List Structure” in Category

// List of attributes added to the Person category
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_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"personName"."T@\"NSString\",C,N"}}};Copy the code

From the “attribute list structure” source we see:

Only the attribute list structure _OBJC_$_PROP_LIST_Person_$_Addition is added to the Person category, and there is no member variable structure _ivar_list_t. There is nothing about the corresponding set/GET methods. This directly addresses the fact that you can’t add member variables to a Category.

2.3 Substantial summary of Category

Let’s summarize the nature of categories:

The essence of a Category is the _category_T structure type, which contains the following sections:

  1. _method_list_tObject method list structure of type;
  2. _method_list_tClass method list structure of type;
  3. _protocol_list_tType of the protocol list structure;
  4. _prop_list_tType of the property list structure.

The _category_T structure does not contain the _ivar_list_t type, that is, the “member variable structure”.

3. Category loading process

3.1 General process of DYLD loading

We talked earlier about the fact that categories are dynamically loaded at runtime. The Runtime loads without a dynamic linker called dyLD.

On MacOS and iOS, the dynamic link loader dyld is used to load all libraries and executables. The process of loading the Runtime occurs when the DYLD is loaded.

The code for DyLD is available for download on Apple’s open source website. Dyld Apple open source code

The dyLD loading process looks like this:

  1. Configure environment variables;
  2. Load the shared cache;
  3. Initialize the main APP;
  4. Insert dynamic cache library;
  5. Link main program;
  6. Link-inserted dynamic library;
  7. Initialization main program: OC, C++ global variable initialization;
  8. Returns the main program entry function.

In this article, we only need to care about step 7, because this is where the Runtime is initialized. Loading a Category is also part of this process.

In the main program, the Runtime initializes the call stack as follows:

dyldbootstrap::start —> dyld::_main —> initializeMainExecutable —> runInitializers —> recursiveInitialization —> doInitialization —> doModInitFunctions —> _objc_init

The last call to _objc_init is a method in the LibobJC library that initializes the Runtime and is an Objective-C entry point.

The runtime code is available for download on Apple’s open source web site. Objc4 Apple open source code

In the _objc_init step: Runtime binds a callback to DYLD. When image is loaded into memory, DYLD notifies Runtime for processing. Runtime takes over and calls MAP_images for parsing and processing. Call the _read_images method to add the object methods, protocols, and attributes of the Category to the class, and the class methods and protocols to the metaclass of the class. The call_load_methods method is then called in load_images to iterate over all the loaded classes, calling the load method of the Class and the load method of its Category in order of inheritance and compilation.

The call stack for loading a Category is as follows:

_objc_init –> map_images –> map_images_NOLock –> _read_images (load classification) –> load_images.

Now that we know that Category loading happens in the _read_images method, we just need to focus on the code for Category loading in the _read_images method.

3.2 Category loading process

3.2.1 _read_images method

Ignore the other code in the _read_images method that is not relevant to this article and get the following:

// Get the classification array in the mirror
category_t **catlist = 
    _getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();

// Iterate over the classification array
for (i = 0; i < count; i++) {
    category_t *cat = catlist[i];
    Class cls = remapClass(cat->cls);
    // Handle this classification
    // First, register the current classification with the target class
    // Then, if the class is implemented, rebuild the class's list of methods
    bool classExists = NO;
    if (cat->instanceMethods ||  cat->protocols  
        ||  cat->instanceProperties) 
    {
        addUnattachedCategoryForClass(cat, cls, hi);  
        if(cls->isRealized()) { remethodizeClass(cls); classExists = YES; }}if (cat->classMethods  ||  cat->protocols  
        ||  (hasClassProperties && cat->_classProperties)) 
    {
        addUnattachedCategoryForClass(cat, cls->ISA(), hi);
        if(cls->ISA()->isRealized()) { remethodizeClass(cls->ISA()); }}}Copy the code

Two main methods are used:

  • addUnattachedCategoryForClass(cat, cls, hi);Add unattached categories for classes
  • remethodizeClass(cls);Rebuild the class’s method list

Two objectives are achieved through these two methods:

  1. theCategory (Category)Object methods, protocols, and properties are added to the class.
  2. theCategory (Category)Class method, protocol added to the classmetaclassOn.

Here are the two methods mentioned above.

3.2.2 addUnattachedCategoryForClass (cat, CLS, hi); methods

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertLocked();

    // Get the list of all unattached categories stored: cats
    NXMapTable *cats = unattachedCategories();
    category_list *list;
    // Find the list of CLS unattached classes from cats list: list
    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
        list = (category_list *)
            realloc(list.sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    // Add the new category cat to the list
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    // Inserting the newly generated list into cats overwrites the old list
    NXMapInsert(cats, cls, list);
}
Copy the code

addUnattachedCategoryForClass(cat, cls, hi); You can refer to the code comments for the execution of the. After executing this method, the system places the current class cat into the list of unattached classes corresponding to the CLS of that class. Classes and categories are associated with each other.

RemethodizeClass (CLS); Methods.

3.2.3 remethodizeClass (CLS); methods

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Get the list of CLS unattached classes: cats
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        // Append the list of unattached classes to the CLS class
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats); }}Copy the code

remethodizeClass(cls); The attachCategories method basically does one thing: call attachCategories(CLS, cats, True); Method Append the list of unattached classes cats to CLS class. So again, attachCategories(CLS, Cats, True); Methods.

3.2.4 attachCategories (CLS, cats, true); methods

I swear this is the last piece of code to load a Category in this article. But it is also the most core piece of code.

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if(! cats)return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // Create a method list, attribute list, and protocol list to store the methods, attributes, and protocols of the classification
    method_list_t **mlists = (method_list_t* *)malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t* *)malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t* *)malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;           // Record the number of methods
    int propcount = 0;        // Record the number of attributes
    int protocount = 0;       // Record the number of protocols
    int i = cats->count;      // Start at the end of the category array, ensuring that the latest category is fetched first
    bool fromBundle = NO;     // Whether the record is fetched from the bundle
    while (i--) { // iterate from back to front
        auto& entry = cats->list[i];  // Retrieve the current category
    
        // Retrieve the list of methods in the classification. If it's a metaclass, it gets a list of class methods. Otherwise you get a list of object methods
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;            // Put the method list into the mlists method list array
            fromBundle |= entry.hi->isBundle();  // Remember whether the bundle is stored in the header of the class
        }

        // Get the list of attributes in the class. If it's a metaclass, get nil
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        // Retrieve the list of protocols followed in the classification
        protocol_list_t *protolist = entry.cat->protocols;
        if(protolist) { protolists[protocount++] = protolist; }}// Retrieve the class_rw_t data of the current CLS class
    auto rw = cls->data();

    // Store an array of methods, attributes, and protocols into the RW
    // Prepare method list methods in mlists
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    // Add the list of new methods to the list of methods in rW
    rw->methods.attachLists(mlists, mcount);
    // Release method list mlists
    free(mlists);
    // Clear the CLS cache list
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    // Add the new property list to the property list in the RW
    rw->properties.attachLists(proplists, propcount);
    // Release the property list
    free(proplists);

    // Add the new protocol list to the protocol list in rW
    rw->protocols.attachLists(protolists, protocount);
    // Release protocol list
    free(protolists);
}
Copy the code

From attachCategories (CLS, cats, true); You can see from the annotation of the method that this method is the core code that stores the methods, attributes, and protocols of the classification.

But there are a few details to note:

  • The methods, attributes, and protocols of a Category are only added to the original class, without completely replacing the methods, attributes, and protocols of the original class.

    An example of this is: suppose the original class ownsMethodAMethods, categories also haveMethodAMethod, then after the class is loaded, the class will have two methods in the listMethodAMethods.
  • The methods, attributes, and protocols of a Category are added to the top of the list of methods, attributes, and protocols of the original class, while the methods, attributes, and protocols of the original class are moved to the bottom of the list. Because methods are searched at runtime along the list of methods, Category methods are found first and executed directly, while the methods of the original class are not executed. This is the most immediate reason why the methods in a Category override the methods of the original class.

4. +load methods for Category and Class

The methods, attributes, and protocols in the Category are appended to the class before the + load method executes. That is, the methods, attributes, and protocols in the Category are already loaded in the class before the + load method is executed.

The order of the Category and Class + load methods is as follows:

  1. First call the main class, according to the compile order, according to the inheritance relationship from the parent class to the child class call;
  2. Call the main class, then call the classification, according to the compile order, call; ı II
  3. + loadMethods are called only once unless actively called.

Using this call rule, we know that the main class’s + load method call must precede the class’s + load method call. However, the classification + load method is not called in accordance with the inheritance relationship, but in accordance with the compile order, which also leads to the call order of the + load method is not always determined. One order may be: Parent -> subclass -> parent category -> subclass category, or parent -> Subclass -> subclass category -> parent category.

5. Category and associated objects

As we mentioned earlier, you can add attributes to a Category, but you don’t generate corresponding member variables, and you don’t generate getters and setters. As a result, an error is reported when you call the properties declared in the Category.

So there’s no way to use the Category properties?

The answer, of course, is no.

We can implement getters and setters ourselves and use Objective-C Associated Objects to implement getters and setters. Associative objects can help us associate arbitrary properties to an object at run time. Specific needs to use the following methods:

// 1. Set the associated attribute of the object in the form of key: value
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

// 2. Use key to obtain the associated attribute object
id objc_getAssociatedObject(id object, const void *key);

// 3. Remove the attributes associated with the object
void objc_removeAssociatedObjects(id object);
Copy the code

Here is an example.

5.1 Added network address attributes to UIImage classification

/ * * * * * * * * * * * * * * * * * * * * * UIImage + Property. H file * * * * * * * * * * * * * * * * * * * * * /

#import <UIKit/UIKit.h>

@interface UIImage (Property)

/* Image network address */
@property (nonatomic.copy) NSString *urlString;

// Clear associated objects
- (void)clearAssociatedObjcet;

@end

/ * * * * * * * * * * * * * * * * * * * * * UIImage + Property. The m file * * * * * * * * * * * * * * * * * * * * * /

#import "UIImage+Property.h"
#import <objc/runtime.h>

@implementation UIImage (Property)

/ / set methods
- (void)setUrlString:(NSString *)urlString {
    objc_setAssociatedObject(self.@selector(urlString), urlString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

/ / get methods
- (NSString *)urlString {
    return objc_getAssociatedObject(self.@selector(urlString));
}

// Clear the associated object
- (void)clearAssociatedObjcet {
    objc_removeAssociatedObjects(self);
}

@end
Copy the code

Test code:

UIImage *image = [[UIImage alloc] init];
image.urlString = @"http://www.image.png";

NSLog(@"image urlString = %@",image.urlString);

[image clearAssociatedObjcet];
NSLog(@"image urlString = %@",image.urlString);
Copy the code

Print result: 2019-07-24 18:36:31.051789+0800 ysc-category [74564:17944298] image urlString = www.image.png 2019-07-24 18:36:31.051926+0800 ysc-category [74564:17944298] image urlString = (null)

As can be seen: with the aid of the association object, we successfully added the urlString association attribute to the UImage class in UIImage classification, and implemented the getter and setter methods.

Note: You can disconnect all associations using objc_removeAssociatedObjects. It is generally not recommended because it breaks all connections. If you want to disassociate the association you can use objc_setAssociatedObject, just pass the associated object to nil.

The resources

  • Meituan technical team: In-depth understanding of Objective-C: Category
  • CJS_ : Summary of the underlying implementation principle of iOS classification
  • Run-time apps for iOS
  • Objc4 apple open source code | in this reference: objc4-750 version
  • Dyld apple open source code | in this reference: dyld – version 635.2

The last

Finally, I just wanted to write about categories and associated objects at first. Accidentally touching the bottom of a Category… And then I accidentally wrote too much. Heart tired…

If the article is wrong, please correct, thank you.

IOS Development: Runtime

  • IOS development: “Runtime” details (1) Basic knowledge
  • IOS development: “Runtime” details (ii) Method Swizzling
  • IOS development: “Runtime” details (3) Category underlying principles
  • IOS development: “Runtime” detailed explanation (4) obtain the class detailed attributes, methods

Not yet completed:

  • IOS development: “Runtime” details (5) Crash protection system
  • IOS development: “Runtime” (6) Objective-C 2.0
  • IOS development: “Runtime” details (seven) KVO low-level implementation


I would like to recommend an excellent iOS communication platform. The partners in the platform are all excellent iOS developers. We focus on sharing technology and exchanging skills. Welcome to join us (if you want to enter, you can add small series wechat 17512010526)