This paper carries on the above, decoding YYModel (a) basis.
At the end of the above, give an example:
@interface YYMessage : NSObject
@property (nonatomic.assign) uint64_t messageId;
@property (nonatomic.strong) NSString *content;
@property (nonatomic.strong) NSDate *time;
@property (nonatomic ,copy) NSString *name;
@end
@implementation YYMessage
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist
{
return@ [@"name"];
}
+ (NSDictionary *)modelCustomPropertyMapper {
return@ {@"messageId": @ [@"id".@"ID".@"mes_id"].@"time":@"t".@"name":@"user.name"
};
}
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic {
uint64_t timestamp = [dic unsignedLongLongValueForKey:@"t" default:0];
self.time = [NSDate dateWithTimeIntervalSince1970:timestamp / 1000.0];
return YES;
}
- (void)modelCustomTransformToDictionary:(NSMutableDictionary *)dic {
dic[@"t"] = @ ([self.time timeIntervalSince1970] * 1000).description;
}
Copy the code
A high performance
Based on YYModel, author put forward in the [post] (https://blog.ibireme.com/2015/10/23/ios_model_framework_benchmark/) tips, straighten out the performance optimization of the following points:Copy the code
The cache
In the following methodCopy the code
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary {
if(! dictionary || dictionary == (id)kCFNull) return nil;
if(! [dictionary isKindOfClass:[NSDictionary class]]) return nil;
Class cls = [self class];
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]; // Build cache
if(modelMeta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:dictionary] ? : cls; }NSObject *one = [cls new];
if ([one modelSetWithDictionary:dictionary]) return one; // Use cache
return nil;
}
Copy the code
- The meta Class cache
_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
Copy the code
- The class info cache
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
Copy the code
The Core Foundation object
CoreFoundation objects and corresponding methods were used throughout the caching period above.Copy the code
Traversal and search
- Find — NSSet instead of NSArray
In places like black-and-white lists, it’s more efficient to use AN NSSet instead of an NSArray to find out if an element is in a collection. Because NSSet does a traversal lookup, whereas NSArray does a traversal lookup.
// Whitelist is converted to NSSet when the whitelist overlooks objects
blacklist = [NSSet setWithArray:properties];
whitelist = [NSSet setWithArray:properties];
Copy the code
- Look-up table
When you encounter multiple choice conditions, try to use look-up table method to achieve, such as switch/case, C Array, if the look-up table condition is an object, you can use NSDictionary to achieve.
//switch/case instead of if/else
// Switch /case is in the library, everywhere.
//NSDictionary
// When using cache, the meta of the corresponding object is obtained according to NSDictionary
_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
Copy the code
- Reduce the number of loops traversed
Before converting JSON to Model, the number of Model attributes and the number of JSON attributes are both known. In this case, it will save a lot of time to select the one with a smaller number for traversal.
// If the number of attributes in model _keyMappedCount is more than the number of attributes in JSON, then the number of JSON attributes is traversed
// Otherwise, the _keyMappedCount is traversed
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
} else {
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
Copy the code
- When iterating through the container class, choose a more efficient method
Relative to the Foundation of the method, the method of CoreFoundation has higher performance, use CFArrayApplyFunction () and CFDictionaryApplyFunction () method to traverse the container classes can bring a lot of performance improvement.
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
Copy the code
C functions and inline functions
- C functions, which use pure C functions to avoid the overhead of ObjC’s message sending.
- Inline function
If C functions are small, using inline can avoid some of the overhead of function calls such as stack pops.
In the code, we see a lot of functionsstaticForce_inline begins with:Copy the code
// Refer to the GCC macro definition for inlining mandatory functions, see inline functions below
#define force_inline __inline__ __attribute__((always_inline))
Copy the code
A few more words about inline functions:Copy the code
- Inline function: similar to macro replacement, replaces the function name at the call with the function body at compile time.
- Advantages: Inlining reduces the overhead of function calls, with no transfer of control at call time.
- Disadvantages: If the number of calls is too many, it generally increases the amount of code.
- Writing:
inline
__inline
Is an inline function customized by some compilers and used by others in C codeinlineTo implement functions like __inline.- __forceInline is also a compiler-specific keyword that forces inline inlining of code, even when code is bloated, not based on compiler performance and optimization analysis.
- GCC also defines the __attribute__((always_inline)) attribute to tell the compiler to inline the function.
- Notes for inline functions:
- Keep functions short;
- Try not to include loop statements and switch statements;
- The inline function definition appears before the first call.
- Similarities and differences with macros:
- Compilation stage: Macro definition is implemented using the preprocessor Preprocessor and is a simple replacement for the preprocessed symbol table. Inline functions are replaced at compile time, with type checking and parameter validity checks.
- Return value: The return value defined by a macro cannot be cast to another type, whereas inline functions can;
- Security: Inline functions have type checking and parameter validation, while macros do not.
- Macros have further disadvantages :(1) they cannot access private variables; (2) Macro definition is easy to produce ambiguity;
Memory optimization
Under ARC, the default declared object is __strong. Assignment may result in a retain/release call. If a variable is not freed over its lifetime, using __unsafe_unretained can save a lot of overhead. Accessing a variable with an __weak attribute is actually done by calling objc_loadWeak() and objc_storeWeak(), which is also expensive, so avoid using the __weak attribute. When creating and using objects, try to avoid objects entering autoReleasepool to avoid additional resource overhead.Copy the code
Set the value
- Set values to avoid KVC, in the last step of JSON to Model, set values, the following methods heavily use setter methods:
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta) {
.......
// Call setter method Settings
((void(*) (id, SEL, id(a))void *) objc_msgSend)((id)model, meta->_setter, value);
}
Copy the code
Getters/setters are superior to KVC in performance.Copy the code
- Avoid getter/setter calls
If you can access IVAR directly, use IVAR instead of getters/setters to save some overhead.
Blacklist/whitelist
Attributes in the blacklist are ignored during processing.
Attributes that are not in the whitelist are ignored during processing.
Blacklist, first of all to achieve:
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist
{
return @[@"name"];
}
Copy the code
How to achieve the blacklist:
while (curClassInfo && curClassInfo.superCls ! // recursive parse super class, but ignore root class (NSObject/NSProxy) // But ignores the root class for (YYClassPropertyInfo * propertyInfo in curClassInfo. PropertyInfos. AllValues) {if (! propertyInfo.name) continue; / / list including the attribute, continue to the next cycle the if (blacklist && [blacklist containsObject: propertyInfo. Name]) continue; // Whitelist does not include this attribute, continue the next loop if (whitelist &&! [whitelist containsObject:propertyInfo.name]) continue; _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo propertyInfo:propertyInfo generic:genericMapper[propertyInfo.name]]; if (! meta || ! meta->_name) continue; if (! meta->_getter || ! meta->_setter) continue; if (allPropertyMetas[meta->_name]) continue; allPropertyMetas[meta->_name] = meta; } curClassInfo = curClassInfo.superClassInfo; }Copy the code
Automatic type conversion
Automatic type conversion, in fact, the following type to do some compatibility. We can analyze step by step how to make compatible.
- NSNumber,NSURL,SEL,Class -> NSString
case YYEncodingTypeNSString:
case YYEncodingTypeNSMutableString: {
if ([value isKindOfClass:[NSString class]]) {
if (meta->_nsType == YYEncodingTypeNSString) {
} else {
}
} else if ([value isKindOfClass:[NSNumber class]]) {
} else if ([value isKindOfClass:[NSData class]]) {
} else if ([value isKindOfClass:[NSURL class]]) {
} else if ([value isKindOfClass:[NSAttributedString class]]) {
}
} break;
Copy the code
As you can see from the code snippet above, it is compatible with NSString, NSNumber, NSData, NSURL, and NSAttributedString.Copy the code
To convert each of the above classes to NSString or NSMutableString, see:
//NSNumber->NSString
((NSNumber *)value).stringValue
((NSNumber *)value).stringValue.mutableCopy
//NSData->NSString
NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
//NSURL->NSString
((NSURL *)value).absoluteString
((NSURL *)value).absoluteString.mutableCopy
//NSAttributedString->NSString
((NSAttributedString *)value).string
((NSAttributedString *)value).string.mutableCopy
Copy the code
The use and principle of objc_msgSend method Click [here] (http://www.wenghengcong.com/2016/04/obj-msgSend%E8%B5%B0%E7%9A%84%E6%98%AF%E5%95%A5%E8%B7%AF%E5%AD%90/).Copy the code
- digital
Numbers are divided into basic C primitive types and number object types in Foundation.
C Basic Type | Object type |
---|---|
bool | |
int (8/16/32/64bit) | |
unsigned int(8/16/32/64bit) | |
float/double/long double |
How do you determine the type of an attribute?
There are three types of variables in _YYModelPropertyMeta:
@interface _YYModelPropertyMeta : NSObject {
.......
YYEncodingType _type; ///< property's type
YYEncodingNSType _nsType; ///< property's Foundation type
BOOL _isCNumber; ///< is c number type
.......
}
Copy the code
In our last article, we explained how to resolve a '_type' type from a property, which specifies the property value and type. Let's go over it again:Copy the code
// yyclasspropertyinfo. m - (instancetype)initWithProperty:(objc_property_t)property {//type type read type = YYEncodingGetType(attrs[i].value); } YYEncodingType YYEncodingGetType(const char *typeEncoding) {switch (*type) {....... case 'C': return YYEncodingTypeUInt8 | qualifier; . case '{': return YYEncodingTypeStruct | qualifier; default: return YYEncodingTypeUnknown | qualifier; }}Copy the code
Where '_nsType' is the type of the object in Foundation:Copy the code
/// Get the Foundation class type from property info. static force_inline YYEncodingNSType YYClassGetNSType(Class cls) { if (! cls) return YYEncodingTypeNSUnknown; . . if ([cls isSubclassOfClass:[NSSet class]]) return YYEncodingTypeNSSet; return YYEncodingTypeNSUnknown; }Copy the code
` _isCNumber ` type:Copy the code
/// Whether the type is c number.
static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type) {
switch (type & YYEncodingTypeMask) {
case YYEncodingTypeBool:
case YYEncodingTypeInt8:
......
case YYEncodingTypeDouble:
case YYEncodingTypeLongDouble: return YES;
default: return NO;
}
}
Copy the code
JSON->Model: JSON->Model: JSON->ModelCopy the code
If (meta->_isCNumber) {NSNumber *num = YYNSNumberCreateFromID(value); ModelSetNumberToProperty(model, num, meta); if (num ! = nil) [num class]; // Hold the number} else if (meta->_nsType) {// Handle the NSString type case YYEncodingTypeNSValue as above: case YYEncodingTypeNSNumber: case YYEncodingTypeNSDecimalNumber: { if (meta->_nsType == YYEncodingTypeNSNumber) { } else if (meta->_nsType == YYEncodingTypeNSDecimalNumber) { if ([value isKindOfClass:[NSDecimalNumber class]]) { } else if ([value isKindOfClass:[NSNumber class]]) { } else if ([value isKindOfClass:[NSString class]]) { } } else { // YYEncodingTypeNSValue if ([value isKindOfClass:[NSValue class]]) { } } } break; }Copy the code
For basic TYPE C, there are two steps:Copy the code
- The value obtained from JSON is treated as an NSNumber
- Assign NSNumber to the property in the Model
Type safety
Type safety is embodied in two aspects: values and set values. Continue the analysis with numeric type parsing:Copy the code
- The value obtained from JSON is treated as an NSNumber
- Assign NSNumber to the property in the Model
The first step, namely, the value, how to achieve security:Copy the code
static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value) { static NSCharacterSet *dot; static NSDictionary *dic; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)]; dic = @{@"TRUE" : @(YES), @"True" : @(YES), @"true" : @(YES), @"FALSE" : @(NO), @"False" : @(NO), @"false" : @(NO), @"YES" : @(YES), @"Yes" : @(YES), @"yes" : @(YES), @"NO" : @(NO), @"No" : @(NO), @"no" : @(NO), @"NIL" : (id)kCFNull, @"Nil" : (id)kCFNull, @"nil" : (id)kCFNull, @"NULL" : (id)kCFNull, @"Null" : (id)kCFNull, @"null" : (id)kCFNull, @"(NULL)" : (id)kCFNull, @"(Null)" : (id)kCFNull, @"(null)" : (id)kCFNull, @"<NULL>" : (id)kCFNull, @"<Null>" : (id)kCFNull, @"<null>" : (id)kCFNull}; }); //1. If value is nil or kCFNull, then the value is nil if (! value || value == (id)kCFNull) return nil; If ([value isKindOfClass:[NSNumber class]]) return value; If ([value isKindOfClass:[NSString class]]) {//3-1 possibility 1: NSNumber *num = dic[value]; NSNumber *num = dic[value]; NSNumber *num = dic[value]; if (num ! = nil) { if (num == (id)kCFNull) return nil; return num; } / / 3-2 possibility 2: there is a floating point number, contains ". "if ([(nsstrings *) value rangeOfCharacterFromSet: dot]. The location! = NSNotFound) { const char *cstring = ((NSString *)value).UTF8String; if (! cstring) return nil; double num = atof(cstring); / / exclude non-numeric value isnan with infinite isinf if (isnan (num) | | isinf (num)) return nil; return @(num); } else {//3-3 possibility 3: other string const char * cString = ((NSString *)value).utf8String; if (! cstring) return nil; Return @(atoll(cstring)); // Convert string to numeric value and wrap as NSNumber; } } return nil; }Copy the code
Step 2, set the value?Copy the code
static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model, __unsafe_unretained NSNumber *num, __unsafe_unretained _YYModelPropertyMeta *meta) { switch (meta->_type & YYEncodingTypeMask) { ....... case YYEncodingTypeFloat: { float f = num.floatValue; / / check if not numeric values or an infinite value, namely the invalid values the if (isnan (f) | | isinf (f)) = 0 f; ((void (*)(id, SEL, float))(void *) objc_msgSend)((id)model, meta->_setter, f); } break; . default: break; }}Copy the code
In addition, type safety is more automatic type conversion, to ensure compatibility.Copy the code
A series of
- Decode YYModel (a) basis
- Decode YYModel (two) features
- Decode YYModel (three) reference