01 CodeReading-01-YYModel

感觉值对于优秀源码的学习阅读不是很够,开个坑,希望自己坚持下去 YY作者写的相关文章必看: https://blog.ibireme.com/2015/10/23/ios_model_framework_benchmark/

结构

加上.h只有5个文件相当轻量级的一个转模型框架,十分适合作为源码阅读计划的第一弹

YYModel.h NSObject+YYModel.h NSObject+YYModel.m YYClassInfo.h YYClassInfo.m

YYModel.h

头文件

NSObject+YYModel

负责转模型的操作

类型

YYEncodingNSType

  • 对应Foundation框架中基本类型的枚举

内联函数

YYClassGetNSType(Class cls)

  • 类型映射

  • 返回: YYEncodingNSType

YYEncodingTypeIsCNumber(YYEncodingType type)

  • 判断是否是Cnumber类型

  • 返回: BOOL

NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value)

  • id类型返回NSNumber

  • 其中对空的判断还有小数整数的处理值得一看

NSDate YYNSDateFromString(__unsafe_unretained NSString string)

  • 从字符串获得NSDate对象

  • 作用域的控制值得学习 {}

Class YYNSBlockClass()

  • 获取NSBlock

  • 在判断value类型的时候有用到

id YYValueForKeyPath(unsafe_unretained NSDictionary *dic, unsafe_unretained NSArray *keyPaths)

  • 通过一个 keyPaths 来从字典中获取对应 value

  • 例如 [@"id",@"ID",@"myId"] 来从dic中获取一个ID

id YYValueForMultiKeys(unsafe_unretained NSDictionary *dic, unsafe_unretained NSArray *multiKeys)

  • Get the value with multi key (or key path) from dictionary

  • The dic should be NSDictionary

  • 和上一个方法类似但这里的遍历时如果key不是NSString会调用上面的方法

NSNumber ModelCreateNumberFromProperty(unsafe_unretained id model,unsafe_unretained _YYModelPropertyMeta meta)

  • Get number from property.

  • 这里位移枚举的使用值得一看

void ModelSetNumberToProperty(unsafe_unretained id model,unsafe_unretained NSNumber num,__unsafe_unretained _YYModelPropertyMeta meta)

  • Set number to property.

  • 与上面的方法一对

void ModelSetValueForProperty(unsafe_unretained id model,unsafe_unretained id value,__unsafe_unretained _YYModelPropertyMeta *meta)

  • Set value to model with a property meta.

  • 核心函数之一

void ModelSetWithDictionaryFunction(const void _key, const void _value, void *_context)

  • Apply function for dictionary, to set the key-value pair to model.

  • void CFDictionaryApplyFunction(CFDictionaryRef theDict, CFDictionaryApplierFunction applier, void *context);方法中调用

  • 对字典中所有键值对执行applier函数

void ModelSetWithPropertyMetaArrayFunction(const void _propertyMeta, void _context)

  • Apply function for model property meta, to set dictionary to model.

  • 和上面的类似,是数组的

Class

结构体 ModelSetContext

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

_YYModelPropertyMeta : NSObject

  • A property info in object model.

  • 表示模型对象中的属性信息

  • 对应YYClassPropertyInfo

  • 可理解为 属性 中间件

_YYModelMeta : NSObject

  • A class info in object model.

  • 表示模型的类信息

  • 对应YYClassInfo

  • 可理解为 类 中间件

/// Returns the cached model class meta
+ (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;
}
  • 其中使用了 dispatch_semaphore信号量保证线程安全

  • 建立了一个静态缓存,缓存了每个class对应的_YYModelMeta对象,提高了性能

YYClassInfo

RuntimeC的一些东西,通过面向对象的方式进行了封装,便于理解使用 转模型的基础

类型

YYEncodingType

Type encoding's type.

  • 列举了各类编码信息,包括值类型、方法限定类型、属性修饰类型。YYEncodingType使用掩码的方式对三类不同的枚举信息进行分类,各占据1个字节

///值类型
YYEncodingTypeMask                = 0xFF       
///方法限定类型
YYEncodingTypeQualifierMask      = 0xFF00       
///属性修饰类型
YYEncodingTypePropertyMask      = 0xFF0000

Class

YYClassIvarInfo

成员变量 objc_ivar

YYClassMethodInfo

方法 objc_method

YYClassPropertyInfo

成员属性 property_t

YYClassInfo

类 objc_class

/.../

/// 成员变量列表
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars
/// 方法列表
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods
/// 成员属性
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///<

/.../

Tips:

kCFNull

+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
    if (!json || json == (id)kCFNull) return nil;
    ...
}
  • 点进去

/* Null representant */

typedef const struct CF_BRIDGED_TYPE(NSNull) __CFNull * CFNullRef;

CF_EXPORT
CFTypeID CFNullGetTypeID(void);

CF_EXPORT
const CFNullRef kCFNull;    // the singleton null instance
  • 打印

id null1 = (id)kCFNull;
id null2 = [NSNull null];
  • 打印地址

null1=(NSNull *)0x10426eaf0
null2=(NSNull *)0x10426eaf0
  • 拓展

static inline 内联函数

__unsafe_unretained

__bridge

OCC指针的转换

命名对内对外

  • 对内的加_前缀

  • 对外的不加

  • CoreFoundation中也挺常见

CFDictionaryApplyFunction & CFDictionaryApplyFunction 函数

  • 相对于 Foundation 的方法来说,CoreFoundation 的方法有更高的性能,用 CFArrayApplyFunction()CFDictionaryApplyFunction() 方法来遍历容器类能带来不少性能提升,但代码写起来会非常麻烦。

性能优化Tips:

  • 来自文章开头YY作者的文章,感觉相当重要这里Copy来方便看

1. 缓存
Model JSON 转换过程中需要很多类的元数据,如果数据足够小,则全部缓存到内存中

2. 查表
当遇到多项选择的条件时,要尽量使用查表法实现,比如 switch/case,C Array,如果查表条件是对象,则可以用 NSDictionary 来实现

3. 避免 KVC
Key-Value Coding 使用起来非常方便,但性能上要差于直接调用 Getter/Setter,所以如果能避免 KVC 而用 Getter/Setter 代替,性能会有较大提升

4. 避免 Getter/Setter 调用
如果能直接访问 ivar,则尽量使用 ivar 而不要使用 Getter/Setter 这样也能节省一部分开销

5. 避免多余的内存管理方法
在 ARC 条件下,默认声明的对象是 __strong 类型的,赋值时有可能会产生 retain/release 调用,如果一个变量在其生命周期内不会被释放,则使用 __unsafe_unretained 会节省很大的开销

访问具有 __weak 属性的变量时,实际上会调用 objc_loadWeak()objc_storeWeak() 来完成,这也会带来很大的开销,所以要避免使用 __weak 属性
创建和使用对象时,要尽量避免对象进入 autoreleasepool,以避免额外的资源开销

6. 遍历容器类时,选择更高效的方法
相对于 Foundation 的方法来说,CoreFoundation 的方法有更高的性能,用 CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法来遍历容器类能带来不少性能提升,但代码写起来会非常麻烦。

7. 尽量用纯 C 函数内联函数
使用纯 C 函数可以避免 ObjC 的消息发送带来的开销如果 C 函数比较小,使用 inline 可以避免一部分压栈弹栈等函数调用的开销

8. 减少遍历的循环次数
在 JSON 和 Model 转换前,Model 的属性个数和 JSON 的属性个数都是已知的,这时选择数量较少的那一方进行遍历,会节省很多时间

参考

Last updated