# 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)

* 判断是否是`C`的`number`类型
* 返回: `BOOL`

> NSNumber \*YYNSNumberCreateFromID(\_\_unsafe\_unretained id value)

* 从`id`类型返回`NSNumber`
* 其中对空的判断还有小数整数的处理值得一看

> NSDate *YYNSDateFromString(\_\_unsafe\_unretained NSString* string)

* 从字符串获得`NSDate`对象
* 作用域的控制值得学习 `{}`

> Class YYNSBlockClass()

* 获取`NSBlock`类&#x20;
* 在判断`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,**&#x75;nsafe\_unretained \_YYModelPropertyMeta* meta)

* Get number from property.
* 这里位移枚举的使用值得一看

> void ModelSetNumberToProperty(**unsafe\_unretained id model,**&#x75;nsafe\_unretained NSNumber *num,\_\_unsafe\_unretained \_YYModelPropertyMeta* meta)

* Set number to property.
* 与上面的方法一对

> void ModelSetValueForProperty(**unsafe\_unretained id model,**&#x75;nsafe\_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

```swift
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`
* 可理解为 类 中间件

```swift
/// 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

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

### 类型

#### YYEncodingType

> Type encoding's type.

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

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

### Class

#### YYClassIvarInfo

> 成员变量 objc\_ivar

#### YYClassMethodInfo

> 方法 objc\_method

#### YYClassPropertyInfo

> 成员属性 property\_t

#### YYClassInfo

> 类 objc\_class

```swift
/.../

/// 成员变量列表
@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

```swift
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
    if (!json || json == (id)kCFNull) return nil;
    ...
}
```

* 点进去

```swift
/* 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
```

* 打印

```swift
id null1 = (id)kCFNull;
id null2 = [NSNull null];
```

* 打印地址

```swift
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 内联函数

* 看到这样一个宏 `#define force_inline __inline__ __attribute__((always_inline))`
* 挺好的一篇参考文章: <http://www.cnblogs.com/wyk19910103/p/5660627.html>

### \_\_unsafe\_unretained

* 与`weak`类似但有本质不同
* <https://www.zhihu.com/question/55831650>
* <https://blog.csdn.net/bsplover/article/details/7707964>
* <https://blog.csdn.net/yueyansheng2/article/details/49813987>

### \_\_bridge

`OC`与`C`指针的转换

### 命名对内对外

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

### CFDictionaryApplyFunction & CFDictionaryApplyFunction 函数

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

### 性能优化Tips:

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

```swift
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 的属性个数都是已知的，这时选择数量较少的那一方进行遍历，会节省很多时间。
```

## 参考

* <http://triplecc.github.io/blog/2016-06-05-yymodelyue-du-xiao-ji/>
* <https://lision.me/yymodel_x01/>
* <https://lision.me/yymodel_x02/>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/framework/codereading-01-yymodel.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
