• 经典iOS第三方库源码分析


    YYModel介绍

    YYModel是一个针对iOS/OSX平台的高性能的Model解析库,是属于YYKit的一个组件,创建是ibireme

    其实在YYModel出现之前,已经有非常多的Model解析库,例如JSONModelMantleMJExtension

    YYModel从易用性和性能方面均达到了最高水平。

    性能

     
    Model解析库对比

    特性

    • High performance: The conversion performance is close to handwriting code.
    • Automatic type conversion: The object types can be automatically converted.
    • Type Safe: All data types will be verified to ensure type-safe during the conversion process.
    • Non-intrusive: There is no need to make the model class inherit from other base class.
    • Lightwight: This library contains only 5 files.
    • Docs and unit testing: 100% docs coverage, 99.6% code coverage.

    YYModel使用

    简单Model和JSON转换

    // JSON:
    {
        "uid":123456,
        "name":"Harry",
        "created":"1965-07-31T00:00:00+0000"
    }
    
    // Model:
    @interface User : NSObject
    @property UInt64 uid;
    @property NSString *name;
    @property NSDate *created;
    @end
    @implementation User
    @end
    
    // Convert json to model:
    User *user = [User yy_modelWithJSON:json];
        
    // Convert model to json:
    NSDictionary *json = [user yy_modelToJSONObject];
    

    内嵌Model

    // JSON
    {
        "author":{
            "name":"J.K.Rowling",
            "birthday":"1965-07-31T00:00:00+0000"
        },
        "name":"Harry Potter",
        "pages":256
    }
    
    // Model: (no need to do anything)
    @interface Author : NSObject
    @property NSString *name;
    @property NSDate *birthday;
    @end
    @implementation Author
    @end
        
    @interface Book : NSObject
    @property NSString *name;
    @property NSUInteger pages;
    @property Author *author;
    @end
    @implementation Book
    @end
    

    集合类型 - Array、Set

    @class Shadow, Border, Attachment;
    
    @interface Attributes
    @property NSString *name;
    @property NSArray *shadows; //Array<Shadow>
    @property NSSet *borders; //Set<Border>
    @property NSMutableDictionary *attachments; //Dict<NSString,Attachment>
    @end
    
    @implementation Attributes
    + (NSDictionary *)modelContainerPropertyGenericClass {
        // value should be Class or Class name.
        return @{@"shadows" : [Shadow class],
                 @"borders" : Border.class,
                 @"attachments" : @"Attachment" };
    }
    @end
    

    YYModel代码结构

    YYModel整个项目非常简洁,只有5个文件。

    文件描述
    NSObject+YYModel YYModel对于NSObject的扩展
    YYClassInfo 类信息
    YYModel.h YYModel的头文件

    详细分析

    以一个例子来分析,外部是Book对象,内部有一个Author对象。

        NSString *json = @"{ 
        "author":{ 
            "name":"J.K.Rowling", 
            "birthday":"1965-07-31T00:00:00+0000" 
        }, 
        "name":"Harry Potter", 
        "pages":256 
        }";
        
        Book *book = [Book yy_modelWithJSON:json];
    

    yy_modelWithJSON

    入口从[NSObject yy_modelWithJSON]进入

    + (instancetype)yy_modelWithJSON:(id)json {
        NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
        return [self yy_modelWithDictionary:dic];
    }
    

    _yy_dictionaryWithJSON:将JSON的数据(String或者NSData)转换成NSDictionary,主要使用系统方法[NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];

    yy_modelWithDictionary

    + (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
        ...
        Class cls = [self class];
        _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
        if (modelMeta->_hasCustomClassFromDictionary) {
            cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
        }
        
        NSObject *one = [cls new];
        if ([one yy_modelSetWithDictionary:dictionary]) return one;
        return nil;
    }
    

    modelCustomClassForDictionary - Model类可以重载这个方法,将JSON转换成另外一个Model类
    后续处理都放在了yy_modelSetWithDictionary这个方法

    yy_modelSetWithDictionary

    首先根据Class信息构造出_YYModelMeta

    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
    

    _YYModelMeta中包含如下属性:

    • YYClassInfo *_classInfo:类信息,例如class、superclass、ivarInfo、methodInfos、propertyInfos
    • NSDictionary *_mapper:属性key和对应的_YYModelPropertyMeta
    {
        author = "<_YYModelPropertyMeta: 0x6080000f5c00>";
        name = "<_YYModelPropertyMeta: 0x6080000f5b00>";
        pages = "<_YYModelPropertyMeta: 0x6080000f5b80>";
    }
    
    

    看下Name里面对应的_YYModelPropertyMeta的内容:

     
    _YYModelPropertyMeta
    * _name: 对应的是property的名字
    * _nsType:对应property的类型
    * _getter:getter方法
    * _setter:setter方法
    
    • NSArray *_allPropertyMetas:所有的_YYModelPropertyMeta
    • NSArray *_keyPathPropertyMetas:Array<_YYModelPropertyMeta>, property meta which is mapped to a key path
    • NSArray *_multiKeysPropertyMetas:Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys.

    数据填充

        if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
            CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
            if (modelMeta->_keyPathPropertyMetas) {
                CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                     CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                     ModelSetWithPropertyMetaArrayFunction,
                                     &context);
            }
            if (modelMeta->_multiKeysPropertyMetas) {
                CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                     CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                     ModelSetWithPropertyMetaArrayFunction,
                                     &context);
            }
        } else {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                                 CFRangeMake(0, modelMeta->_keyMappedCount),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    

    CFDictionaryApplyFunction/CFArrayApplyFunction:针对NSDictionary和NSArray的每一个值,执行一个方法。Context作为方法中一个参数,带入了Model的信息。

    Context数据结构如下:

    typedef struct {
        void *modelMeta;  ///< _YYModelMeta
        void *model;      ///< id (self)
        void *dictionary; ///< NSDictionary (json)
    } ModelSetContext;
    

    ModelSetWithDictionaryFunction

    static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
        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);
        while (propertyMeta) {
            if (propertyMeta->_setter) {
                ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
            }
            propertyMeta = propertyMeta->_next;
        };
    }
    

    这个方法是将Dictionary的数据填充到Model的核心过程。
    通过Context获取meta(Model的类信息),通过meta获取当前Key的propertyMeta(属性信息),递归调用ModealSetValueForProperty填充model里面对应Key的Property。

    ModelSetValueForProperty

    这个方法会将数据填充到Model对应的Property中。

    对于普通数据类型的数据填充,大体如下:

    switch (meta->_nsType) {
                    case YYEncodingTypeNSString:
                    case YYEncodingTypeNSMutableString: {
                        if ([value isKindOfClass:[NSString class]]) {
                            if (meta->_nsType == YYEncodingTypeNSString) {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                            } else {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);
                            }
                        }
    

    对于内嵌的对象属性,处理如下:
    Value通常是一个NSDicationary,如果有getter方法,获取这个property的对象,如果为nill则创建一个实例,再通过[one yy_modelSetWithDictionary:value],填充这个property对象。

                case YYEncodingTypeObject: {
                    ...
                    else if ([value isKindOfClass:[NSDictionary class]]) {
                        NSObject *one = nil;
                        if (meta->_getter) {
                            one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
                        }
                        if (one) {
                            [one yy_modelSetWithDictionary:value];
                        } else {
                            Class cls = meta->_cls;
                            if (meta->_hasCustomClassFromDictionary) {
                                cls = [cls modelCustomClassForDictionary:value];
                                if (!cls) cls = meta->_genericCls; // for xcode code coverage
                            }
                            one = [cls new];
                            [one yy_modelSetWithDictionary:value];
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
                        }
                    }
                } break;
    

    最佳实践

    force_inline

    在YYModel实现中大量使用force_inline关键词来修饰方法,inline的作用可以参考Wikipedia: Inline Function。Inline Function会在编译阶段将方法实现直接拷贝到调用处,减少方法参数传递和查找,可以提高运行效率。

    YYMode的使用方法如下:

    #define force_inline __inline__ __attribute__((always_inline))
    
    static force_inline YYEncodingNSType YYClassGetNSType(Class cls) {
        ...
    }
    

    一次性初始化

    对于一次性初始化的代码尽量放在dispatch_once block中,保证只会初始化一次。

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    

    Lock

    通过Lock来保证多线程执行的一致性

    static dispatch_semaphore_t lock;
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    // do something
    dispatch_semaphore_signal(lock);
    

    缓存的实现

    通过CFDictionaryCreateMutable实现了一个简易的文件缓存,注意在读取和写入缓存的时候都使用了Lock来保证多线程一致性。

    + (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->_classInfo.needUpdate) {
            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;
    }
    

    总结

    YYModel是一个非常简洁、高性能的Model解析库,作者使用了大量的runtime方式解析class内部信息,使用了inline、缓存、Lock等方式提高了性能和安全性。
    多读经典的开源库,理解作者的实现方式,对于提高iOS设计和编程能力有很大的帮助。

  • 相关阅读:
    LintCode "Subarray Sum II"
    LintCode "Maximum Subarray Difference"
    LeetCode "Flip Game II"
    LintCode "Sliding Window Median" & "Data Stream Median"
    LintCode "Permutation Index"
    LintCode "Count of Smaller Number before itself"
    LeetCode "Nim Game"
    Etcd在Linux CentOS7下载、安装
    CentOS7 查看开启端口
    CentOS7-防火墙firewall 状态、重启、关闭
  • 原文地址:https://www.cnblogs.com/wdsunny/p/8927320.html
Copyright © 2020-2023  润新知