The Runtime profile

Objective-c is a very dynamic programming language. Unlike C, C++ and other languages, it allows many operations to be deferred until the program is running. The dynamism of Objective-C is supported and implemented by the Runtime API. The interfaces provided by the Runtime API are basically in C language, and the source code is written by C++ assembly language. Before learning the Runtime mechanism, you need to understand the essence of the OC object. Otherwise, you will not understand the following contents. The OC code written at ordinary times is converted into the Runtime API to call.

The structure of the class
//struct objc_class struct objc_class{Class isa; Class superclass; Cache_t cache; // Method cache class_data_bit_t bit; Struct class_rw_t {// uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; //method_t array property_array_t properties; Protocol_array_t protocols; Class firstSubclass; Class nextSiblingClass; char *demangledName; // uint32_t flags; // uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif const uint8_t * ivarLayout; const char * name; // Class name method_list_t * baseMethodList; // List of methods protocol_list_t * baseProtocols; // protocol list const ivar_list_t * ivars; // List of member variables const uint8_t * weakIvarLayout; property_list_t *baseProperties; }Copy the code

Methods, properties, and protocols in class_rw_t are two-dimensional arrays that can be read and written. BaseMethodList, baseProtocols, Ivars and baseProperties in class_ro_t are one-dimensional arrays that are read-only and contain the initial content of the class

Struct method_t{SEL name; // const char *type; // Encode (including return value type, parameter type) IMP IMP; // function pointer (pointer to function)}Copy the code
  • SEL stands for method \ function name, usually called selector, and has an underlying structure similar to char * (string)

Methods with the same name in different classes have the same method selector

  • Type is an encoded string containing the return values and parameters of the function
Methods the cache

The Class internal structure has a method cache (cache_t), which is used to cache previously called methods using hash tables (hash tables where space is exchanged for time to speed up the lookup) to speed up the lookup of methods

Struct cahe_t{struct bucket_t *buckets; // hash mask_t _mask; // The length of the hash table is -1 mask_t _occupied; Struct_t buket_t{cahe_ket_t _key; //SEL as key IMP _imp; // Function memory address}Copy the code
Objc_msgSend Execution process

OC method calls are converted to objc_msgSend(Receiver,message) function calls. The execution process of objc_msgSend can be divided into three phases: message sending; Dynamic method analysis; Message forwarding.

  • Objc_msgSend Phase 1: Message sending

  • Objc_msgSend Stage 2: Dynamic method parsing

Developers can implement the following methods to dynamically add method implementations

+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel
Copy the code

Dynamically parses object methods

void c_other(id self, SEL _cmd){ NSLog(@"c_other:%@ - %@", self, NSStringFromSelector(_cmd)); } - (void)other{ NSLog(@"%s", __func__); } + (BOOL)resolveClassMethod:(SEL) SEL {if (SEL == @selector(test)) {resolveClassMethod:(SEL) SEL {if (SEL == @selector(test)) { // class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8"); Method Method = class_getInstanceMethod(self, @selector(other)); // Dynamically add an implementation of the test method class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method)); return YES; } return [super resolveClassMethod:sel]; }Copy the code

Dynamically resolve class methods

+ (BOOL)resolveInstanceMethod:(SEL) SEL {if (SEL == @selector(test)) {struct *method = (struct method_t *)class_getInstanceMethod(self, @selector(other)); Class_addMethod (self, sel, method->imp, method->types); Return YES; } return [super resolveInstanceMethod:sel]; }Copy the code

After dynamic parsing, the message sending process starts from the receiverClass cache lookup step

  • Objc_msgSend Phase 3: Message forwarding

Developers can implement message forwarding in the following ways

- (id) forwardingTargetForSelector aSelector: (SEL) / / or - (NSMethodSignature *) methodSignatureForSelector aSelector - : (SEL) (void)forwardInvocation:(NSInvocation *)anInvocationCopy the code

forward

- (id) forwardingTargetForSelector: (SEL) aSelector {the if (aSelector = = @ the selector (test)) {/ / / / the Cat object implements the test method objc_msgSend([[MJCat alloc] init], aSelector) return [[Cat alloc] init]; } return [super forwardingTargetForSelector:aSelector]; }Copy the code
// Method signature: Return value type and parameter types - (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {the if (aSelector = = @ the selector (test)) {return  [NSMethodSignature signatureWithObjCTypes:"v16@0:8"]; } return [super methodSignatureForSelector:aSelector]; } // NSInvocation encapsulates a method call including: // Invocation invocation. Target Invocation. Selector Method name // [anInvocation Invocation :NULL atIndex:0] - (void)forwardInvocation:(NSInvocation *)anInvocation { // anInvocation.target = [[Cat alloc] init]; // [anInvocation invoke]; [anInvocation invokeWithTarget:[Cat alloc] init]]; }Copy the code
The loading process of a Category

The underlying structure of the categories we write daily is as follows

struct category_t { const char *name; // Class name classref_t CLS; struct method_list_t *instanceMethods; Struct method_list_t *classMethods; Struct protocol_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
  • The loading process of a category
  1. Load the category data of a class through the Runtime
  2. Combine all category methods, attributes, and protocol data into one large arrayThe category data that will be compiled later will be in front of the array
  3. Interpolate the combined classification data (methods, attributes, protocols) before the original data of the class
The Runtime application
  • Look at the private member variable of the object, and change the value of the private member variable with a KVC assignment
- (void)lookIvarsFromClass:(Class) CLS {unsigned int count; Ivar *ivars = class_copyIvarList(cls, &count); for (int i = 0; i < count; i ++) { Ivar ivar = ivars[i]; NSLog(@"%s",ivar_getName(ivar)); } free(ivars); Mydb - (void)changePlaceholderColor{[self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [super touchesBegan:touches withEvent:event]; [self lookIvarsFromClass:[UITextField class]]; }Copy the code
  • Dynamically adding properties

Dynamically adding attributes to the Person object (indirectly implementing property effects)

@implementation Person (kj) static char nameKey; - (void)setName:(NSString *)name{ objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)name{ return objc_getAssociatedObject(self, &nameKey); } @endCopy the code
  • The dictionary extension model (MJExtension framework) uses Runtime to iterate through all attributes or member variables and then sets values using KVC
+ (instancetype)objectWithJson:(NSDictionary *)json{ id obj = [[self alloc] init]; unsigned int count; Ivar *ivars = class_copyIvarList(self, &count); For (int I = 0; i < count; i ++) { Ivar ivar = ivars[i]; NSMutableString *ivarName = [NSMutableString stringWithUTF8String:ivar_getName(ivar)]; Front / / delete the underline [ivarName deleteCharactersInRange: NSMakeRange (0, 1)); // KVC set [self setValue:json[ivarName] forKeyPath:ivarName]; } free(ivars); return obj; }Copy the code
  • Use message forwarding to solve the problem of App blinking when no method can be found
- (NSMethodSignature *) methodSignatureForSelector aSelector: (SEL) {/ / should have been able to call the method the if ([self respondsToSelector: aSelector]) {  return [super methodSignatureForSelector:aSelector]; Return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } // If no method is found, - (void)forwardInvocation:(NSInvocation *)anInvocation{NSLog(@" not found %@ method ", NSStringFromSelector(anInvocation.selector)); }Copy the code
  • Replace Method Swizzling hook system Method, custom processing their own logic
#import <objc/runtime.h> @implementation UIViewController (KJ) + (void)load{Method hookMethod = class_getInstanceMethod(self, NSSelectorFromString(@"hookViewDidLoad"))); Method originalMethod = class_getInstanceMethod(self, @selector(viewDidLoad)); method_exchangeImplementations(hookMethod, originalMethod); } - (void)hookViewDidLoad{// call the original method (self hookViewDidLoad); NSLog(@" loaded %@",[self class]); } @endCopy the code
  • .
Using the Runtime mechanism properly can achieve powerful results. It depends on the understanding of the Runtime mechanism and the flexible use of the API. After all, practice makes perfect