There are a lot of articles about the understanding of Runtime, so I just want to talk about where Runtime can be used and where I used Runtime in my project. This article mainly introduces the use of runtime based on the actual application process.

So how do you use Runtime? In what situations can it be used?

First, to use the Runtime API, #import

1. The runtime gets a property or function of a class

Dynamically fetching properties or functions of a class at runtime can be used to do a variety of things, such as JSON parsing, database result parsing, determining subclasses of a class, and so on.

1.1 Analysis and conversion to Model
Objc_property_t * class_copyPropertyList(Class CLS, Unsigned int *outCount) const char *property_getName(objc_property_t property) const char *property_getAttributes(objc_property_t property)Copy the code

The above methods can be used to:

  • Parse the JSON data into a Model object.
  • Parse the database query results into Model objects.

Here is an example code snippet that dynamically gets the attributes of a class:

    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    for(i = 0; i < outCount; i++) { objc_property_t property = properties[i]; / / get nsstrings * propertyName attribute name = [nsstrings stringWithCString: property_getName (property) encoding: NSUTF8StringEncoding]; NSString *propertyType = [NSString stringWithCString: property_getAttributes(property) encoding:NSUTF8StringEncoding]; /* Each symbol corresponds to the type, some types are changed in the new SDK, Such as long and long long C char C unsigned char I int I unsigned int L long L unsigned long s short S unsigned short D double D unsigned double ffloat        F unsigned floatQ Long long q unsigned long LONG B BOOL @ object type // Pointer object type like NSString is @ "NSString" propertyType, you can print it out and see what it is. To determine the type of a property, simply [propertyType hasPrefix:@"Ti"] this means it is of type int. */ } free(properties);Copy the code
1.2 Determine the subclasses of a class

Sometimes in a program we need to determine whether a class is a subclass of another class. This function can also be implemented using the Runtime class. Here is a sample code:

    int numClasses;
    Class *classes = NULL;
    numClasses = objc_getClassList(NULL,0);
    
    if (numClasses >0 )
    {
        classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
        numClasses = objc_getClassList(classes, numClasses);
        for (int i = 0; i < numClasses; i++) {
            if(class_getSuperclass(classes[i]) == [xxxxClass class]){ id class = classes[i]; // Perform a method or do something else [class performSelector:@selector(xxxxMethod) withObject:nil]; } } free(classes); }Copy the code

The above two examples are taken from the FMDB Model wrapper I wrote earlier: JKDBModel. You can go to see more details about parsing and using the FMDB Model.

1.3 Obtaining instance variables of a class

If you also need to get an instance variable of a class, you can use the following apis:

Ivar * class_copyIvarList(Class CLS, Const char * ivar_getName(Ivar Ivar) const char * ivar_getTypeEncoding(  Ivar ivar)Copy the code

Here is an example code snippet for getting instance variables:

    unsigned int outCount, i;
    
    Ivar *ivaries = class_copyIvarList([Son class], &outCount);
    for (i = 0; i < outCount; i++) {
        Ivar ivar = ivaries[i];
        NSString *ivarName = [NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding];
        NSString *ivarType = [NSString stringWithCString:ivar_getTypeEncoding(ivar) encoding:NSUTF8StringEncoding];
        NSLog(@"Name :%@-- Type :%@",ivarName,ivarType); /* Each symbol corresponds to the type, some types are changed in the new SDK, Such as long and long long C char C unsigned char I int I unsigned int L long L unsigned long s short S unsigned short D double D unsigned double ffloat        F unsigned floatQ Long long q unsigned long LONG B BOOL @ Object type // Pointer object types such as NSString is @ "NSString" */} free(iranges);Copy the code
1.4 Getting a method of a class

Methods that get a class include the property set and get methods of that class, but not the property set and get methods of the parent class, and not the methods of the parent class (if overridden in the current class).

The main API:

Method * class_copyMethodList(Class CLS, Const char* sel_getName(SEL aSelector) const char* sel_getName(SEL aSelector)Copy the code

Sample code snippet for getting an array of methods:

    unsigned int outMethodCount, j;
    Method *methods = class_copyMethodList([Son class], &outMethodCount);
    for (j = 0; j < outMethodCount; j++) {
        Method method = methods[j];
        SEL selector = method_getName(method);
        if (selector) {
            NSString *methodName = [NSString  stringWithCString:sel_getName(selector) encoding:NSUTF8StringEncoding];
            NSLog(@Methods: "% @",methodName);
        }
    }
    free(methods);
Copy the code

2. Method Swizzling at runtime

Use Method Swizzling carefully, because it may lead to bugs that cannot be checked. After all, it replaces official apis, and it is difficult to fully understand what is going on inside some apis.

In usage scenarios, you need to monitor the interface that users frequently open and the duration of staying on a certain interface.

So what can we do?

Write a UIViewController Category, then add custom methods to the Category, such as -xxxViewDidAppear: and -xxxViewDidDisappear:, and then in the -load method, replace it with custom methods.

+ (void)load {
        static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];         
        // When swizzling a class method, use the following:
                    // Class class = object_getClass((id)self);

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}); }#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
        [self xxx_viewWillAppear:animated];
        NSLog(@"xxx_viewWillAppear: %@", self); // Here, we can send a message to the server, or do other things. }Copy the code

The above sample code is taken from objective-C Runtime Runtime 4: Method Swizzling

In the case of Method Swizzling, he switched the implementation parts of the two methods.

For example, above we call -xxx_viewwillAppear, because -xxx_viewwillAppear: The internal implementation of -viewwillAppear: is called instead of executing the above implementation. So the above code does not loop at all.

Write a code to illustrate:

- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"This is the old way.");
}

- (void)xxx_viewWillAppear:(BOOL)animated {
    NSLog(@"xxx_viewWillAppear: %@", self); // Here, we can send a message to the server, or do other things. }Copy the code

If we replace these two methods with method Swizzling, we call -xxx_ViewwillAppear: it prints this is the original method; And -viewwillAppear: prints xxx_viewWillAppear:. It needs to be digested here.

For more information about Method Swizzling, see Method Swizzling

3. Associated Objects

Object associations (or associative references) are an important feature of the Objective-C runtime, allowing developers to add custom attributes to existing classes in extensions.

The following three functions are needed:

void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)

id objc_getAssociatedObject(id object, void *key)

void objc_removeAssociatedObjects(id object)
Copy the code

As we all know, we can’t add new attributes to a Category in OC, but we can indirectly add custom attributes to a class through Associated Objects.

The fundamental reason we can’t add attributes is that it doesn’t help us automatically add instance variables to objects, nor does it help us generate set and GET methods. Although the set/GET methods can be implemented themselves, there are no instance variables to store data.

static char kAssociatedObjectKey;

objc_setAssociatedObject(self, &kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(self, &kAssociatedObjectKey);
Copy the code

And, of course, there’s a better way to do it with keys, which is selector. An example of using selector is below.

The following code demonstrates how to add a new attribute to a Category.

This is Son + AssociatedObject. H

#import "Son.h"@interface Son (AssociatedObject) /** Home address */ @Property (copy, nonatomic) NSString *address; /** height */ @property (assign, nonatomic) int height; @endCopy the code

This is Son + AssociatedObject. M

#import "Son+AssociatedObject.h"
#import <objc/runtime.h>

@implementation Son (AssociatedObject)

- (void)setAddress:(NSString *)address
{
    objc_setAssociatedObject(self, @selector(address), address, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

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

- (void)setHeight:(int)height
{
    NSNumber *heighNum = [NSNumber numberWithInt:height];
    objc_setAssociatedObject(self, @selector(height), heighNum, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)height
{
    NSNumber *heightNum = objc_getAssociatedObject(self, @selector(height));
    return heightNum.intValue;
}

@end
Copy the code

Although void objc_removeAssociatedObjects(ID Object) is mentioned above, do not use this function lightly because it removes all associated objects. We usually want to remove an associated object, just pass in nil with objc_setAssociatedObject.

Add an associated object usage scenario:

When using an AlertView or ActionSheet, have you ever been frustrated that you can’t easily retrieve a Model object from a clickable proxy method?

In addition to adding a property to the controller, we can also add an associated object to the AlertView or ActionSheet so that the Model object can be easily accessed in the proxy method.

If we add a Category to AlertView or ActionSheet, the code is basically the same as adding a Category to Son, and the object type is id.

Or we can add the associated object when we call it in the controller. Here’s how:

static char kAssociatedObjectKey;
objc_setAssociatedObject(self, &kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(self, &kAssociatedObjectKey);
Copy the code

UIAlertController is the same thing.

For the use of Associated Objects, there are two examples of extending the Category functionality to make it easy to add properties and corresponding getters and setters to a Category.

OC automatically generates a library of classification attribute methods –DProperty

4. Create a class dynamically at runtime

I tested writing a method in a controller that creates a MyClass class. There is no class file called MyClass in the project.

- (void)createClass
{
    Class MyClass = objc_allocateClassPair([NSObject class], "MyClass", 0); // 1. Add an instance variable named name of type NSString. The fourth parameter is the method and the fifth parameter is the parameter typeif (class_addIvar(MyClass, "name", sizeof(NSString *), 0, "@")) {
        NSLog(@"add ivar success"); } // 2. Add a property // Add an instance variable to the propertyif (class_addIvar(MyClass, "_address", sizeof(NSString *), 0, "@")) {
        NSLog(@"add ivar success");
    }
    
    objc_property_attribute_t type = {"T"."@\"NSString\""};
    objc_property_attribute_t ownership = { "C"."" };
    objc_property_attribute_t backingivar = { "V"."_address"};
    objc_property_attribute_t attrs[] = {type, ownership, backingivar};
    class_addProperty(MyClass, "address", attrs,2); Myclasstest = myclasstest = myclasstest;"v@:"Class_addMethod (MyClass, @selector(myclasstest:), (IMP)myclasstest,"v@:"); Objc_registerClassPair (MyClass); // 4. Myobj = [[MyClass alloc] init]; NSString *str = @"Name"; Object_setInstanceVariable (myobj, object_setInstanceVariable);"itest", (void *)&str); [myobj] is not allowed under ARCsetValue:str forKey:@"name"];
    [myobj setValue:@"Here's the address." forKey:@"address"]; Myobj myclasstest [myobj myclasstest:10]; } // this method is not actually called, but it must be implemented otherwise the following method will not be called - (void)myclasstest:(int)a {NSLog(@)"Ahhhhhhh!"); } static void myclasstest(id self, SEL _cmd, int a) Other arguments can be added later {Ivar v = class_getInstanceVariable([self class],"name"); // Return the ivar variable named name id o = object_getIvar(self, v); // The result is successfully printed NSLog(@"name is %@", o);
    NSLog(@"A is %d", a);
    
    objc_property_t property = class_getProperty([self class], "address");
    NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
    id value = [self valueForKey:propertyName];
    NSLog(@"address is %@", value);
}
Copy the code

Creating a new class at runtime is covered in detail in the comments above.

Have Fun!