preface

  • Skilled use of Runtime related technologies to better solve complex problems and fulfill complex requirements

Class representation in Runtime

Struct objc_class {// The isa of an instance refers to the Class object, and the ISA of a Class object refers to the metaclass isa; // The pointer, as the name implies, represents a what #if! __OBJC2__ Class super_class; Const char *name; // Class name long version; long info; Long instance_size struct objc_ivar_list *ivars struct objc_ivar_list **methodLists; Struct objc_cache *cache; Struct objc_protocol_list *protocols // protocol list #endif} OBJC2_UNAVAILABLE;Copy the code

The API is introduced

Objc family of functions

For example, space allocation, registration, and deregistration of classes and protocols

function Function are
objc_getClass Get the Class object
objc_getMetaClass Get the MetaClass object
objc_allocateClassPair Allocate space and create classes
objc_registerClassPair Registering a class
objc_disposeClassPair Unregister a class
objc_allocateProtocol Open up space to create protocols
objc_registerProtocol Register a protocol
objc_setAssociatedObject Associate an object with an instance object
objc_getAssociatedObject Gets the associated object of the instance object
objc_removeAssociatedObjects Clears all associated objects of the instance object
objc_getProtocol Get a protocol
objc_copyProtocolList Copy the list of protocols registered at run time

Class functions

Examples include instance variables, methods, properties, protocols, and other related issues

function Function are
class_addIvar Add instance variables for the class
class_addProperty Add attributes to the class
class_addMethod Add methods for classes
class_addProtocol Follow the protocol for the class
class_replaceMethod Replace the implementation of a method of a class
class_getName Get the name of the class
class_isMetaClass Determine whether it is a metaclass
class_getSuperclass Gets the parent of a class
class_setSuperclass Sets the parent of a class
class_getProperty Gets a property of a class
class_getInstanceVariable Get instance variables
class_getClassVariable Get class variables
class_getInstanceMethod Get instance method
class_getClassMethod Get class methods
class_getMethodImplementation Get the implementation of the method
class_getInstanceSize Gets the size of an instance of the class
class_respondsToSelector Determines whether a class implements a method
class_conformsToProtocol Determines whether a class complies with a protocol
class_createInstance Create an instance of the class
class_copyIvarList Copy the list of instance variables of the class
class_copyMethodList Copy the list of methods for the class
class_copyProtocolList Copy the list of protocols that the class follows
class_copyPropertyList Copy the property list of the class

Object series functions

For instance variables

function Function are
object_getClassName Gets the class name of the object
object_getClass Gets the Class of the object
object_setClass Sets the Class of the object
object_getIvar Gets the value of an instance variable in an object
object_setIvar Sets the value of the instance variable in the object
object_getInstanceVariable Get the value of the instance variable in the object (not valid in ARC, using object_getIvar)
object_setInstanceVariable Set the value of an instance variable in an object (not valid in ARC, using object_setIvar)

Method series functions

Such as method parameters and return value types and method implementation

function Function are
method_getName Get method name
method_getImplementation Get the implementation of the method
method_getTypeEncoding Gets the type encoding of the method
method_getNumberOfArguments Gets the number of arguments to the method
method_copyReturnType The return type of the copy method
method_getReturnType Gets the return type of the method
method_copyArgumentType The parameter type of the copy method
method_getArgumentType Gets the parameter type of the method
method_getDescription Gets a description of the method
method_setImplementation The implementation of the set method
method_exchangeImplementations Implementation of replacement methods

Property series functions

For example, attribute name, attribute characteristics, etc

function Function are
property_getName Get attribute name
property_getAttributes Gets the property list for the property
property_copyAttributeList Property list of copy properties
property_copyAttributeValue Copies the value of a property in the property

Protocol functions

For example, obtain the protocol name and check whether to comply with the protocol

function Function are
protocol_conformsToProtocol Determine whether one protocol complies with another
protocol_isEqual Determine whether the two protocols are consistent
protocol_getName Obtaining the protocol name
protocol_copyPropertyList Copy the protocol property list
protocol_copyProtocolList Copy the protocol list followed by a protocol
protocol_copyMethodDescriptionList List of methods to copy the protocol
protocol_addProtocol Follow one protocol for another
protocol_addProperty Add attributes for the protocol
protocol_getProperty Gets an attribute in the protocol
protocol_addMethodDescription Add a method description for the protocol
protocol_getMethodDescription Gets the description of a method in the protocol

Ivar series of functions

function Function are
ivar_getName Obtain the Ivar name
ivar_getTypeEncoding Get type encoding
ivar_getOffset Get offset

Sel series functions

function Function are
sel_getName Get the name
sel_getUid Registration method
sel_registerName Registration method
sel_isEqual Determine whether the methods are equal

Imp series functions

function Function are
imp_implementationWithBlock Create imPs from blocks of code
imp_getBlock Gets a block of code in a function pointer
imp_removeBlock Remove code blocks in IMP

Runtime Actual use

To obtain a list of

Typedef struct objc_method *Method; Typedef struct objc_ivar *Ivar; /// Category typedef struct objc_category *Category; Typedef struct objc_property *objc_property_t;Copy the code

Sometimes there is a need to know the name of each property in the current class (e.g. dictionary to model, dictionary Key does not match the property name of the model object) and we can get some information about the class through a series of methods in Runtime

  • Property list
  • Methods list
  • List of member variables
  • List of protocols followed
/// @dynamic propertyTemps; - (NSArray<NSString*>*)propertyTemps{ NSMutableArray *temps = [NSMutableArray array]; unsigned int outCount, i; Class targetClass = [self class]; while (targetClass ! = [NSObject class]) { objc_property_t *properties = class_copyPropertyList(targetClass, &outCount); for (i = 0; i < outCount; i++) { objc_property_t property = properties[i]; const char *char_f = property_getName(property); NSString *propertyName = [NSString stringWithUTF8String:char_f]; if (propertyName) [temps addObject:propertyName]; } free(properties); targetClass = [targetClass superclass]; } return temps.mutableCopy; } @dynamic ivarTemps; - (NSArray<NSString*>*)ivarTemps{ unsigned int count; Ivar *ivar = class_copyIvarList([self class], &count); NSMutableArray *temp = [NSMutableArray arrayWithCapacity:count]; for (int i = 0; i < count; i++) { const char *char_f = ivar_getName(ivar[i]); NSString *name = [NSString stringWithCString:char_f encoding:NSUTF8StringEncoding]; if (name) [temp addObject:name]; } return temp.mutableCopy; } @dynamic methodTemps; - (NSArray<NSString*>*)methodTemps{ unsigned int count; Method *method = class_copyMethodList([self class], &count); NSMutableArray *temp = [NSMutableArray arrayWithCapacity:count]; for (int i = 0; i < count; i++) { NSString *name = NSStringFromSelector(method_getName(method[i])); if (name) [temp addObject:name]; } return temp.mutableCopy; } @dynamic protocolTemps; - (NSArray<NSString*>*)protocolTemps{ unsigned int count; __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count); NSMutableArray *temp = [NSMutableArray arrayWithCapacity:count]; for (unsigned int i = 0; i<count; i++) { const char *protocolName = protocol_getName(protocolList[i]); NSString *name = [NSString stringWithCString:protocolName encoding:NSUTF8StringEncoding]; if (name) [temp addObject:name]; } return temp.mutableCopy; }Copy the code

Actual combat example: realize NSCoding automatic archiving and reconciliation files

@implementation KJTestModel

- (void)encodeWithCoder:(NSCoder *)encoder{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([KJTestModel class], &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [encoder encodeObject:value forKey:key];
    }
    free(ivars);
}
- (id)initWithCoder:(NSCoder *)decoder{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([KJTestModel class], &count);
        for (int i = 0; i<count; i++) {
            Ivar ivar = ivars[i];
            const char *name = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [decoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivars);
    }
    return self;
}
@end
Copy the code

The method call

  • If you call an instance method from an instance object, you will operate on the object (class object) to which the instance’s ISA pointer points.
  • If a class method is called, the operation is performed on the object to which the isa pointer to the class object points (that is, a metaclass object).
  • The object calls a method through three phases

ResolveInstanceMethod (resolveInstanceMethod) or resolveClassMethod (resolveClassMethod) First to determine whether there are other objects can handle forwardingTargetForSelector () method returns a new object, if there is no new object for processing, will be called methodSignatureForSelector method returns the method signature, Then call the forwardInvocation

Here’s a simple way to prevent calls to unimplemented methods from crashing: Choose to do processing, at the end of the forward step methodSignatureForSelector: news function parameters and return values, and then [self respondsToSelector: aSelector] judgment whether to have the method, if there is no return function signatures, Create an NSInvocation object and send it to the forwardInvocation

@implementation NSObject (KJUnrecognizedSelectorException) + (void)kj_openUnrecognizedSelectorExchangeMethod{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ kExceptionMethodSwizzling(self, @selector(methodSignatureForSelector:), @selector(kj_methodSignatureForSelector:)); kExceptionMethodSwizzling(self, @selector(forwardInvocation:), @selector(kj_forwardInvocation:)); kExceptionClassMethodSwizzling(self, @selector(methodSignatureForSelector:), @selector(kj_methodSignatureForSelector:));  kExceptionClassMethodSwizzling(self, @selector(forwardInvocation:), @selector(kj_forwardInvocation:)); }); } - (NSMethodSignature*)kj_methodSignatureForSelector:(SEL)aSelector{ if ([self respondsToSelector:aSelector]) { return [self kj_methodSignatureForSelector:aSelector]; } return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } - (void)kj_forwardInvocation:(NSString invocation *)anInvocation{NSString * String = [NSString stringWithFormat:@"🍉🍉 crash: NSStringFromClass([self class])]; NSString * Reason = [NSStringFromSelector(anInvocation. Selector) stringByAppendingString:@" 🚗🚗 instance method not found 🚗🚗"]; NSException *exception = [NSException exceptionWithName:@" not found "Reason :reason userInfo:@{}]; [KJCrashManager kj_crashDealWithException:exception CrashTitle:string]; } + (NSMethodSignature*)kj_methodSignatureForSelector:(SEL)aSelector{ if ([self respondsToSelector:aSelector]) { return [self kj_methodSignatureForSelector:aSelector]; } return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } + (void)kj_forwardInvocation:(NSString Invocation*)anInvocation{NSString * String = [NSString stringWithFormat:@"🍉🍉 crash: NSStringFromClass([self class])]; NSString * Reason = [NSStringFromSelector(anInvocation. Selector) stringByAppendingString:@" 🚗🚗 class method not found 🚗🚗"]; NSException *exception = [NSException exceptionWithName:@" not found "Reason :reason userInfo:@{}]; [KJCrashManager kj_crashDealWithException:exception CrashTitle:string]; } @endCopy the code

Overrides a method of the parent class, not overwriting the method of the parent class, but only finding the method in the current class object and not looking for the parent class. If you want to call the overridden implementation of a method’s parent class, simply use the super compiler flag, which skips finding methods in the current class object at runtime

High-frequency call method

Runtime source IMP as a function pointer to the implementation of the method. It allows us to bypass the process of sending messages to improve the efficiency of function calls

void (*test)(id, SEL, BOOL);
test = (void(*)(id, SEL, BOOL))[target methodForSelector:@selector(xxx:)];
for (int i = 0; i < 100000; i++) {
    test(targetList[i], @selector(xxx:), YES);
}
Copy the code

Intercept calls

As mentioned in the method call, if a method is not found, it will turn to the interception call, so what is the interception call? Intercepting calls is, you have a chance to do that by overriding all four methods in NSObject before the method that you can’t find is called crashes.

  • resolveClassMethod:When you call a nonexistent class method, this method is called and returns NO by default. You can add your own handler and return YES.
  • resolveInstanceMethod:It is similar to the first method, except that it deals with instance methods.

The last two methods need to be forwarded to other classes for processing

  • forwardingTargetForSelector:To redirect your nonexistent method to another class that declares the method, you just need to return a target with the method.
  • forwardInvocation:Package your non-existent method invocation to you as NSInvocation. After you’ve done your own processing, call invokeWithTarget: to have a target trigger the method.

Dynamic addition method

Overrides the intercepting call and returns YES. What do we do? Dynamically add a method based on the SEL type selector passed in

// Implicitly call a nonexistent method [target performSelector:@selector(XXX: withObject:@"test"];Copy the code

The intercepting method is then overridden inside the target object, adding the method dynamically.

void testAddMethod(id self, SEL _cmd, NSString *string){
    NSLog(@"xxxx");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if ([NSStringFromSelector(sel) isEqualToString:@"xxx:"]) {
        class_addMethod(self, sel, (IMP)testAddMethod, "v@:*");
    }
    return YES;
}
Copy the code

Where the four parameters of class_addMethod are:

  • Class cls Which class to add a method to, in this case self
  • SEL nameThe added method, in this case, is the overridden intercepting call to the selector that’s passed in.
  • IMP impC method implementations are available directly. The realization of OC acquisition method+ (IMP)instanceMethodForSelector:(SEL)aSelector
  • "v@:*"The signature of the method, which represents the method with one parameter

Dynamic inheritance

Dynamic inheritance modifies the ISA pointer to an NSBundle object to point to a subclass KJLanguageManager, which calls the subclass’s methods

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        object_setClass([NSBundle mainBundle], [KJLanguageManager class]);
    });
}
Copy the code

associations

To declare an attribute to a class is to add an association to the class, not to add the value’s memory space to the class storage space. There is a requirement that the system’s classes do not meet your requirements and you need to add an additional attribute, and the general solution to this is inheritance. But adding just one attribute to a class always feels too cumbersome. This can be handled using the Runtime’s associated object

// Global variable - associatedObjectKey static char associatedObjectKey; // Set the association object objc_setAssociatedObject(target, &associatedObjectKey, @" association test ", OBJC_ASSOCIATION_RETAIN_NONATOMIC); NSString *string = objc_getAssociatedObject(Target, &associateDobJectKey); NSLog(@"----:%@", string);Copy the code

Objc_setAssociatedObject takes four parameters:

  • id objectTo whom to set the associated object.
  • const void *keyThe unique key of the associated object, which is used when retrieving it.
  • id valueAssociated object.
  • objc_AssociationPolicyAssociation policies include the following:
enum {
    OBJC_ASSOCIATION_ASSIGN = 0,
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
    OBJC_ASSOCIATION_RETAIN = 01401,
    OBJC_ASSOCIATION_COPY = 01403 
};
Copy the code

In fact, you can also write the methods of adding and retrieving associated objects in the category, which is easy to use.

// Get the associated object - (CGFloat)timeInterval{return [objc_getAssociatedObject(self, _cmd) doubleValue]; } // add an associated object - (void)setTimeInterval:(CGFloat)timeInterval{objc_setAssociatedObject(self, @selector(timeInterval), @(timeInterval), OBJC_ASSOCIATION_ASSIGN); }Copy the code

Note: we use the address of the timeInterval method as the only key. _cmd represents the address of the current calling method.

Methods exchange

Method swapping, as the name implies, is the swapping of implementations of two methods

The Runtime obtains the address of the method implementation, and then dynamically swaps the functions of the two methods

Exchange instance method

void kExceptionMethodSwizzling(Class clazz, SEL original, SEL swizzled){ Method originalMethod = class_getInstanceMethod(clazz, original); Method swizzledMethod = class_getInstanceMethod(clazz, swizzled); if (class_addMethod(clazz, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) { class_replaceMethod(clazz, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}Copy the code

Exchange class method

void kExceptionClassMethodSwizzling(Class clazz, SEL original, SEL swizzled){ Method originalMethod = class_getClassMethod(clazz, original); Method swizzledMethod = class_getClassMethod(clazz, swizzled); Class metaclass = objc_getMetaClass(NSStringFromClass(clazz).UTF8String); if (class_addMethod(metaclass, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) { class_replaceMethod(metaclass, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}Copy the code

Method swapping seems to me more like the best technique for implementing an idea: AOP swaps faceted programming and then calls itself back, making sure it only swaps once or it gets messy

For example, if you switch methods A and B, the code in method B is executed when method A is called, and the code in method B is executed when method A is called.

// Call the old method and the new method to swap and handle crashes. + (void)load {// Execute the dispatch_once_t onceToken static dispatch_once_t onceToken; Dispatch_once (&oncetoken, ^{dispatch_once(&oncetoken, ^{dispatch_once(&oncetoken, ^{ B_sel = @selector(kj_objectAtIndex:); A_Method = class_getInstanceMethod(objc_getClass("__NSArrayI"), A_sel); B_Method = class_getInstanceMethod(objc_getClass("__NSArrayI"), B_sel); // First add the method dynamically, the implementation is the method to be swapped, BOOL isAdd = class_addMethod(self, A_sel, method_getImplementation(B_Method), method_getTypeEncoding(B_Method)); If (isAdd) {// If successful, // Replace the swapped method implementation with the non-existent implementation class_replaceMethod(self, B_sel, method_getImplementation(A_Method), method_getTypeEncoding(A_Method)); }else{// otherwise, method_exchangeImplementations(A_Method, B_Method); }}); } - (instancetype)kj_objectAtIndex:(NSUInteger)index{ NSArray *temp = nil; @try { temp = [self kj_objectAtIndex:index]; }@catch (NSException *exception) {NSString *string = @"🍉🍉 crash: "; If (self.count == 0) {string = [string stringByAppendingString:@" array number = 0 "]; }else if (self.count <= index) {string = [string stringByAppendingString:@" arrayindex "]; } [KJCrashManager kj_crashDealWithException:exception CrashTitle:string]; }@finally { return temp; }}Copy the code

Note: this is called internallytemp = [self kj_objectAtIndex:index];It looks a little bit like a recursive loop, but it’s not, because it’s swapping methods, it’s actually the original method that was calledobjectAtIndex:

For example, if I move the Item on the CollectionView without affecting the rotation of the normal CollectionView, I can get the Touch event and pass it as a callback. What would you do if I were you?

+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self kj_swizzleMethod:@selector(touchesBegan:withEvent:) Method:@selector(kj_touchesBegan:withEvent:)]; [self kj_swizzleMethod:@selector(touchesMoved:withEvent:) Method:@selector(kj_touchesMoved:withEvent:)]; [self kj_swizzleMethod:@selector(touchesEnded:withEvent:) Method:@selector(kj_touchesEnded:withEvent:)]; [self kj_swizzleMethod:@selector(touchesCancelled:withEvent:) Method:@selector(kj_touchesCancelled:withEvent:)]; }); } - (void)kj_touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event{ if (self.kOpenExchange && self.moveblock)  { CGPoint point = [[touches anyObject] locationInView:self]; self.moveblock(KJMoveStateTypeBegin,point); } [self kj_touchesBegan:touches withEvent:event]; } - (void)kj_touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event{ if (self.kOpenExchange && self.moveblock)  { CGPoint point = [[touches anyObject] locationInView:self]; self.moveblock(KJMoveStateTypeMove,point); } [self kj_touchesMoved:touches withEvent:event]; } - (void)kj_touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event{ if (self.kOpenExchange && self.moveblock)  { CGPoint point = [[touches anyObject] locationInView:self]; self.moveblock(KJMoveStateTypeEnd,point); } [self kj_touchesEnded:touches withEvent:event]; } - (void)kj_touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event{ if (self.kOpenExchange && self.moveblock) { CGPoint point = [[touches anyObject] locationInView:self]; self.moveblock(KJMoveStateTypeCancelled,point); } [self kj_touchesEnded:touches withEvent:event]; }Copy the code

This is the end of the Runtime introduction, there are related to the supplement, writing articles is not easy, also please click **Little stars* * portal