preface

Scenario: A system class is needed, but the system class does not meet the requirements. You need to add an extra property. The general solution is inheritance. But adding just one attribute to a class is too cumbersome. At this point, the Runtime’s associated object comes into play.

Add attributes to a class, such as @property (assign, nonatomic) int age; There are actually three things that are done internally: generating member variables, declaring and implementing getters and setters.

You can add member methods to a Category, but you can’t add member variables directly from a Category. Because the member variable _age is generated, getter and setter declarations are generated, but no getter and setter implementations are generated. Instance variables may not be placed in categories

That’s because in the structure of a Category, there’s no array for member variables, just properties, protocols, etc.

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;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
Copy the code

Add attributes to the Category

There are three options:

1. Use global variables

Such as:

#import "YZPerson + PD.h" @implementation YZPerson (PD) int _weight; // global variable - (void)setWeight:(int)weight{_weight = weight; } - (int)weight{ return _weight; } @endCopy the code
YZPerson *person = [[YZPerson alloc] init];
person.weight = 103;
NSLog(@"weight = %d",person.weight);
Copy the code

It seems to work. But because global variables are shared, there are data security issues if multiple instances access/modify the variable.

2. Use a dictionary

Against the above disadvantages, use a dictionary to ensure a one-to-one relationship. The address value of the object is used as the key, and the weight value is stored and used as the value. There is no interference between different objects.

But since it’s a global variable, the problem is obvious. There are memory leaks, thread safety issues, and a lot of code to write for each additional attribute. Bad for maintenance.

#####3. Associated objects This method is described below

Associated object scheme

The runtime provides the following interfaces:

  • Setting an Associated Object

Interface methods used:

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
Copy the code

Parameter Description:

Id object: To add an attribute to an object. In this case, to add an attribute to yourself, use self. Void * == ID key: obtains the property value of the associated object based on the key. In objc_getAssociatedObject, obtains the property value through the secondary key and returns the value. Id value: The associated value, that is, the value passed to the property by the set method. Objc_AssociationPolicy Policy: Indicates the policy in which attributes are saved.

// Typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {OBJC_ASSOCIATION_ASSIGN = 0, // Assign OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // Assign nonatomic to strong, nonatomic OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // copy, nonatomic OBJC_ASSOCIATION_RETAIN = 01401, // strong, atomic OBJC_ASSOCIATION_COPY = 01403 // copy, atomic };Copy the code

In the list above, there is no policy corresponding to the weak modifier

  • Gets the attributes of the associated object

Methods of use:

objc_getAssociatedObject(id object, const void *key);
Copy the code

Parameter Description:

Id object: Gets the associated attribute in which object. Void * == ID key: An attribute corresponding to the key in objc_setAssociatedObject that retrieves value from the key value.

  • Removing associated Objects
- (void)removeAssociatedObjects{// Remove the associated object objc_removeAssociatedObjects(self); }Copy the code

For example:

Add an attribute name to the Category

#import "YZPerson.h" @interface YZPerson (PD) @property (nonatomic,strong) NSString *name; // Add attribute @endCopy the code
#import "YZPerson+PD.h"
#import <objc/runtime.h>

@implementation YZPerson (PD)

const void *YZNameKey = &YZNameKey;

- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, YZNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

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

- (void)dealloc{
    objc_removeAssociatedObjects(self);
}

@end
Copy the code

Use it as a normal property.