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 selfSEL name
The added method, in this case, is the overridden intercepting call to the selector that’s passed in.IMP imp
C 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 object
To whom to set the associated object.const void *key
The unique key of the associated object, which is used when retrieving it.id value
Associated object.objc_AssociationPolicy
Association 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