Introduction and Application of Runtime VI - Practice

Keywords: xcode Attribute simulator hot update

What exactly does runtime do in real development?How can we use tools like Niu X?

To use runtime, first add a header file before writing the runtime code:

#import <objc/objc-runtime.h> //simulator
 perhaps
 #import <objc/runtime.h> //real machine
 #import <objc/message.h> //real machine
  • 1
  • 2
  • 3
  • 4

1. Add a class dynamically

(The KVO implementation utilizes runtime to dynamically add classes)

Originally, when you look at an object, the system automatically creates a new class that inherits from the original class, then overrides the setter method of the observed property. Then the overridden setter method is responsible for notifying the observer before and after calling the original setter method. Then the isa pointer of the original object points to the new class, and we know that the object uses the isa pointer to find out which class it belongs to, and to goLook for methods in the list of methods of the class you are in, so this object will naturally become an instance object of the new class.

Just like KVO, the system dynamically adds a new class that inherits from the class you want to listen to while the program is running, then overrides the setter method of the original class and notifies the observer there.

So how do you dynamically add a class? Code directly:

// Create a class (size_t extraBytes This parameter is usually specified as 0, which is the number of bytes assigned to index ivars at the end of class and metaclass objects.)
Class clazz = objc_allocateClassPair([NSObject class], "GoodPerson", 0);

// Add ivar
// @encode(aType): Returns a C string of that type
class_addIvar(clazz, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));

class_addIvar(clazz, "_age", sizeof(NSUInteger), log2(sizeof(NSUInteger)), @encode(NSUInteger));

// Register this class
objc_registerClassPair(clazz);

// Create Instance Object
id object = [[clazz alloc] init];

// Set ivar
[object setValue:@"Tracy" forKey:@"name"];

Ivar ageIvar = class_getInstanceVariable(clazz, "_age");
object_setIvar(object, ageIvar, @18);

// Class and memory address of the print object
NSLog(@"%@", object);

// Print property values of objects
NSLog(@"name = %@, age = %@", [object valueForKey:@"name"], object_getIvar(object, ageIvar));

// The objc_disposeClassPair method cannot be called when an instance of the class or its subclass still exists
object = nil;

// Destroy Class
objc_disposeClassPair(clazz);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

The results are:

2016-09-04 17:04:08.328 Runtime-Practice [13699:1043458] <GoodPerson: 0x1002039b0>
2016-09-04 17:04:08.329 Runtime-Practice [13699:1043458] name = Tracy, age = 18
  • 1
  • 2
  • 3

In this way, we dynamically add a GoodPerson class inherited from NSObject and add name and age member variables to it while the program is running.

2. What can we do to get all the properties of a class through runtime?

1. Print all ivars, properties, and method s of a class (simple and direct use)

Person *p = [[Person alloc] init];
[p setValue:@"Kobe" forKey:@"name"];
[p setValue:@18 forKey:@"age"];
//    p.address = @ "Guangzhou University City";
p.weight = 110.0f;

// 1. Print all ivars
unsigned int ivarCount = 0;
// Use a dictionary to load ivarName and value
NSMutableDictionary *ivarDict = [NSMutableDictionary dictionary];
Ivar *ivarList = class_copyIvarList([p class], &ivarCount);
for(int i = 0; i < ivarCount; i++){
    NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivarList[i])];
    id value = [p valueForKey:ivarName];

    if (value) {
        ivarDict[ivarName] = value;
    } else {
        ivarDict[ivarName] = @"Value is nil";
    }
}
// Print ivar
for (NSString *ivarName in ivarDict.allKeys) {
    NSLog(@"ivarName:%@, ivarValue:%@",ivarName, ivarDict[ivarName]);
}

// 2. Print all properties
unsigned int propertyCount = 0;
// Use a dictionary to load propertyName and value
NSMutableDictionary *propertyDict = [NSMutableDictionary dictionary];
objc_property_t *propertyList = class_copyPropertyList([p class], &propertyCount);
for(int j = 0; j < propertyCount; j++){
    NSString *propertyName = [NSString stringWithUTF8String:property_getName(propertyList[j])];
    id value = [p valueForKey:propertyName];

    if (value) {
        propertyDict[propertyName] = value;
    } else {
        propertyDict[propertyName] = @"Value is nil";
    }
}
// Print property
for (NSString *propertyName in propertyDict.allKeys) {
    NSLog(@"propertyName:%@, propertyValue:%@",propertyName, propertyDict[propertyName]);
}

// 3. Print all methods
unsigned int methodCount = 0;
// Install methodName and arguments in a dictionary
NSMutableDictionary *methodDict = [NSMutableDictionary dictionary];
Method *methodList = class_copyMethodList([p class], &methodCount);
for(int k = 0; k < methodCount; k++){
    SEL methodSel = method_getName(methodList[k]);
    NSString *methodName = [NSString stringWithUTF8String:sel_getName(methodSel)];

    unsigned int argumentNums = method_getNumberOfArguments(methodList[k]);

    methodDict[methodName] = @(argumentNums - 2); // The reason for -2 is that there are two self and selector parameters inside each method
}
// Print method
for (NSString *methodName in methodDict.allKeys) {
    NSLog(@"methodName:%@, argumentsCount:%@", methodName, methodDict[methodName]);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

The printout is:

2016-09-04 17:06:49.070 Runtime-Practice [13723:1044813] ivarName:_name, ivarValue:Kobe
 2016-09-04 17:06:49.071 Runtime-Practice [13723:1044813] ivarName:_age, ivarValue:18
 2016-09-04 17:06:49.071 Runtime-Practice [13723:1044813] ivarName:_weight, ivarValue:110
 2016-09-04 17:06:49.072 Runtime-Practice [13723:1044813] ivarName:_address, ivarValue: Value is nil
 2016-09-04 17:06:49.072 Runtime-Practice [13723:1044813] propertyName:address, propertyValue:value is nil
 2016-09-04 17:06:49.072 Runtime-Practice [13723:1044813] propertyName:weight, propertyValue:110
 2016-09-04 17:06:49.073 Runtime-Practice [13723:1044813] methodName:setWeight:, argumentsCount:1
 2016-09-04 17:06:49.073 Runtime-Practice [13723:1044813] methodName:weight, argumentsCount:0
 2016-09-04 17:06:49.074 Runtime-Practice [13723:1044813] methodName:setAddress:, argumentsCount:1
 2016-09-04 17:06:49.074 Runtime-Practice [13723:1044813] methodName:address, argumentsCount:0
 2016-09-04 17:06:49.074 Runtime-Practice [13723:1044813] methodName:.cxx_destruct, argumentsCount
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2. Dynamic Variable Control

In the program, XiaoMing's age is 10, then 20 by runtime, to see how runtime does it:

-(void)changeAge{
     unsigned int count = 0;
     //Get all the properties in the XiaoMing class dynamically [including private, of course] 
     Ivar *ivar = class_copyIvarList([self.xiaoMing class], &count);
     //Traverse attributes to find corresponding age fields 
     for (int i = 0; i<count; i++) {
         Ivar var = ivar[i];
         const char *varName = ivar_getName(var);
         NSString *name = [NSString stringWithUTF8String:varName];
         if ([name isEqualToString:@"_age"]) {
             //Modify the corresponding field value to 20
             object_setIvar(self.xiaoMing, var, @"20");
             break;
         }
     }
     NSLog(@"XiaoMing's age is %@",self.xiaoMing.age);
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3. Adding methods to the classification of NSObject s to avoid crashes when using KVC assignments

Sometimes we need to modify a class's private variable through KVC, but we don't know if the property exists. If the property doesn't exist in the class, it will crash through KVC assignment, which can also be judged at runtime.We also add the following methods to the classification of NSObject s.

/**
 *  Determine if there is this attribute in the class
 *
 *  @param property Property Name
 *
 *  @return Judgement Result
 */
-(BOOL)hasProperty:(NSString *)property {
    BOOL flag = NO;
    u_int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i < count; i++) {
        const char *propertyName = ivar_getName(ivars[i]);
        NSString *propertyString = [NSString stringWithUTF8String:propertyName];
        if ([propertyString isEqualToString:property]){
            flag = YES;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

4. Automatic archiving and archiving

Due to the len gt h of the article, extract this content as a single article, skip links -->. runtime From Beginning to Proficiency (7) - Automatic Archiving and Documentation

5. Dictionary to Model

Due to the len gt h of the article, extract this content as a single article, skip links -->. runtime From Beginning to Proficiency (8) - Dictionary to Model Using runtime

3. What can we do by using the dynamic exchange method of runtime?

1. Simple exchange of methods

Create a Person class that implements the following two class methods and declares them in the.h file

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

+ (void)study {
    NSLog(@"Study");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Called in the controller, print run first, then print learn

[Person run];
[Person study];
  • 1
  • 2

Following is a runtime implementation of method exchange, with class_getClassMethod for class methods and class_getInstanceMethod for object methods

// Get Class Methods for Two Classes
Method m1 = class_getClassMethod([Person class], @selector(run));
Method m2 = class_getClassMethod([Person class], @selector(study));
// Start Exchange Method Implementation
method_exchangeImplementations(m1, m2);
// After exchanging, print your studies before you run!
[Person run];
[Person study];
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2. Intercept system methods (Swizzle black magic), or replace system methods

For some reason, we need to change the implementation of this method, but we can't move its source code (system methods or some open source libraries have problems), so runtime comes in handy.

Requirements: For example, if iOS6 upgrades iOS7 and requires a version adapter, how can I add version judgment statements to each UIImage without manually modifying each image one by one, depending on the different systems that use different styles of pictures (parameterization and flattening)?

Step:
1. Create a classification for UIImage (UIImage+Category)

2. Implement a custom method in the classification that writes statements to be included in the system method, such as version judgment

+ (UIImage *)xh_imageNamed:(NSString *)name {
    double version = [[UIDevice currentDevice].systemVersion doubleValue];
    if (version >= 7.0) {
        // If the system version is above 7.0, use a flattened picture with a different filename ending with'_os7'
        name = [name stringByAppendingString:@"_os7"];
    }
    return [UIImage xh_imageNamed:name];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3. Rewrite the UIImage's load method in the classification to exchange methods (load is only appropriate if you allow it to execute a method exchange statement once)

+ (void)load {
    // Get Class Methods for Two Classes
    Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
    Method m2 = class_getClassMethod([UIImage class], @selector(xh_imageNamed:));
    // Start Exchange Method Implementation
    method_exchangeImplementations(m1, m2);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Note: The last thing you have to do in the custom method is to call the system's method again so that it has the ability to load pictures. However, due to method exchange, the system's method name has become our custom method name (a bit around, that is, we can call the system's method with our name, we can call our method with the system's name), which implements the interception of system methods!

With the above ideas, we can also add classifications to NSObject, count how many objects are created, add classifications to controllers, count how many controllers are created, especially when the company's demand changes, add a function on some of the original controls or modules, we recommend using this method!

Exchange principle:

  • Before swapping:

  • After exchange:

3. Implement multiple inheritance at runtime

Now that the method can be intercepted and exchanged, the effect of achieving multiple inheritance is left to the reader to think about (avoid taking too long, and come back to this in a blog later)

IV. Dynamic Addition Method

Development Use Scenario: If there are many class methods and the loading of classes into memory is resource intensive, a mapping table needs to be generated for each method, which can be solved by adding a method to a class dynamically.

Classic Interview Question: Do you use performSelector? In fact, I want to ask you if you have dynamically added a method.

Simple use:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    Person *p = [[Person alloc] init];

    // The default person, which does not implement the eat method, can be called through performSelector, but will cause an error.
    // Dynamic Add Method Will Not Error
    [p performSelector:@selector(eat)];

}


@end


@implementation Person
// void(*)()
// The default method has two implicit parameters.
void eat(id self,SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// When an object calls an unimplemented method, the method processing is called and the corresponding method list is passed in.
// You can just tell if the unimplemented method is what we want to add dynamically?
+ (BOOL)resolveInstanceMethod:(SEL)sel
{

    if (sel == @selector(eat)) {
        // Dynamic add eat method

        // First parameter: which class to add a method to
        // Second parameter: Method number of the add method
        // Third parameter: Functional implementation of the add method (function address)
        // The fourth parameter is the type of function (return value + parameter type) v:void @: object->self: denotes SEL->_cmd
        class_addMethod(self, @selector(eat), eat, "v@:");

    }

    return [super resolveInstanceMethod:sel];
}
@end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

5. Runtime set and get API s allow categories to add attributes

Steps:

1. Creating a category, such as adding a name attribute to any object, is NSObject Add Classification (NSObject+Category)

2. Declare the get and set methods in @property in.h first for easy syntax calls

@property(nonatomic,copy)NSString *name;
  • 1
  • 2

3. Rewrite set and get methods in.m, and internally use runtime to assign and value attributes

char nameKey;

- (void)setName:(NSString *)name {
    // Associate a value with an object, store a value in an object
    objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, &nameKey);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

6. Universal Interface Jump (using more than N methods of runtime)

Due to the len gt h of the article, extract this content as a single article, skip the link -> runtime From Beginning to Proficiency (9) - Universal Interface Jump

7. Plug-in Development

Getting started with plug-ins

XCode has a big hole: it does not officially support plug-in development, there is no official documentation, and XCode does not have open source, but because XCode is written by Objective-C, OC dynamics are too strong, so people can make all kinds of plug-ins in such a closed situation. Its core development method is:

dump takes out all the header files of the Xcode and knows what classes and interfaces are in the Xcode.

Plug-in logic is implemented by inserting your own code through header file method name guessing methods, swizzle methods.

Listen for events through the NSNotificationCenter.

There are many articles on the web for more detailed development tutorials, so search for them yourself if you are interested.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

8. Jspath hot update is also using runtime, jspatch is basically black technology, online repair version bug s, Wechat uses this technology, details of Baidu "JSPatch" (now banned by Apple)

Posted by Eiolon on Tue, 21 May 2019 09:44:29 -0700