Links
Comment on page

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
  • 拓展
标志
含义
NULL
(void *)0
C指针的字面零值
nil
(id)0
Objective-C对象的字面零值
Nil
(Class)0
Objective-C类的字面零值
NSNull
[NSNull null]
用来表示零值的单独的对象

static inline 内联函数

__unsafe_unretained

__bridge

OCC指针的转换

命名对内对外

  • 对内的加_前缀
  • 对外的不加
  • CoreFoundation中也挺常见

CFDictionaryApplyFunction & CFDictionaryApplyFunction 函数

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

性能优化Tips:

  • 来自文章开头YY作者的文章,感觉相当重要这里Copy来方便看
1. 缓存
Model JSON 转换过程中需要很多类的元数据,如果数据足够小,则全部缓存到内存中。
2. 查表
当遇到多项选择的条件时,要尽量使用查表法实现,比如 switch/caseC 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. 减少遍历的循环次数
JSONModel 转换前,Model 的属性个数和 JSON 的属性个数都是已知的,这时选择数量较少的那一方进行遍历,会节省很多时间。

参考