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, goYYEncodingTypeNSStringThrough theobjc_msgSendAssign values to attributes of the model

For values of integers, the approach is similar

  • If it’s a level 2 dictionary, goYYEncodingTypeObject, 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
Runtime related, continue in the next article

github repo