This article takes a simple example, look at YYModel dictionary model source code
Here’s a model
@interface Author : NSObject
@property NSString *name;
@property NSString *birthday;
@end
@interface Book : NSObject
@property NSString *name;
@property NSUInteger pages;
@property Author *author;
@end
Copy the code
The general flow of creation looks like this
Book * page = [Book new]; Page. Name = @" "; page.pages = 245; Author * writer = [Author new]; Writer. name = @" sun da sheng "; Writer. birthday = @" don't know "; page.author = writer;Copy the code
With YYModel, you can do this
NSString * path = [NSBundle.mainBundle pathForResource: @"one" ofType: @"json"];
NSString* jsonString = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
Book * page = [Book yy_modelWithJSON: jsonData];
Copy the code
YYModel automates what we do manually at runtime
- The run-time introspection feature takes the attributes of the model and matches them with the keys of the dictionary
It then implements automatic assignment
- I’m going to recursively arrange the data, level by level, of the model
Look at the source code
For its source code, there is a simplification
YYModel method implementation,
It’s an extension to NSObject
@interface NSObject (YYModel)
@end
Copy the code
Take the data, spit out the model
+ (instancetype)yy_modelWithJSON:(id)json {NSDictionary *dic = [self _yy_dictionaryWithJSON:json]; Return [self yy_modelWithDictionary:dic]; }Copy the code
Give the dictionary, the way to get the model
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary { dictionary || dictionary == (id)kCFNull) return nil; if (! [dictionary isKindOfClass:[NSDictionary class]]) return nil; // create model CLS = [self Class]; NSObject *one = [cls new]; // The model is empty. // The model is empty. // The model is empty. Returns the model / / fill in the data is not OK, is nil if ([one yy_modelSetWithDictionary: dictionary]) return one; return nil; }Copy the code
Where, this row
NSObject *one = [cls new];
Copy the code
The corresponding
Book * page = [Book new];
Copy the code
In summary, YYModel is not magic. What YYModel does is to create models and fill in data for their properties
Populate the model with data
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic { dic || dic == (id)kCFNull) return NO; if (! [dic isKindOfClass:[NSDictionary class]]) return NO; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)]; if (modelMeta->_keyMappedCount == 0) return NO; ModelMeta ModelSetContext Context = {0}; modelMeta ModelSetContext = {0}; modelMeta ModelSetContext = {0}; context.modelMeta = (__bridge void *)(modelMeta); context.model = (__bridge void *)(self); context.dictionary = (__bridge void *)(dic); If (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) { Batch call ModelSetWithDictionaryFunction method CFDictionaryApplyFunction (CFDictionaryRef dic, ModelSetWithDictionaryFunction, &context); / /... If} else {//... } return YES; }Copy the code
Enter the ModelSetWithDictionaryFunction attribute assignment method
The static void ModelSetWithDictionaryFunction (const void * _key, const void * _value, void * _context) {/ / the context, contains two information, ModelSetContext *context = _context; __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta); __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)]; __unsafe_unretained id model = (__bridge id)(context->model); / /... ModelSetValueForProperty(model, (__bridge __unsafe_unretained ID)_value, propertyMeta); / /... }Copy the code
Enter the specific assignment method ModelSetValueForProperty
- If it is a string, go
YYEncodingTypeNSString
Through theobjc_msgSend
Assign values to attributes of the model
For values of integers, the approach is similar
- If it’s a level 2 dictionary, go
YYEncodingTypeObject
, create a model,
Call yy_modelSetWithDictionary, the process above, and start over
Recursive calls appear
// The following method is greatly simplified, Static void ModelSetValueForProperty(__unsafe_unretained ID model, __unsafe_unretained ID value, __unsafe_unretained _YYModelPropertyMeta *meta) { if (meta->_nsTypeX) { switch (meta->_nsTypeX) { case YYEncodingTypeNSString:{if ([value isKindOfClass:[NSString class]]) {// String, (void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } } break; default: break; } } else { BOOL isNull = (value == (id)kCFNull); switch (meta->_type & YYEncodingTypeMask) { case YYEncodingTypeObject: { Class cls = meta->_genericCls ? : meta->_cls; if (isNull) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil); } else if ([value isKindOfClass:cls] || ! cls) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value); } else if ([value isKindOfClass:[NSDictionary class]]) { NSObject *one = nil; // one = [CLS new]; [one yy_modelSetWithDictionary:value]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one); } } break; default: break; }}}Copy the code
The sentence above
[cls new];
Copy the code
Corresponding to the beginning
Author * writer = [Author new];
Copy the code
The process is complete
So let’s look at the code
Give me a JSON, get the dictionary,
This JSON can be a string, a dictionary, binary NSData
They’ve been checked and treated accordingly
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {// empty if (! json || json == (id)kCFNull) return nil; NSDictionary *dic = nil; NSData *jsonData = nil; If ([json isKindOfClass:[NSDictionary class]]) {dic = json; } else if ([json isKindOfClass:[NSString class]]) {jsonData = [(NSString *)json dataUsingEncoding: NSUTF8StringEncoding]; } else if ([json isKindOfClass:[NSData class]]) {// } if (jsonData) { dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]; if (! [dic isKindOfClass:[NSDictionary class]]) dic = nil; } return dic; }Copy the code
Get the property information through the runtime
The entrance is this one
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
Copy the code
Enter the class _YYModelMeta
Initialization method, which looks scary,
Again singleton, lock, semaphore
It’s actually quite simple
Useful code, just one line
meta = [[_YYModelMeta alloc] initWithClass:cls];
Copy the code
Create two singletons, a cache, and a lock that are actually semaphores
And then get the meta,
There is cache, fetch cache
No cache, go create
The semaphore lock keeps the cache operation thread-safe
+ (instancetype)metaWithClass:(Class)cls { if (! cls) return nil; static CFMutableDictionaryRef cache; static dispatch_once_t onceToken; static dispatch_semaphore_t lock; dispatch_once(&onceToken, ^{ cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); lock = dispatch_semaphore_create(1); }); dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); dispatch_semaphore_signal(lock); if (! meta){ meta = [[_YYModelMeta alloc] initWithClass:cls]; if (meta) { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta)); dispatch_semaphore_signal(lock); } } return meta; }Copy the code