Statement: This article is my [programming small weng] original, reproduced please note.

Variable declaration

Create a parent class Biology and a subclass Person for the purposes of this discussion:

Biology:

@interface Biology : NSObject
{
    NSInteger *_hairCountInBiology;
}
@property (nonatomic, copy) NSString *introInBiology;
@end

@implementation Biology
@endCopy the code

Person:

#import 
#import "Biology.h"
#import 

@interface Person : Biology
{
    NSString *_father;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person

@endCopy the code

Any property or variable defined in a parent class has an InBiology flag at the end. And vice versa

Two, the introduction of problems

In iOS, a custom object cannot be stored directly to a file, it must be converted to a binary stream first. The process from objects to binary data is commonly called Serialization, also known as Archive. Similarly, the process of going from binary data to objects is commonly referred to as deserialization or de-archiving. The following methods for implementing NSCoding and NSCopying(optional) protocols are unavoidable in serialization implementations:

- (id)initWithCoder:(NSCoder *)coder;
- (void)encodeWithCoder:(NSCoder *)coder;
- (id)copyWithZone:(NSZone *)zone;Copy the code

Suppose we now need to serialize a Person class that inherits directly from NSObject. The code would look like this:

- (void)encodeWithCoder:(NSCoder *)coder {[coder encodeObject:self.name forKey:@"name"]; [coder encodeObject:@(self.age) forKey:@"age"]; [coder encodeObject:_father forKey:@"_father"]; / /... . Other instance variables} // decode variables - (id)initWithCoder:(NSCoder *)coder {self.name = [coder decodeObjectForKey:@"name"]; self.age = [[coder decodeObjectForKey:@"age"] integerValue]; _father = [coder decodeObjectForKey:@"_father"]; / /... . other instance variablesCopy the code

Seems so easy? So far, at least. But consider the following:

  • What if Person is a large class with a lot of variables to encode/decode?
  • What if your project has a lot of custom classes like Person that need to be serialized?
  • What if Person doesn’t inherit directly from NSObject but has multiple layers of parent classes? (Note that the principle of serialization is that all levels of the parent class’s attribute variables also need to be serialized);

If the traditional serialization method is used in the beginning, the following defects are easily exposed when encountering the above problems (only defects, not problems) :

  • There are many redundant codes in engineering code
  • The complexity of the parent class hierarchy makes it easy to miss some attribute variables in the parent class

Is there a more elegant way to avoid these problems? That’s a must. Here we will explore using the Runtime to implement a simple and versatile iOS serialization and de-sequence scheme.

Runtime: iOS serialization and deserialization tool

3.1 General Idea

Looking at the initWithCoder code above, we can see that the most important step in serialization and deserialization is to iterate through the class variables to ensure that they are not missed.

The important thing to note here is that the scope of codec should not only be the variables of its own class, but also the attribute variables of all hierarchical parent classes except NSObject.

Thus, this is almost a pure physical work. How can runtime help us with iterating over variables? We can use runtime to get all the variables of its own class at runtime for encoding and decoding; It then recurses over the parent class, getting the attributes (non-private variables) of each level of parent except NSObject, and decoding them.

3.2 Using Runtime to get variables and attributes

Get all variables (attribute variables and instance variables) of a class from runtime:

Ivar *class_copyIvarList(Class cls, unsigned int *outCount)Copy the code

Get all attribute variable apis of a class:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)Copy the code

All the runtime open apis are in objc/ Runtime.h. Ivar is a variable defined by the Runtime, which is a structure in nature:

struct objc_ivar {
    char *ivar_name;                                   
    char *ivar_type;                                    
    int ivar_offset;
#ifdef __LP64__
    int space;
#endif
} 
typedef struct objc_ivar *Ivar;Copy the code
  • Ivar_name: variable name, for a given Ivar, can passconst char *ivar_getName(Ivar v)Function to obtainchar *The variable name of the type;
  • Ivar_type: variable type. In runtime, variable types are expressed as strings. For example, @ indicates id, I indicates int… . This is beyond the scope of this article. Similarly, you can passconst char *ivar_getTypeEncoding(Ivar v)The function gets the variable type;
  • Ivar_offset: the number of bytes offset from the base address, which can be ignored

    The code that gets all the variables looks like this:
    unsigned int numIvars; Ivar *vars = class_copyIvarList(NSClassFromString(@"UIView"), &numivars); NSString *key=nil; for(int i = 0; i < numIvars; i++) { Ivar thisIvar = vars[i]; key = [NSString stringWithUTF8String:ivar_getName(thisIvar)]; // Get the name of the member variable NSLog(@"variable name :%@", key); key = [NSString stringWithUTF8String:ivar_getTypeEncoding(thisIvar)]; // Get the data type of the member variable NSLog(@"variable type :%@", key); } free(vars); // Remember to release itCopy the code

    Objc_property_t is the Runtime’s definition of a property variable, which is essentially a structure (OC is actually a wrapper around C, and most types are essentially C structures). inruntime.hOnly in the header filetypedef struct objc_property *objc_property_t, and there is no more detailed structure introduction. Although the Source code for runtime is open source, it is not intended to be covered here, which does not affect our topic today. As with Ivar, the code to get the class’s attribute variables looks like this:

    unsigned int outCount, i; objc_property_t *properties = class_copyPropertyList([self class], &outCount); for (i = 0; i < outCount; i++) { objc_property_t property = properties[i]; NSString *propertyName = [[[NSString alloc] initWithCString:property_getName(property)] ;  NSLog(@"property name:%@", propertyName); } free(properties);Copy the code

    3.3 Implement serialization and deserialization with Runtime

    With the first two sections of the foreshadowing, here will naturally come naturally. We can do it atinitWithCoder:As well asencoderWithCoder:, iterates through all variables of the class, obtains the variable name as the KEY value, and finally uses KVC to force or assign the value to the object. So we can get the following automatic serialization and serialization code, the key part of the comment:

    @implementation Person // decoder - (id)initWithCoder:(NSCoder *)coder {unsigned int iVarCount = 0; Ivar *iVarList = class_copyIvarList([self class], &iVarCount); For (int I = 0; i < iVarCount; i++) { Ivar var = *(iVarList + i); const char * varName = ivar_getName(var); / / variable name, will be the key nsstrings * key = [nsstrings stringWithUTF8String: varName]; //decode id value = [coder decodeObjectForKey:key]; // decode if (value) {[self setValue:value forKey:key]; }} Free (iVarList); Return self; } // encode - (void)encodeWithCoder:(NSCoder *)coder {unsigned int varCount = 0; Ivar *ivarList = class_copyIvarList([self class], &varCount); for (int i = 0; i < varCount; i++) { Ivar var = *(ivarList + i); const char *varName = ivar_getName(var); NSString *key = [NSString stringWithUTF8String:varName]; id varValue = [self valueForKey:key]; If (varValue) {[coder encodeObject:varValue forKey:key]; } } free(ivarList); }Copy the code

    3.4 optimization

    There is a flaw in the above code that always specifies the current class when fetching a variable, i.e[self class]. It’s easy to omit superclass properties when your Model object doesn’t inherit directly from NSObject. Keep in mind what we mentioned in section 3.1:

    You can’t just codec the variables of your own class. You should also codec the attribute variables of all the parent classes except NSObject!

So we need to pay attention to the details of the above code, set a pointer, first to itself, after processing to SuperClass, then to SuperClass SuperClass. . The code is as follows (only encodeWithCoder: is used here, after all initWithCoder: is the same) :

- (void)encodeWithCoder:(NSCoder *)coder { Class cls = [self class]; while (cls ! = [NSObject class]) {unsigned int iVarCount = 0; Ivar *ivarList = class_copyIvarList([cls class], &iVarCount); /* for (int I = 0; i < iVarCount; i++) { const char *varName = ivar_getName(*(ivarList + i)); NSString *key = [NSString stringWithUTF8String:varName]; */ id varValue = [self valueForKey:key] */ id varValue = [self valueForKey:key]; if (varValue) { [coder encodeObject:varValue forKey:key]; } } free(ivarList); cls = class_getSuperclass(cls); // pointer to the parent of the current class}}Copy the code

Is this really the end of it? Isn’t. [self objectForKey:key] [self objectForKey:key] [self objectForKey:key] [self objectForKey:key] It turns out that KVC can’t get the parent’s private variables (instance variables). Therefore, you can’t simply use class_copyIvarList when dealing with a parent class. Instead, you can only take the parent class’s attribute variables. This is where the class_copyPropertyList in Section 3.2 comes in handy. Replace the parent class with the latter. So the final code (well, not really final) :

- (id)initWithCoder:(NSCoder *)coder { NSLog(@"%s",__func__); Class cls = [self class]; while (cls ! = [NSObject class]) {/* bIsSelfClass = (CLS == [self]); unsigned int iVarCount = 0; unsigned int propVarCount = 0; unsigned int sharedVarCount = 0; Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL; /* List of variables, including properties and private variables */ objc_property_t *propList = bIsSelfClass? NULL : class_copyPropertyList(cls, &propVarCount); /* Attribute list */ sharedVarCount = bIsSelfClass? iVarCount : propVarCount; for (int i = 0; i < sharedVarCount; i++) { const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); NSString *key = [NSString stringWithUTF8String:varName]; id varValue = [coder decodeObjectForKey:key]; if (varValue) { [self setValue:varValue forKey:key]; } } free(ivarList); free(propList); cls = class_getSuperclass(cls); } return self; } - (void)encodeWithCoder:(NSCoder *)coder { NSLog(@"%s",__func__); Class cls = [self class]; while (cls ! = [NSObject class]) {/* bIsSelfClass = (CLS == [self]); unsigned int iVarCount = 0; unsigned int propVarCount = 0; unsigned int sharedVarCount = 0; Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL; /* List of variables, including properties and private variables */ objc_property_t *propList = bIsSelfClass? NULL : class_copyPropertyList(cls, &propVarCount); /* Attribute list */ sharedVarCount = bIsSelfClass? iVarCount : propVarCount; for (int i = 0; i < sharedVarCount; i++) { const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); NSString *key = [NSString stringWithUTF8String:varName]; */ id varValue = [self valueForKey:key] */ id varValue = [self valueForKey:key]; if (varValue) { [coder encodeObject:varValue forKey:key]; } } free(ivarList); free(propList); cls = class_getSuperclass(cls); }}Copy the code

3.5 Final packaging

Logically, the above code should be by far the perfect automatic serialization and unsequence solution. Even if the inheritance of a class is extremely deep and the variables are extremely large, that’s all the serialized code is. But let’s go back to some scenario assumptions proposed in section 2 of the article, one of which mentioned:

What if your project has a lot of custom classes like Person that need to be serialized?

In the above scenario, each Model class would need to write the above code once. This also creates redundancy to a certain extent. At the same time, you’ll think the title of this article is bullshit, not a single line of code. The above code redundancy, I have a very strong code cleanliness of the program Wang is absolutely unacceptable. Then wrap another layer! Here I use macros to condense the code into a single line in a header file called wzlSerializeKit.h:

#define WZLSERIALIZE_CODER_DECODER() \ \ - (id)initWithCoder:(NSCoder *)coder \ { \ NSLog(@"%s",__func__); \ Class cls = [self class]; \ while (cls ! = [NSObject class]) {/* bIsSelfClass = (CLS == [self class]); \ unsigned int iVarCount = 0; \ unsigned int propVarCount = 0; \ unsigned int sharedVarCount = 0; \ Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL; /* List of variables, including properties and private variables */ \ objc_property_t *propList = bIsSelfClass? NULL : class_copyPropertyList(cls, &propVarCount); /* sharedVarCount = bIsSelfClass? iVarCount : propVarCount; \ \ for (int i = 0; i < sharedVarCount; i++) { \ const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \ NSString *key = [NSString stringWithUTF8String:varName]; \ id varValue = [coder decodeObjectForKey:key]; \ if (varValue) { \ [self setValue:varValue forKey:key]; \ } \ } \ free(ivarList); \ free(propList); \ cls = class_getSuperclass(cls); \ } \ return self; \ } \ \ - (void)encodeWithCoder:(NSCoder *)coder \ { \ NSLog(@"%s",__func__); \ Class cls = [self class]; \ while (cls ! = [NSObject class]) {/* bIsSelfClass = (CLS == [self class]); \ unsigned int iVarCount = 0; \ unsigned int propVarCount = 0; \ unsigned int sharedVarCount = 0; \ Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL; /* List of variables, including properties and private variables */ \ objc_property_t *propList = bIsSelfClass? NULL : class_copyPropertyList(cls, &propVarCount); /* sharedVarCount = bIsSelfClass? iVarCount : propVarCount; \ \ for (int i = 0; i < sharedVarCount; i++) { \ const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \ NSString *key = [NSString stringWithUTF8String:varName]; */ id varValue = [self valueForKey:key]; */ id varValue = [self Value :key]; \ if (varValue) { \ [coder encodeObject:varValue forKey:key]; \ } \ } \ free(ivarList); \ free(propList); \ cls = class_getSuperclass(cls); \} \}Copy the code

Import “wzlSerializekit.h”; call WZLSERIALIZE_CODER_DECODER(); Can. Two words: fresh. In addition, the same principles apply to automatic copy of variables in copyWithZone. Similarly, we can encapsulate the copyWithZone method with a macro. I will not repeat it here. It’s worth noting that I already put the above code on my Github and provide CocoaPods support. To use it, only pod ‘WZLSerializeKit’ is required. Click here to jump to my Github.

  • If you like this article, click like and follow me, or leave a message saying you love me (ogling)
  • Don’t like to leave a message to give advice, I will humbly accept
  • Welcome to reprint