A list,
1.1 What is Runtime
Runtime is a set of low-level PURE C API. OC code is eventually converted to Runtime code by the compiler. The message mechanism determines how functions are called, which is the basis of OC as a dynamic language.Copy the code
2.1 Basic principles of the Message mechanism
In object-C, Object method calls take the form of receiver Selector, which is essentially the process by which an Object sends a message at runtime.
The method call [Receiver Selector] is divided into two processes:
- Compilation phase
The [Receiver Selector] method is converted by the compiler into two cases:
1. Methods without parameters are compiled as objc_msgSend(receiver, selector) 2. Methods with arguments are compiled as objc_msgSend(recevier, selector, org1, org2...).Copy the code
- Runtime phase
The message receiver recever finds the corresponding selector in two cases:
1. The receiver can find the corresponding selector and directly execute the selector method of the receiver object. 2. The receiver cannot find the corresponding selector, and the message is forwarded or the corresponding implementation content of the selector is temporarily added to the receiver. Otherwise, the message crashesCopy the code
To sum up:
OC calls the method [Receiver Selector]. The compile phase determines which receiver to send the message to, but how the receiver responds is up to the runtime's discretionCopy the code
1.3 Concept analysis in Runtime
1.3.1 objc_msgSend
All Objective-C method calls are converted at compile time to calls to the C function objc_msgSend. Objc_msgSend (receiver, the selector); Is the [receiver selector]; The corresponding C functionCopy the code
1.3.2 Object
In objc/ Runtime.h, Object is defined as a pointer to the objC_Object structure, which has the following data structure:
Struct objc_object {Class _Nonnull ISA OBJC_ISA_AVAILABILITY; struct objc_object {Class _Nonnull isa OBJC_ISA_AVAILABILITY; }; //id is a pointer to the objc_object structure, that is, in the Runtime: typedef struct objc_object *id; //OC objects do not explicitly use Pointers, but after OC code is compiled into C, every OC object actually has a pointer to ISACopy the code
1.3.2 Class (Class)
In objc/ Runtime.h, Class is defined as a pointer to the objc_class structure, which has the following data structure:
Struct objc_class {Class _Nonnull isa; struct objc_class {Class _Nonnull isa; // The instance pointer to the objc_class structure#if ! __OBJC2__Class _Nullable super_class; // Pointer to parent const char * _Nonnull name; // Class name long version; // Class version information, default is 0 long info; // Class information, some bits for run-time use to identify long instance_size; // The size of the instance variable of the class; struct objc_ivar_list * _Nullable ivars; Struct objc_method_list * _Nullable * _Nullable methodLists; Struct objc_cache * _Nonnull cache; Struct objc_PROTOCOL_list * _Nullable protocols; // List of protocols to comply with#endif}; //class is a pointer to the objc_class structure, i.e., the Runtime: typedef struct objc_class * class;Copy the code
1.3.3 SEL (Method selector)
In objc/ Runtime. h, SEL (method selector) is defined as a pointer to the objc_selector structure:
typedef struct objc_selector *SEL; //Objective-C generates a unique integer identifier (address of type Int) for each method based on its name and argument sequence. This identifier is SELCopy the code
Note:
1. Methods with the same name in different classes have the same method selector. 2. Even in the same class, the same method name but different variable type can cause them to have the same method selector.Copy the code
There are generally three ways to obtain SEL:
OC, NSSelectorFromString(method name string).Runtime, sel_registerName(method name string)Copy the code
1.3.4 Ivar
In objc/ Runtime.h, Ivar is defined as a pointer to the objc_ivar structure, which has the following data structure:
struct objc_ivar {
char * Nullable ivar_name OBJC2UNAVAILABLE;
char * Nullable ivar_type OBJC2UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef LP64
int space OBJC2_UNAVAILABLE;
#endifTypedef struct objc_ivar *Ivar; typedef struct ojbcet_ivar;Copy the code
See the ivars member list in objc_class, where the element is Ivar, and you can look up its name in the class by instance. This process is called reflection. The following class_copyIvarList retrieves both instance variables and attributes:
Ivar *ivarList = class_copyIvarList([self class], &count);
for (int i= 0; i<count; i++) {
Ivar ivar = ivarList[i];
const char *ivarName = ivar_getName(ivar);
NSLog(@"Ivar(%d): %@", i, [NSString stringWithUTF8String:ivarName]); } free (ivarList);Copy the code
1.3.5 Method
In objc/ Runtime. h, Method is defined as a pointer to the objc_method structure. See methodLists in the objct_class definition, The objc_method structure has the following data structure:
struct objc_method { SEL _Nonnull method_name; Char * _Nullable method_types; // Method type IMP _Nonnull method_IMP; // method implementation}; Typedef struct objc_method *Method;Copy the code
Three ways to interact with Runtime
2.1 OC Source Code
OC code is converted by the compiler at compile time. Classes, methods, protocols, and so on in the OC are defined by data structures in the Runtime. So in daily project development, when OC is used to code, it is already interacting with the Runtime, but the process is not felt by the developerCopy the code
2.2 NSObject methods
The most important feature of the Runtime is the dynamic nature of the OC language.Copy the code
NSObject, the root class of most Objective-C class inheritance systems, has some very runtime dynamic methods of its own, such as:
-description: returns the description of the current class. 3. -iskindofClass: and -ismemberofClass: 4. -conformsToProtocol: checks whether the object implements the methods of the specified protocol class. 5. -methodForSelector: Returns the address of the specified method implementation.Copy the code
2.3 Using the Runtime Function
The Runtime system is a dynamic shared library consisting of a series of functions and data structures with common interfaces. The header file is stored in the /usr/include/objc directory.Copy the code
Referencing the Runtime header in the project code can also achieve the same effect as OC code:
// equivalent to: Class = [UIView Class]; Class viewClass = objc_getClass("UIView"); UIView *view = [UIView alloc]; UIView *view = ((id (*)(id, SEL))(void *)objc_msgSend)((id)viewClass, sel_registerName("alloc")); UIView *view = [view init]; ((id (*)(id, SEL))(void *)objc_msgSend)((id)view, sel_registerName("init"));
Copy the code
3. Runtime message forwarding
3.1 Dynamic method parsing and message forwarding
- Dynamic method resolution: Add methods dynamically
The Runtime is powerful enough to dynamically add an unimplemented method at Runtime. This feature has two main applications:
1. Dynamically add unrealized methods to solve the problem of error reporting because the method is not found in the code; 2. If a class has many methods, loading them into memory at the same time will consume resources, you can use dynamic parsing to add methodsCopy the code
Methods Dynamic analysis is mainly used as follows:
/ / OC method: + (BOOL)resolveClassMethod:(SEL) SEL // + (BOOL)resolveInstanceMethod:(SEL) SEL Runtime: /** Runtime: @param CLS Class to which the method is added @param name Selector method name @param IMP pointer to the function that implements the method @param types Imp function implementation return value and parameter type @return*/ BOOL class_addMethod(Class _Nullable CLS, SEL _Nonnull NAME, IMP _Nonnull IMP, const char * _Nullable types)Copy the code
- Solution No response crash problem
OC method execution is actually a process of sending messages, if the method is not implemented, you can use method dynamic resolution and message forwarding to avoid program crash, which mainly involves the following process of processing unimplemented messages:
Other methods that may be used in this process are:
Example:
#import "ViewController.h"
#import <objc/runtime.h>@interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Perform fun [self performSelector:@selector(fun)]; + (BOOL)resolveInstanceMethod:(SEL) SEL {if(sel = = @ the selector (fun)) {/ / if it is executed fun function, dynamic analysis, specify the new IMP class_addMethod ([self class], sel, funMethod (IMP),"v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void funMethod(id obj, SEL _cmd) {
NSLog(@"funMethod"); } @end // 2019-09-01 23:24:34.911774+0800 XKRuntimeKit[3064:521123] funMethodCopy the code
You can view the following output logs:
Fun is not implemented, but funMethod is implemented by overriding resolveInstanceMethod:, adding object methods with class_addMethod, and executing. From the printed result, the funMethod method was successfully invoked.Copy the code
3.2 Message Receiver Redirection:
If +resolveInstanceMethod: or +resolveClassMethod: no other function implementation was added in the previous step, the runtime proceeds to the next step: message recipient redirection.
Realized – forwardingTargetForSelector: if the current object, the Runtime will call this method, allows the recipient of the message forwarded to other objects, the main methods are as follows:
/ / a redirect message receiver class methods, return a class - (id) forwardingTargetForSelector aSelector: (SEL) / / redirect message receiver instance methods, Returns an instance object - (id) forwardingTargetForSelector aSelector: (SEL)Copy the code
Example:
#import "ViewController.h"
#import <objc/runtime.h>
@interface Person : NSObject
- (void)fun;
@end
@implementation Person
- (void)fun {
NSLog(@"fun"); } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Perform fun [self performSelector:@selector(fun)]; } + (BOOL)resolveInstanceMethod:(SEL)sel {returnYES; / / for the next message recipient redirect} / / message recipient redirect - (id) forwardingTargetForSelector aSelector: (SEL) {if (aSelector == @selector(fun)) {
return[[Person alloc] init]; Return the Person object to receive the message}return[super forwardingTargetForSelector:aSelector]; } // 2019-09-01 23:24:34.911774+0800 XKRuntimeKit[3064:521123] funCopy the code
You can view the following output logs:
While the current ViewController does not implement fun, +resolveInstanceMethod: does not add any other function implementations. But we through forwardingTargetForSelector ViewController in the current method is forwarded to the Person object to performCopy the code
Through forwardingTargetForSelector can modify the message receiver, this method returns the parameter is an object, if the object is nil, also is not self, system will run forward messages to this object. Otherwise, proceed to the next step: the message redirection process
3.3 Message Redirection:
If after a redirect message dynamic parsing, the receiver, the Runtime system still can’t find the corresponding methods to the response message, the Runtime system will use – methodSignatureForSelector: method for function arguments and return values of type.
The process:
1. If - methodSignatureForSelector: After returning an NSMethodSignature object (function signature), the Runtime system creates an NSInvocation object and uses -ForwardInvocation: The message notifies the current object, giving the message sender one last chance to find the IMP. 2. If - methodSignatureForSelector: returns nil. The Runtime system will send - doesNotRecognizeSelector: news, the program has crashedCopy the code
So the message can be forwarded in the -forwardInvocation: method.
Its main methods:
// message redirection - (void)forwardInvocation:(NSInvocation *)anInvocation; / / get function parameters and return values of type, return the signature - (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector;Copy the code
Example:
#import "ViewController.h"
#import <objc/runtime.h>
@interface Person : NSObject
- (void)fun;
@end
@implementation Person
- (void)fun {
NSLog(@"fun"); } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Perform fun [self performSelector:@selector(fun)]; } + (BOOL)resolveInstanceMethod:(SEL)sel {returnYES; / / for the next message recipient redirect} - (id) forwardingTargetForSelector aSelector: (SEL) {returnnil; / / for the next message redirect} / / gain function parameters and return values of type, return the signature - (NSMethodSignature *) methodSignatureForSelector aSelector: (SEL) {if ([NSStringFromSelector(aSelector) isEqualToString:@"fun"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return[super methodSignatureForSelector:aSelector]; } // message redirection - (void)forwardInvocation:(NSInvocation *)anInvocation {SEL SEL = anInvocation. Person *p = [[Person alloc] init];if([p respondsToSelector:sel]) {// Determine if the Person object method can respond to sel [anInvocation invokeWithTarget:p]; // If it can respond, the message is forwarded to other objects for processing}else{ [self doesNotRecognizeSelector:sel]; }} @end // 2019-09-01 23:24:34.911774+0800 XKRuntimeKit[30032:8724248] funCopy the code
You can view the following output logs:
The fun function is invoked from the Person object in the -Forward Invocation: methodCopy the code
Since – forwardingTargetForSelector: and – forwardInvocation: all messages can be forwarded to other objects, then where is the difference between the two?
The difference is that - forwardingTargetForSelector: only forward the message to an object. The -forwardInvocation: The message can be forwarded to multiple objects.Copy the code
4. Application of Runtime
4.1 Dynamic method exchange
Implementing Method Swizzling is one of the most well-known application scenarios in Runtime. It works as follows:
The Runtime obtains the address of the method implementation, and then dynamically swaps the functions of the two methodsCopy the code
Key methods:
Nullable class_getClassMethod(Class _Nullable CLS, SEL _Nonnull name) // Mthod Method _Nullable class_getInstanceMethod(Class _Nullable CLS, Void method_exchangeImplementations(Method _Nonnull M1, Method _Nonnull m2)Copy the code
- Dynamic method exchange
#import "RuntimeKit.h"
#import <objc/runtime.h>
@implementation RuntimeKit
- (instancetype)init
{
self = [super init];
ifMethod methodA = class_getInstanceMethod([self class], @selector()testA));
Method methodB = class_getInstanceMethod([self class], @selector(testB));
method_exchangeImplementations(methodA, methodB);
[self testA];
[self testB];
}
return self;
}
- (void)testA{
NSLog(@"I am method A");
}
- (void)testB{
NSLog(@"I'm method B."); } @end 2019-09-01 21:25:32.858860+0800 XKRuntimeKit[1662:280727] 2019-09-01 21:25:32.859059+0800 XKRuntimeKit[1662:280727] I am A methodCopy the code
- Intercept and replace system methods
#import "UIViewController+xk.h"
#import <objc/runtime.h>@implementation UIViewController (xk) + (void)load{// implementation UIViewController (xk) + (void)load{// implementation UIViewController (xk) + (void)load{// implementation UIViewController (xk) + (void)load{// implementation UIViewController (xk) + (void)load class], @selector(viewWillAppear:)); CustomMethod = class_getInstanceMethod([self class], @selector(run_viewWillAppear:)); // Check whether it exists or notif(! class_addMethod([self class], @selector(viewWillAppear:), method_getImplementation(customMethod), method_getTypeEncoding(customMethod))) { method_exchangeImplementations(sytemMethod, customMethod); }else{
class_replaceMethod([self class], @selector(run_viewWillAppear:), method_getImplementation(sytemMethod), method_getTypeEncoding(sytemMethod));
}
}
- (void)run_viewWillAppear:(BOOL)animated{
[self run_viewWillAppear:animated];
NSLog(@"I'm a run-time replacement method -viewWillAppear");
}
- (void)run_viewWillDisappear:(BOOL)animated{
[self run_viewWillDisappear:animated];
NSLog(@"I'm a run-time replacement method -viewWillDisappear"); } @end log output: 2019-09-01 21:36:55.610385+0800 XKRuntimeKit[1921:310118] I am run-time replacement method -viewWillAppearCopy the code
Introducing this classification, you can see from the execution results, but when the system’s controller executes viewWillAppear, it enters the replaced method run_viewWillAppear.
4.2 Add new attributes for categories
In daily development, it is common to use Category categories to extend functionality to some existing classes. Although inheritance can also add new methods to existing classes and has the advantage of adding attributes compared with categories, inheritance is a heavyweight operation after all, and adding unnecessary inheritance undoubtedly increases the complexity of code.
Unfortunately, the OC category does not support adding attributes directlyCopy the code
To add attributes to a class, we also use the Runtime’s Associated Objects feature, which allows us to associate arbitrary attributes to an object at Runtime:
@param object Specifies the object to which the associated attribute is to be set. @param key Specifies the key corresponding to the associated attribute. You can obtain this attribute by using the key. @param Value Value for the association attribute @param policy Storage policy for the association attribute (corresponding to the Property attribute assign,copy, Retain, etc.) OBJC_ASSOCIATION_ASSIGN @property(assign). Nonatomic OBJC_ASSOCIATION_RETAIN_NONATOMIC @ property (strong). Nonatomic OBJC_ASSOCIATION_COPY_NONATOMIC @ property (copy). Atomic OBJC_ASSOCIATION_RETAIN @ property (strong). Atomic OBJC_ASSOCIATION_COPY @ property (copy). */ void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) /** 2. Which object does the object obtain the associated property from? @param Key The key corresponding to the associated property @return*/ id _Nullable objc_getAssociatedObject(ID _Nonnull object, const void * _Nonnull key) /** 3 */ void objc_removeAssociatedObjects(id _Nonnull Object)Copy the code
Note:
A key corresponds to an association property, so we have to make sure that it's globally unique, and usually we use @ Selector (methodName) as a keyCopy the code
Example:
Add a name attribute to UIViewController+xk.h:
@interface UIViewController (xk) // New property: name @property(nonatomic,copy)NSString * name; - (void)clearAssociatedObjcet; @endCopy the code
Add the corresponding implementation to UIViewController+xk.m:
#import "UIViewController+xk.h"
#import <objc/runtime.h>
@implementation UIViewController (xk)
//setMethods - (void)setName:(NSString *)name{ objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } //get method - (NSString *)name{returnobjc_getAssociatedObject(self, @selector(name)); } // Add a custom method that clears all associated attributes - (void)clearAssociatedObjcet{objc_removeAssociatedObjects(self); } @endCopy the code
Perform tasks:
ViewController * vc = [ViewController new];
vc.name = @I'm the root controller;
NSLog(@"Get association attribute name: %@",vc.name);
[vc clearAssociatedObjcet];
NSLog(@"Get association attribute name: %@",vc.name); Log output: 2019-09-01 21:50:05.162915+0800 XKRuntimeKit[2066:335327] I am the root controller 2019-09-01 21:50:05.163080+0800 XKRuntimeKit[2066:335327]Copy the code
Also, using the runtime, you can add methods to a class that you don’t already have, such as adding click events to UIView:
#import <objc/runtime.h>static char onTapGestureKey; static char onTapGestureBlockKey; @ implementation UIView (Gesture) / / add a dab hand gestures - (void) addTapGestureActionWithBlock: (onGestureActionBlock) block { UITapGestureRecognizer *gesture = objc_getAssociatedObject(self, &onTapGestureKey); self.userInteractionEnabled = YES;if(! gesture){ gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(xk_handleActionForTapGesture:)]; [self addGestureRecognizer:gesture]; objc_setAssociatedObject(self, &onTapGestureKey, gesture, OBJC_ASSOCIATION_RETAIN); } // Add the click gesture response block property objc_setAssociatedObject(self, &onTapGestureBlockKey, block, OBJC_ASSOCIATION_COPY); } / / click callback - (void) xk_handleActionForTapGesture: (UITapGestureRecognizer *) sender {onGestureActionBlock block = objc_getAssociatedObject(self, &onTapGestureBlockKey);if (block) block(sender);
}
@end
Copy the code
However, when adding a delegate attribute to a class using the runtime, you need to pay attention to the problem of circular application. Because the attribute added during the runtime is the retain operation, the corresponding delegate will not be released during the execution, which will cause a crash. To solve this problem, you can perform the following modification:
Scenario: Add UIViewemptyDataDelegate
Empty page proxy to handle the display of some abnormal cases
Add an emptyDataDelegate property to UIView+EmptyDataSet. H:
@protocol XKEmptyDataSetDelegate <NSObject> @optional // placeholder text - (NSString*)placeholderForEmptyDataSet:(UIScrollView*)scrollView; @interface UIView (EmptyDataSet) @property (nonatomic,weak) id<XKEmptyDataSetDelegate>emptyDataDelegate; @endCopy the code
In UIView + EmptyDataSet. M XKEmptyDataWeakObjectContainer gain its methods:
/ / weak reference agency @ interface XKEmptyDataWeakObjectContainer: NSObject @ property (nonatomic, weak,readonly)id weakObject;
- (instancetype)initWithWeakObject:(id)object;
@end
@implementation XKEmptyDataWeakObjectContainer
- (instancetype)initWithWeakObject:(id)object{
self = [super init];
if (self) {
_weakObject = object;
}
returnself; } @end static char xk_EmptyDataSetDelegateKey; @implementation UIView (EmptyDataSet) - (void)setEmptyDataDelegate:(id<XKEmptyDataSetDelegate>)emptyDataDelegate{
objc_setAssociatedObject(self, &xk_EmptyDataSetDelegateKey, [[XKEmptyDataWeakObjectContainer alloc] initWithWeakObject:emptyDataDelegate], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id<XKEmptyDataSetDelegate>)emptyDataDelegate{
XKEmptyDataWeakObjectContainer * container = objc_getAssociatedObject(self, &xk_EmptyDataSetDelegateKey);
return container.weakObject;
}
@end
Copy the code
4.3 Obtaining detailed class attributes
- Get property list
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"PropertyName(%d): %@",i,[NSString stringWithUTF8String:propertyName]);
}
free(propertyList);
Copy the code
- Gets all member variables
Ivar *ivarList = class_copyIvarList([self class], &count);
for (int i= 0; i<count; i++) {
Ivar ivar = ivarList[i];
const char *ivarName = ivar_getName(ivar);
NSLog(@"Ivar(%d): %@", i, [NSString stringWithUTF8String:ivarName]);
}
free(ivarList);
Copy the code
- Get all methods
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i = 0; i<count; i++) {
Method method = methodList[i];
SEL mthodName = method_getName(method);
NSLog(@"MethodName(%d): %@",i,NSStringFromSelector(mthodName));
}
free(methodList);
Copy the code
- Gets all the protocols that are currently followed
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (int i=0; i<count; i++) {
Protocol *protocal = protocolList[i];
const char *protocolName = protocol_getName(protocal);
NSLog(@"protocol(%d): %@",i, [NSString stringWithUTF8String:protocolName]); } free(propertyList); // To use Copy in C, release Pointers to prevent memory leaksCopy the code
4.4 Solve the efficiency problem of frequent invocation of the same method
Runtime source IMP as a function pointer to the implementation of the method. It allows you to bypass the process of sending messages to improve the efficiency of function calls. This is useful when a method needs to be repeatedly called in large numbers, as follows:
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);
Copy the code
4.5 Dynamically Manipulating Properties
- Modifying private properties
Scene:
We use the Person class from a third-party framework and want to change its proprietary nickName for special needs. In this case, we can use the Runtime to dynamically modify the property of the object.Copy the code
Person *ps = [[Person alloc] init];
NSLog(@"nickName: %@",[ps valueForKey:@"nickName"]); // unsigned int count; // unsigned int count; Ivar *ivarList = class_copyIvarList([ps class], &count);for(int i= 0; i<count; Ivar = ivarList[I]; Ivar = ivarList[I]; const char *ivarName = ivar_getName(ivar); NSString *propertyName = [NSString stringWithUTF8String:ivarName];if ([propertyName isEqualToString:@"_nickName"] {// Step 3: match the corresponding attribute, then modify; Note that the attribute has the underscore object_setIvar(ps, ivar, @"allenlas");
}
}
NSLog(@"nickName: %@",[ps valueForKey:@"nickName"]); //allenlas
Copy the code
- Improved iOS archiving and archiving
Archiving is a common lightweight file storage method, but it has a disadvantage:
During archiving, if a Model has multiple attributes, we have to deal with each attribute, which is tediousCopy the code
The archive operation mainly involves two methods: encodeObject and decodeObjectForKey. For these two methods, Runtime can be used to improve:
- (instanceType)initWithCoder:(NSCoder *)aDecoder{self = [super init];if (self) {
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
const char *ivarName = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:ivarName];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key]; } free(ivarList); // Release pointer}returnself; } // archive operation - (void)encodeWithCoder:(NSCoder *)aCoder{unsigned int count = 0; Ivar *ivarList = class_copyIvarList([self class], &count);for (NSInteger i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key]; } free(ivarList); // Release pointer}Copy the code
Testing:
//-- test archiving Person *ps = [[Person alloc] init]; ps.name = @"allenlas";
ps.age = 20;
NSString *temp = NSTemporaryDirectory();
NSString *fileTemp = [temp stringByAppendingString:@"person.archive"]; [NSKeyedArchiver archiveRootObject:ps toFile:fileTemp]; NSString *temp = NSTemporaryDirectory(); NSString *fileTemp = [temp stringByAppendingString:@"person.henry"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:fileTemp];
NSLog(@"The person - the name: % @, person - age: % ld." ",person.name,person.age); / / the person - name: allenlas, person - age: 20Copy the code
- Implement dictionary and model conversion
In daily project development, YYModel or MJExtension and other data objects returned by the interface are often used to implement model-to-model operations. For this, similar functionality can be implemented using KVC and Runtime. The following problems need to be solved in this process:
The implementation of Runtime is as follows:
With the help of Runtime, you can dynamically obtain the member list, iterate over all attributes in the model, and then find the corresponding value in the JSON dictionary with the acquired attribute name key. Then assign each corresponding Value to the model to complete the purpose of dictionary transformation.Copy the code
The json data:
{
"id":"10089"."name": "Allen"."age":"20"."position":"IOS Development Engineer"."address": {"country":"China"."province": "Guangzhou"
},
"tasks": [{"name":"Home"."desc":"App Homepage Development"}, {"name":"Train"."desc":"App Training Module Development"}, {"name":"Me"."desc":"Complete personal page"}}]Copy the code
- create
NSObject
The category ofNSObject+model
Is used to implement dictionary-to-model
// in NSObject+model.h NS_ASSUME_NONNULL_BEGIN //AAModel protocol method returns a dictionary, @protocol AAModel<NSObject> @optional + (Nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass; @end; @interface NSObject (model) + (instancetype)xk_modelWithDictionary:(NSDictionary *)dictionary; @end NS_ASSUME_NONNULL_ENDCopy the code
#import "NSObject+model.h"
#import <objc/runtime.h>@implementation NSObject (model) + (instancetype)xk_modelWithDictionary:(NSDictionary *)dictionary{// create current model object id object = [[self alloc] init]; Unsigned int count = 0; Ivar *ivarList = class_copyIvarList([self class], &count); //2. Select key from ivarList and Value from ivarListfor(int i= 0; i<count; Ivar = ivarList[I]; Ivar = ivarList[I]; NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)] ; //2.2 Truncate the name of a member variable: Remove the prefix of a member variable"_"NSString *propertyName = [ivarName substringFromIndex:1]; Value id value = dictionary[propertyName]; //3. Get the member variable type, because ivar_getTypeEncoding gets the type"@\"NSString\""/ / so we have to do the following, in the form of replacement nsstrings * ivarType = [nsstrings stringWithUTF8String: ivar_getTypeEncoding (ivar)]; // Replace: //3.1 Remove escape character: @\"name\" -> @"name" ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""]; / / 3.2 remove the @ symbol ivarType = [ivarType stringByReplacingOccurrencesOfString: @"@" withString:@""]; NSDictionary *perpertyTypeDic; NSDictionary *perpertyTypeDic;if([self respondsToSelector:@selector(modelContainerPropertyGenericClass)]){ perpertyTypeDic = [self performSelector:@selector(modelContainerPropertyGenericClass) withObject:nil]; Id ->uid id anotherName = perpertyTypeDic[propertyName];if(anotherName && [anotherName isKindOfClass:[NSString class]]){ value = dictionary[anotherName]; } //4.2. Processing: Model nested modelif([value isKindOfClass:[NSDictionary class]] && ! [ivarType hasPrefix:@"NS"]) {
Class modelClass = NSClassFromString(ivarType);
if(modelClass ! = nil) {/ / will be nested dictionary data into the Model value = [modelClass xk_modelWithDictionary: value]; // Check that the current Vaue is an array and there is a protocol method that returns perpertyTypeDicif([value isKindOfClass:[NSArray class]] && perpertyTypeDic) { Class itemModelClass = perpertyTypeDic[propertyName]; NSMutableArray *itemArray = @[]. MutableCopy;for (NSDictionary *itemDic invalue) { id model = [itemModelClass xk_modelWithDictionary:itemDic]; [itemArray addObject:model]; } value = itemArray; } //5. Use the KVC method to update the Vlue to the objectif(value ! = nil) { [objectsetValue:value forKey:propertyName]; } } free(ivarList); // Release the C pointerreturn object;
}
@end
Copy the code
- Each new
UserModel
,AddressModel
,TasksModel
To handle json processing:
UserModel class
#import "NSObject+model.h"
#import "AddressModel.h"
#import "TasksModel.h"NS_ASSUME_NONNULL_BEGIN @interface UserModel: NSObject<AAModel> @property (nonatomic, copy) NSString * uid; @property (nonatomic, copy) NSString * name; @property (nonatomic, copy) NSString * position; @property (nonatomic, assign) NSInteger age; @property (nonatomic, strong) AddressModel *address; @property (nonatomic, strong) NSArray *tasks; @end NS_ASSUME_NONNULL_END @implementation UserModel + (NSDictionary<NSString *,id> *) modelContainerPropertyGenericClass {/ / need special handling propertiesreturn@ {@"tasks" : [TasksModel class],@"uid": @"id"};
}
@end
Copy the code
AddressModel class
#import "NSObject+model.h"
NS_ASSUME_NONNULL_BEGIN
@interface AddressModel : NSObject
@property (nonatomic, copy) NSString * country;
@property (nonatomic, copy) NSString * province;
@end
NS_ASSUME_NONNULL_END
@implementation AddressModel
@end
Copy the code
TasksModel class
#import "NSObject+model.h"
NS_ASSUME_NONNULL_BEGIN
@interface TasksModel : NSObject
@property (nonatomic, copy) NSString * name;
@property (nonatomic, copy) NSString * desc;
@end
NS_ASSUME_NONNULL_END
@implementation TasksModel
@end
Copy the code
- The test code
- (void)viewDidLoad { [super viewDidLoad]; NSDictionary * jsonData = @{@"id": @"10089"The @"name": @"Allen"The @"age": @"20"The @"position": @"IOS Development Engineer"The @"address": @ {@"country": @"China"The @"province": @"Guangzhou"
},
@"tasks": @ [@ {@"name": @"Home"The @"desc": @"App Homepage Development"}, @ {@"name": @"Train"The @"desc": @"App Training Module Development"}, @ {@"name": @"Me"The @"desc": @"Complete personal page"}}; / / dictionary model UserModel * user = [UserModel xk_modelWithDictionary: jsonData]; TasksModel * task = user.tasks[0]; NSLog(@"% @",task.name);
}
Copy the code
The data structure of the execution result is as follows: