Good articles to my personal technology blog: https://cainluo.github.io/15034954202343.html
We have come to the third part of our tutorial on loading technology. If you haven’t seen the previous part, you can go to playing iOS development: Loading Technology in iOS Development – RunTime(2).
What is it this time? Let’s take a look
Reprint statement: if you need to reprint this article, please contact the author, and indicate the source, and can not modify this article without authorization.
Automatic archive
What do we do when we file? Take a look at the code:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self = [super init]) {
NSObject *obj = [decoder decodeObjectForKey:@"keyName"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
NSObject *obj = [[NSObject alloc] init];
[coder encodeObject:obj
forKey:@"keyName"];
}
Copy the code
It seems like there’s no problem, but what if we have a hundred files, then we have to write them one by one, a hundred each, and they’re weak.
There must be a better way to do this, and the Runtime can be archived after several steps:
- through
class_copuIvarList
Method to get the currentModel
All member variables of. - through
ivar_getName
Method to get the name of a member variable. - through
KVC
To read theModel
Property value, and finally giveModel
Assigns the property of the.
For example, 🌰 creates a new Model class named CoderModel with the following code:
#import "CoderModel.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation CoderModel
- (void)encodeWithCoder:(NSCoder *)coder{
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[coder encodeObject:value
forKey:key];
}
free(vars);
}
- (nullable instancetype)initWithCoder:(NSCoder *)decoder{
if (self = [super init]) {
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [decoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(vars);
}
return self;
}
@end
Copy the code
- Along with
KVC
As you know,KVC
If you can findkey
The properties of thesetter
Method is calledsetter
Methods. - If you don’t find it
setter
, will look for the member variableskey
Or member variables_key
- So we don’t have to do anything else with prefixes here
_
Prefix the name of the member variable
Here we also need to write a Category to transform the model, find yourself in the project, and finally implement:
- (void)coderModel {
CoderModel *coderModel = [CoderModel objectWithKeyValues:self.dictionary];
NSLog(@"%@, %ld, %ld", coderModel.name, coderModel.age, coderModel.phoneNumber);
NSDictionary *dictionary = [coderModel keyValuesWithObject];
NSLog(@"dictionary is %@", dictionary);
}
Copy the code
2017-08-23 22:27:51.349 1.RunTime[36904:3052474] 1.RunTime[36904:3052474] Dictionary is {age = 18; name = "\U5c0f\U660e"; phoneNumber = 13800138000; }Copy the code
Dictionaries interact with models
Now that I’m done with archiving, I’m going to transfer the dictionary and model to each other.
We used to assign values to the model as:
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if (self = [super init]) {
self.age = dictionary[@"age"];
self.name = dictionary[@"name"];
}
return self;
}
Copy the code
It seems that there is no problem, but in fact it is the same problem as archiving. After using MJMJExtension and YYModel, I found that there are other ways to parse the model.
Let’s look at the process:
- When the dictionary turns to the model
- According to the dictionary
key
generatesetter
methods - use
objc_msgSend
callsetter
Methods forModel
Is assigned to or used byKVC
- According to the dictionary
+ (id)objectWithKeyValues:(NSDictionary *)dictionary {
id objc = [[self alloc] init];
for (NSString *key in dictionary.allKeys) {
id value = dictionary[key];
// Determine whether the current attribute belongs to Model
objc_property_t property = class_getProperty(self, key.UTF8String);
unsigned int outCount = 0;
objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount);
objc_property_attribute_t attribute = attributeList[0];
NSString *typeString = [NSString stringWithUTF8String:attribute.value];
if ([typeString isEqualToString:@"@\"CoderModel\""]) {
value = [self objectWithKeyValues:value];
}
// Generate setter methods and call them with objc_msgSend
NSString *methodName = [NSString stringWithFormat:@"set%@%@:", [key substringToIndex:1].uppercaseString, [key substringFromIndex:1]];
SEL setter = sel_registerName(methodName.UTF8String);
if ([objc respondsToSelector:setter]) {((void (*) (id,SEL,id)) objc_msgSend) (objc,setter,value);
}
free(attributeList);
}
return objc;
}
Copy the code
- When the model turns to the dictionary
- call
class_copyPropertyList
Method to get the currentModel
All attributes of - Call property_getName to get the property name
- Generated from the property name
getter
methods - use
objc_msgSend
callgetter
Method to get the value of an attribute or use theKVC
- call
- (NSDictionary *)keyValuesWithObject {
unsigned int outCount = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
for (int i = 0; i < outCount; i ++) {
objc_property_t property = propertyList[i];
// Generate getter methods and call them with objc_msgSend
const char *propertyName = property_getName(property);
SEL getter = sel_registerName(propertyName);
if ([self respondsToSelector:getter]) {
id value = ((id (*) (id,SEL)) objc_msgSend) (self.getter);
// Determine whether the current attribute belongs to Model
if ([value isKindOfClass:[self class]] && value) {
value = [value keyValuesWithObject];
}
if (value) {
NSString *key = [NSString stringWithUTF8String:propertyName];
[mutableDictionary setObject:value
forKey:key];
}
}
}
free(propertyList);
return mutableDictionary;
}
Copy the code
Dynamic method parsing
That’s basically the end of the story, and the last is to add to what I didn’t say in front.
Usually we call a method that doesn’t exist in the object and it crashes with an error message:
unrecognized selector sent to instance 0x0bcde09abc0
Copy the code
But before the crash, Runtime gives us a chance to do dynamic parsing, which goes something like this:
- detection
selector
Do you need to ignore, like inmacOS
What will ARC not care aboutretain
.release
These are the methods. - detection
target
Whether fornil
.Objective-C
Is allowed to onenil
The object can execute any method without collapsing, so it is ignored, but inSwift
It’s going to be a problem. - If you pass both of these, you’re going to look for this class
IMP
First, from thecache
Inside to find, found to jump to the corresponding function to execute.- if
cache
Can not find a method to be published
- if
- If you can’t find it in the distribution table, you’ll look in the distribution table of the superclass and keep finding it
NSObject
. - If not, the message will start forwarding
General process of forwarding:
The picture was stolen from some blog post on the Internet. I don’t want to draw it.
- Into the
resolveInstanceMethod:
Method, specify whether the method is dynamically added.- If the return is
NO
, will go to the next step - If the return
YES
, will passclass_addMethod
The function dynamically adds methods, the message is processed, and the process is complete.
- If the return is
resolveInstanceMethod:
Method returnsNO
, will enterforwardingTargetForSelector
Method, this isRuntime
Give us a second chance to specify which object responds to thisselector
.- If the return
nil
, will go to the next step, return an object, willCalls a method on that object
.
- If the return
forwardingTargetForSelector:
Return isnil
If so, we need to passmethodSignatureForSelector:
To specify the method signature.- if
methodSignatureForSelector:
returnnil
Return the method signature and proceed to the next step.
- if
- when
methodSignatureForSelector:
Method is called when the signature is returnedforwardInvocation:
Method, we can go throughanInvocation
Object to do processing, for exampleModify the implementation method
.Modifying the response object
And so on. - If at the end, the message is still not being responded to or processed, it will crash.
Let’s look at the code:
#import "BicycleModel.h"
#import "SportsCarModel.h"
#import <objc/runtime.h>
@implementation BicycleModel
- (void)ridingSpeed {
NSLog(@"Slow Ride");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
#if 0
return NO;
#else
class_addMethod(self, sel, class_getMethodImplementation(self, sel_registerName("ridingSpeed")), "v@:");
return [super resolveInstanceMethod:sel];
#endif
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
#if 0
return nil;
#else
return [[SportsCarModel alloc] init];
#endif
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
return [anInvocation invokeWithTarget:[[SportsCarModel alloc] init]];
}
Copy the code
#import "SportsCarModel.h"
@implementation SportsCarModel
- (void)rapidAcceleration {
NSLog(@"High speed");
}
@end
Copy the code
Execution method:
- (void)dynamicAnalysis {
BicycleModel *bicycle = [[BicycleModel alloc] init];
((void (*) (id, SEL)) objc_msgSend) (bicycle, sel_registerName("rapidAcceleration"));
}
Copy the code
Printed results:
2017-08-24 00:09:15.238 1.RunTime[37657:3122648] Slow Ride
Copy the code
The project address
The address of the project: https://github.com/CainRun/iOS-Project-Example/tree/master/RunTime/Three