Everyone must be familiar with YYModel. Its non-invasive nature and ease of use make it the new favorite of JSON-Model. Next, let’s analyze its principle.

Knowledge that must be known

  • YYClassInfo is a runtime Class in the OC layer encapsulation, and parsing added a lot of description, so to understand the principle of YYModel must have some understanding of the Runtime.

  • Objc_class contains Pointers to the superClass, ISA, and class_rw_t. Class_rw_t stores the list of member variables, attributes, and methods of this class. So we can actually read this information about the class through the Runtime API.

  • The compiler encodes the types according to certain rules and stores them in the Runtime data structure, so we can parse out the types of attributes, member variables, and method parameters according to these rulesOfficial Documents

YYEncodingType

YYEncodingType represents the type represented by typeEncoding. The YYEncodingType YYEncodingGetType(const char *typeEncoding) method converts a string to an enumerated value representing a specific type. YYEncodingType not only represents the type, it is a bit-by-bit enumeration, but also stores some attribute descriptions of the class.

YYClassIvarInfo

Member variables are of type Ivar at the Runtime level and can be read by Ivar, etc

@interface YYClassIvarInfo: NSObject assign (nonatomic, assign,readonly) Ivar ivar; @property (nonatomic, strong,readonly) NSString * name; @property (nonatomic, assign,readonly) ptrdiff_t offset; // offset @property (nonatomic, strong,readonly) NSString *typeEncoding; // @property (nonatomic, assign,readonly) YYEncodingType type; -(instancetype)initWithIvar:(Ivar) Ivar; @endCopy the code

The main runtime interfaces used in this class are

Ivar_getName () // Gets the name of the member variable ivar_getOffset() // Gets the offset ivar_getTypeEncoding() // Gets the type encoding of the member variableCopy the code

YYClassMethodInfo

MethodInfo contains a little more information

@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method; //runtime Method @property (nonatomic, assign,readonly) NSString * name; @property (nonatomic, assign,readonly) SEL sel; // selector @property (nonatomic, assign,readonly) IMP imp; // @property (nonatomic, strong,readonly) NSString * typeEncoding; // The type of the method is @property (nonatomic, strong,readonly) NSString * returnTypeEncoding; @property (nonatomic, nullable, strong,readonly) NSArray<NSString *> *argumentTypeEncoding; - (instancetype)initWithMethod:(Method) Method; @endCopy the code

The main runtimeApi used are

method_getName() method_getImplementation() method_getTypeEncoding() method_copyReturnMethod() Method_getNumberOfArguments () // Gets the number of arguments method_copyArgumentType(method, I) // Gets the type encoding of the ith argument to the methodCopy the code

YYClassPropertyInfo

Let’s first analyze what information is contained in the attributes of the class, including the member variables, the protocol followed, and the keywords that describe the attributes such as copy, strong, weak for memory management, and readOnly for reading and writing. There are also default generated setter and getter methods. All of this has to be resolved

@interface YYClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property; @property (nonatomic, strong,readonly) NSString * name; @property (nonatomic, assign,readonly) YYEncodingType type; // Type enumeration @property (nonatomic, strong,readonly) NSString * typeEncoding; // @property (nonatomic, strong,readonly) NSString * ivarName; @property (nonatomic, nullable, assign,readonly) Class cls; @property (nonatomic, nullable, strong,readonly) NSArray<NSString *> *protocols; @property (nonatomic, assign,readonly) SEL getter; // Generate getter @property (nonatomic, assign,readonly) SEL setter; // generated setter method - (instancetype)initWithProperty:(objc_property_t)property; @endCopy the code

The parsing of the attribute is the longest of these because it involves parsing the Encoding string. For example, type resolution. We can get the description of the property using the property_copyAttributeList method. The objc_property_attribute_t data structure is basically an array, and the elements of the array are a map, and different keys have different meanings. For example, “T” stands for the type encoding of the attribute, “V” stands for the name of the member variable, and so on. Let’s focus on the location of parsing types and protocols.

case 'T': / / representativetype encoding
                if(attrs[i].value){ _typeEncoding = [NSString stringWithUTF8String:attrs[i].value]; / / to YYEncodingTypetype= YYEncodingGetType(attrs[i].value); // If the type is oc, parse the oc object, for example: @"NSString"
                    if ((type& YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length){ NSScanner * scanner = [NSScanner scannerWithString:_typeEncoding]; // Move the scan position to" if (! [scanner scanString:@"@ \"" intoString:NULL]) continue; NSString *clsName = nil; // Then three seconds to exist"Or <, because typeEncode is @ if you follow the protocol."NSString<NSCopy>"If [scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString: @"\"<"] intoString:&clsName]){
                            if(clsName.length) _cls = objc_getClass(clsName.UTF8String); } NSMutableArray *protocols = nil; // If multiple protocols are followed typecoding looks like this @"NSString<NSCopy><NSObject>", so move the scanning position to < position and cycle interceptionwhile ([scanner scanString:@"<" intoString:NULL]) {
                            NSString * protocol = nil;
                            if ([scanner scanUpToString:@">" intoString:&protocol]){
                                if (protocol.length){
                                    if(! protocols) protocols = [NSMutableArray new]; [protocols addObject:protocol]; } [scanner scanString:@">"intoString:NULL]; } protocols = protocols; }}}break;
Copy the code

So an OC code is going to look something like this so if YOU look at NSString for example, at sign “NSString”, if you follow the protocol, at sign “NSString” if you follow multiple protocols, at sign “NSString”, so this code is going to follow that as well. Let’s look again at how do we get and set methods

if (_name.length){
            if(! _getter){ _getter = NSSelectorFromString(_name); }if(! _setter){ _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@",[_name substringFromIndex:1].uppercaseString,[_name substringFromIndex:1]]); }}Copy the code

So this is pretty simple and it’s just based on the property name, the first character is big, and then you put get and set in front of it, ha ha, isn’t that cool?

YYClassInfo

So in fact, the most important three parts above are finished, YYClassInfo is very easy

@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; @property (nullable, nonatomic, assign,readonly) Class superCls; @property (nullable, nonatomic, assign,readonly) Class metaCls; // metaclass @property (nonatomic,readonly) BOOL isMetal; // if @property (nonatomic, strong,readonly) NSString * name; @property (nullable, nonatomic, strong,readonly) YYClassInfo * superClassInfo; // classInfo @property (nullable, nonatomic, strong,readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; @property (nullable, nonatomic, strong,readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; @property (nullable, nonatomic, strong,readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; // Update class information is required to set. - (void)setNeedUpdate; - (BOOL)needUpdate; YYClassInfo + (nullable instancetype)classInfoWithClass:(Class) CLS; YYClassInfo + (nullable instancetype)classInfoWithClassName:(NSString *)className; @endCopy the code

So we all passed through classInfo parsing a type which process, first has the cached data is read from the cache, the cache is a static global variable, if there is a judge whether need to update the class data cache, if need to update the reanalysis, if there is no data in the cache, then the analytical data, analytical superclass and recursive data, Until the superClass is nil, NSObject’s superClass is nil. This might seem like a lot of recursion, but our usual models inherit directly from NSObject, so it’s about twice. Let’s take a look at the core code, which is basically calling the runtimeApi

- (void)_update{
    _ivarInfos = nil;
    _methodInfos = nil;
    _propertyInfos = nil;
    Class cls = self.cls;
    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList(cls, &methodCount);
    if (methods){
        NSMutableDictionary * methodInfo = [NSMutableDictionary new];
        _methodInfos = _methodInfos;
        for (unsigned int i = 0; i < methodCount; i++){
            YYClassMethodInfo * info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
            if (info.name) methodInfo[info.name] = info;
        }
        free(methods);
    }
    unsigned int propertyCount = 0;
    objc_property_t * properties = class_copyPropertyList(cls, &propertyCount);
    if (properties){
        NSMutableDictionary * propertyInfos = [NSMutableDictionary new];
        _propertyInfos = propertyInfos;
        for (unsigned int i = 0; i<propertyCount; i++){
            YYClassPropertyInfo * info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
            if (info.name) propertyInfos[info.name] = info;
        }
        free(properties);
    }
    unsigned int ivarCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &ivarCount);
    if (ivars){
        NSMutableDictionary * ivarInfos = [NSMutableDictionary new];
        _ivarInfos = ivarInfos;
        for (unsigned int i = 0; i<ivarCount; i++){
            YYClassIvarInfo * info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
            if (info.name) ivarInfos[info.name] = info;
        }
        free(ivars);
    }
    if(! _ivarInfos) _ivarInfos = @{};if(! _methodInfos) _methodInfos = @{};if(! _propertyInfos) _propertyInfos = @{}; _needUpdate = NO; }Copy the code

conclusion

From YYClassInfo, we can see the author’s deep understanding of Runtime. By encapsulating the class structure of Runtime, we can easily obtain various information about a class. Json to model isn’t that hard, and we’ll talk about NSObject+YYModel in the next chapter. Little brother not just, if there is a misunderstanding, please point out in time.