從YYModel源碼看模型轉(zhuǎn)換

導(dǎo)語:YYModel庫是優(yōu)秀的模型轉(zhuǎn)換庫,可自動(dòng)處理模型轉(zhuǎn)換(從JSON到Model 和 Model到JSON)的工作认轨。在項(xiàng)目中基于YYModel打造QSBaseModel,本文主要分析源碼月培,最后簡(jiǎn)單介紹QSBaseModel的情況嘁字。

一、概述

YYModel庫可以實(shí)現(xiàn) JSON轉(zhuǎn)Model杉畜、Model轉(zhuǎn)JSON纪蜒、Model的序列化和反序列化等;在 JSON轉(zhuǎn)Model過程中此叠,可以設(shè)置屬性名和JSON Key的映射纯续、屬性的黑白名單等。YYModel的具體使用可以參考YYModel灭袁,那里已經(jīng)說得很詳細(xì)了猬错。

1、YYModel源碼文件#####

源碼文件總共5個(gè)茸歧,分別如下:

  • YYModel.h :YYMode庫的頭文件倦炒,在項(xiàng)目中#import"YYModel.h",就可以使用YYModel了
  • NSObject+YYModel.hNSObject+YYModel.m:NSObject的YYModel分類软瞎,YYModel的主要功能代碼在這部分
  • YYClassInfo.h 和 **YYClassInfo.m ** :包含YYClassIvarInfo逢唤、YYClassMethodInfo拉讯、YYClassPropertyInfo、YYClassInfo等類的定義和實(shí)現(xiàn)智玻。將Model的成員變量遂唧、方法芙代、成員屬性以及類等信息抽象成YYClassIvarInfo(成員變量)吊奢、YYClassMethodInfo(方法 )、YYClassPropertyInfo(成員屬性)纹烹、YYClassInfo(類)等對(duì)象页滚。其中還定義了枚舉類型YYEncodingType,列舉了各類編碼信息铺呵,占據(jù)1個(gè)字節(jié)裹驰,包括值類型(YYEncodingTypeMask = 0xFF) 、方法限定類型(YYEncodingTypeQualifierMask = 0xFF00)片挂、屬性修飾類型(YYEncodingTypePropertyMask = 0xFF0000 )幻林。
2、JSON->Model基本流程#####

類方法 + (instancetype)yy_modelWithJSON:函數(shù)是解析JSON的入口音念。從它內(nèi)部的函數(shù)調(diào)用鏈沪饺,可以發(fā)現(xiàn)解析流程主要分三步:

1)將JSON轉(zhuǎn)換成了字典

//NSObject (YYModel) - 解析JSON的入口函數(shù)
+ (instancetype)yy_modelWithJSON:(id)json {
    NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
    //....
}

//NSObject (YYModel) - 真正將JSON轉(zhuǎn)字典的函數(shù)
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
    //...
    return dic;
}

2)根據(jù)類對(duì)象,獲取YYModelMeta對(duì)象

YYModelMeta是描述Model的實(shí)例對(duì)象信息非常重要的類

 //NSObject (YYModel) - 解析JSON的入口函數(shù)
+ (instancetype)yy_modelWithJSON:(id)json {
    //....
    return [self yy_modelWithDictionary:dic];
}

//NSObject (YYModel)  - 獲得YYModelMeta對(duì)象
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
     //...   
    //先獲取當(dāng)前類對(duì)象闷愤,然后根據(jù)類對(duì)象獲得**YYModelMeta對(duì)象**
    Class cls = [self class];
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    //...
}

說明2-1:modelCustomClassForDictionary:函數(shù)在獲得YYModelMeta對(duì)象之后整葡,可以修改解析數(shù)據(jù)對(duì)應(yīng)的Class,一般用來修改Model屬性值對(duì)應(yīng)的Class讥脐。

 // _YYModelMeta -  獲取和緩存了YYModelMeta對(duì)象
 + (instancetype)metaWithClass:(Class)cls {
    //...
    meta = [[_YYModelMeta alloc] initWithClass:cls];
    //...
}

 // _YYModelMeta - 真正將類對(duì)象 轉(zhuǎn)成  _YYModelMeta對(duì)象的函數(shù)
- (instancetype)initWithClass:(Class)cls {
    //獲取類信息對(duì)象
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
    //實(shí)例化_YYModelMeta其他成員變量....
 }

說明2-2:YYModelMeta對(duì)象包含類信息YYClassInfo對(duì)象遭居、解析數(shù)據(jù)等信息。而YYClassInfo對(duì)象中又包含方法列表旬渠、成員變量列表和屬性列表俱萍。這些信息都是JSON解析成Model所必須的。

3)解析字典告丢,獲取Model

//NSObject (YYModel) 
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
        //...
        if (modelMeta->_hasCustomTransformFromDictionary) {
            return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
        }
        return YES;
}

說明3-1:modelCustomTransformFromDictionary:函數(shù)可以在JSON 轉(zhuǎn)換成Model 后調(diào)用枪蘑,在該函數(shù)實(shí)現(xiàn)中,可以對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)芋齿,校驗(yàn)不通過腥寇,返回 NO,則該 Model 會(huì)被忽略觅捆;也可以處理YYModel自動(dòng)轉(zhuǎn)換不能完成的工作赦役。

//靜態(tài)函數(shù)
static void ModelSetValueForProperty(__unsafe_unretained id model,
                                 __unsafe_unretained id value,
                                 __unsafe_unretained _YYModelPropertyMeta *meta) {
      //...
}

說明3-2:ModelSetValueForProperty是真正處理數(shù)據(jù)解析,為屬性賦值的函數(shù)栅炒。

總結(jié):YYModel中JSON解析的關(guān)鍵階段在于:獲取YYModelMeta對(duì)象 和 解析字典掂摔。為此术羔,YYModel設(shè)計(jì)了YYModelMeta、_YYModelPropertyMeta乙漓、YYEncodingType级历、YYClassInfo、YYClassIvarInfo叭披、YYClassMethodInfo寥殖、YYClassPropertyInfo等一系列類。

3涩蜘、Model -> JSON的基本流程#####

實(shí)例方法- (NSString *)yy_modelToJSONObject是Model轉(zhuǎn)JSON的入口函數(shù)嚼贡,從它內(nèi)部的函數(shù)調(diào)用鏈,發(fā)現(xiàn)調(diào)用ModelToJSONObjectRecursive來將Model轉(zhuǎn)成id對(duì)象同诫。最后只返回屬于NSArray或NSDictionary類型的id對(duì)象粤策。

- (id)yy_modelToJSONObject {
    id jsonObject = ModelToJSONObjectRecursive(self);
    if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject;
    if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject;
        return nil;
 }

說明1:Model -> JSON的關(guān)鍵在于:遞歸調(diào)用ModelToJSONObjectRecursive函數(shù),實(shí)現(xiàn)對(duì)model各個(gè)屬性的的轉(zhuǎn)換误窖,最終生成只包含NSArray/NSDictionary/NSString/NSNumber/NSNull的JSON對(duì)象叮盘。

說明2:通過yy_modelToJSONObject得到JSON對(duì)象之后,還可以通過NSJSONSerialization的dataWithJSONObject函數(shù)將JSONObject為NSData對(duì)象霹俺,這也解釋了為什么JSONObject必須是NSArray或NSDictionary類型柔吼,最后將NSData對(duì)象轉(zhuǎn)成NSString對(duì)象,即JSON字符串吭服。

二嚷堡、核心類之YYModelMeta

YYModelMeta通過- (instancetype)initWithClass:(Class)cls方法初始化YYModelMeta對(duì)象,傳入的是Model的Class艇棕。

1蝌戒、YYModelMeta的成員變量
@interface _YYModelMeta : NSObject {
    @package
    //類信息對(duì)象
    YYClassInfo *_classInfo;
    //所有屬性的信息, key為鍵,propertyMeta為值
    NSDictionary *_mapper;
    // 所有屬性的信息
    NSArray *_allPropertyMetas;
    // 映射到keyPath屬性的信息
    NSArray *_keyPathPropertyMetas;
    //映射到一個(gè)數(shù)組的屬性信息
    NSArray *_multiKeysPropertyMetas;
    //所有屬性映射的個(gè)數(shù)
    NSUInteger _keyMappedCount;
    //model的類型
    YYEncodingNSType _nsType;

    BOOL _hasCustomWillTransformFromDictionary;  //是否實(shí)現(xiàn)了modelCustomWillTransformFromDictionary函數(shù)
    BOOL _hasCustomTransformFromDictionary; //是否實(shí)現(xiàn)了modelCustomTransformFromDictionary函數(shù)
    BOOL _hasCustomTransformToDictionary; //是否實(shí)現(xiàn)了modelCustomTransformToDictionary函數(shù)
    BOOL _hasCustomClassFromDictionary; //是否實(shí)現(xiàn)了modelCustomClassForDictionary函數(shù)
}

說明1-1:_mapper存放的是所有以key為鍵沼琉,_YYModelPropertyMeta對(duì)象為值的鍵值對(duì)北苟;_allPropertyMetas、_keyPathPropertyMetas打瘪、_multiKeysPropertyMetas這些數(shù)組結(jié)構(gòu)中存放的都是_YYModelPropertyMeta對(duì)象友鼻。

說明1-2:如果JSON key是使用keyPath來描述的,那么_keyPathPropertyMetas保存的是映射到keyPath屬性的信息闺骚。

說明1-3:如果一個(gè)屬性對(duì)應(yīng)多個(gè)JSON key彩扔,那么_multiKeysPropertyMetas中保存的是映射到一個(gè)數(shù)組的屬性信息。

說明1-4:Model屬性的信息被封裝在YYModelPropertyMeta對(duì)象中僻爽。

2虫碉、YYModelMeta的初始化
 // YYModelMeta - 根據(jù)Model的類對(duì)象獲取類信息對(duì)象classInfo,然后根據(jù)classInfo去初始化成員變量胸梆。
- (instancetype)initWithClass:(Class)cls初始化YYModelMeta{
      //
      YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
      if (!classInfo) return nil;
      self = [super init];
      //....
}

在該函數(shù)中敦捧,init過程如下:

1)根據(jù)Class獲取YYClassInfo實(shí)例

2)獲取黑名單(黑名單中的屬性被忽略须板,實(shí)現(xiàn)+ (NSArray *)modelPropertyBlacklist)

3)獲取白名單(白名單之外的屬性都被忽略,實(shí)現(xiàn) + (NSArray *)modelPropertyWhitelist)

4)獲取類型為集合的屬性中存儲(chǔ)的類類型(實(shí)現(xiàn)+ (NSDictionary *)modelContainerPropertyGenericClass)

5)遍歷Model類及其父類(父類不包括NSObject)的屬性信息兢卵。在遍歷過程中將YYClassPropertyInfo對(duì)象轉(zhuǎn)成_YYModelPropertyMeta對(duì)象习瑰,根據(jù)2)、3)秽荤、4)獲取的信息甜奄,設(shè)置_allPropertyMetas屬性。

6)獲取屬性映射字典王滤,如果沒有沒有指定映射或者說還有部分屬性沒有指定映射贺嫂,就認(rèn)為JSON中key(mappedToKey)和屬性名是一樣的滓鸠,并據(jù)此設(shè)置_mapper雁乡、_keyPathPropertyMetas、_multiKeysPropertyMetas糜俗、_keyMappedCount屬性踱稍。

說明2-1:通過_YYModelMeta對(duì)象的初始化,可了解到我們?yōu)镸odel類設(shè)置的屬性映射悠抹、屬性容器類中數(shù)據(jù)類型珠月、黑白名單等方法,是在這里發(fā)揮作用楔敌,影響Model各個(gè)屬性的賦值的啤挎。

三、核心類之YYClassInfo####

YYClassInfo描述Model類 類信息 的類

1卵凑、YYClassInfo的屬性信息#####
@interface YYClassInfo : NSObject  
@property (nonatomic, assign, readonly) Class cls; ///< class object
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object
@property (nullable, nonatomic, assign, readonly) Class metaCls;  ///< class's meta class object
@property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class
@property (nonatomic, strong, readonly) NSString *name; ///< class name
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info
@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; ///< properties
@end

說明1-1:YYModel將成員變量庆聘、方法、成員屬性以及類這四類信息勺卢,從C層面的函數(shù)調(diào)用抽象成OC的類伙判,這些類分別是YYClassIvarInfo(成員變量)、YYClassMethodInfo(方法 )黑忱、YYClassPropertyInfo(成員屬性)宴抚、YYClassInfo(類)。

說明1-2:YYClassInfo中 成員變量信息ivarInfos甫煞、方法信息methodInfos菇曲、屬性信息propertyInfos分別存放三者的name為鍵霎俩,以后三者為值的字典脏榆,由于YYModel使用遍歷屬性的方式來達(dá)到模型轉(zhuǎn)換的目的,所以其中的propertyInfos起比較重要的作用闽瓢。

2埃跷、YYClassInfo對(duì)象的初始化#####

在YYModelMeta的初始化中是調(diào)用classInfoWithClass:來獲取YYClassInfo對(duì)象的蕊玷,然而真正初始化的地方在initWithClass函數(shù)

//YYClassInfo-獲得和緩存了YYClassInfo對(duì)象
+ (nullable instancetype)classInfoWithClass:(Class)cls{
   if (!info) {
      info = [[YYClassInfo alloc] initWithClass:cls];
      //...
    }
    //...
    return info;
}

//YYClassInfo-真正的初始化函數(shù)
- (instancetype)initWithClass:(Class)cls {
    if (!cls) return nil;
    self = [super init];
    _cls = cls;
    _superCls = class_getSuperclass(cls);
    _isMeta = class_isMetaClass(cls);
    if (!_isMeta) {
        _metaCls = objc_getMetaClass(class_getName(cls));
    }
    _name = NSStringFromClass(cls);
    [self _update];

    _superClassInfo = [self.class classInfoWithClass:_superCls];
    return self;
}
//更新ivarInfos邮利、propertyInfos、methodInfos等屬性值
- (void)_update{
      //...
}

在該函數(shù)中垃帅,init過程如下:

1)根據(jù)Class設(shè)置_cls延届、_superCls、_isMeta等成員變量贸诚。

2)調(diào)用私有函數(shù)_update中方庭。設(shè)置ivarInfos、propertyInfos酱固、methodInfos等屬性(最重要)械念。

3)設(shè)置其他屬性。

說明2-1:_update函數(shù)中运悲,將runtime得到的成員變量龄减、屬性和方法信心 分別 封裝成YYClassIvarInfo對(duì)象、YYClassPropertyInfo對(duì)象和YYClassMethodInfo對(duì)象班眯,然后以他們的名字為key希停,對(duì)象為值,存入對(duì)應(yīng)的ivarInfos署隘、propertyInfos宠能、methodInfos這些字典中。

四磁餐、核心類之_YYModelPropertyMeta

在YYModelMeta的初始化中违崇,很重要的工作是,將classInfo中的屬性信息propertyInfos中每一個(gè)YYClassPropertyInfo對(duì)象轉(zhuǎn)成_YYModelPropertyMeta對(duì)象诊霹。_YYModelPropertyMeta是對(duì)Model類屬性信息的描述類羞延。

1、_YYModelPropertyMeta的成員變量
@interface _YYModelPropertyMeta : NSObject {
    @package
    NSString *_name;             ///< property's name
    YYEncodingType _type;        ///< property's type
    YYEncodingNSType _nsType;    ///< property's Foundation type
    BOOL _isCNumber;             ///< is c number type
    Class _cls;                  ///< property's class, or nil
    Class _genericCls;           ///< container's generic class, or nil if threr's no generic class
    SEL _getter;                 ///< getter, or nil if the instances cannot respond
    SEL _setter;                 ///< setter, or nil if the instances cannot respond
    BOOL _isKVCCompatible;       ///< YES if it can access with key-value coding
    BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver
    BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary:

    /*
     property->key:       _mappedToKey:key     _mappedToKeyPath:nil            _mappedToKeyArray:nil
     property->keyPath:   _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
     property->keys:      _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath    _mappedToKeyArray:keys(array)
     */
    NSString *_mappedToKey;      ///< the key mapped to
    NSArray *_mappedToKeyPath;   ///< the key path mapped to (nil if the name is not key path)
    NSArray *_mappedToKeyArray;  ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys)
    YYClassPropertyInfo *_info;  ///< property's info
    _YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key.
}
@end

說明1:_YYModelPropertyMeta的成員變量中包括屬性類型_type畅哑、_nsType肴楷、屬性Class、集合類中的Class(_genericCls)荠呐、setter和getter方法等赛蔫。

說明2:_mappedToKey是映射到的key(@{property : key});_mappedToKeyPath映射到的keyPath(@{property : key1.key2})
_mappedToKeyArray映射到的數(shù)組(@{property : @[key1, key2]})泥张。而每個(gè)_YYModelPropertyMeta中呵恢,這三者只有其中一個(gè)會(huì)有值。有了這三個(gè)屬性媚创,就可以獲取需要轉(zhuǎn)化的對(duì)應(yīng)字典的value了渗钉。

2、_YYModelPropertyMeta的初始化函數(shù)
//_YYModelPropertyMeta
+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
     //...
    return meta;
}

說明2-1:在該函數(shù)中,根據(jù)propertyInfo對(duì)象和generic信息初始化_YYModelPropertyMeta中各個(gè)成員變量鳄橘。其中重要是屬性Foundation類型_nsType的賦值声离。由于YYModel將屬性的類型大致分為三類:C數(shù)值類型、NS系統(tǒng)自帶基本類類型 以及 非常規(guī)類型(如CGSize等結(jié)構(gòu)體)瘫怜,框架中提供一些函數(shù)判斷類型术徊,方便后面的屬性賦值處理。

1)如果屬性類型是NS系統(tǒng)類型鲸湃,更新_nsType屬性值赠涮,具體的類型通過調(diào)用YYClassGetNSType來判斷。源碼如下:

static force_inline YYEncodingNSType YYClassGetNSType(Class cls) {
    if (!cls) return YYEncodingTypeNSUnknown;
    if ([cls isSubclassOfClass:[NSMutableString class]]) return YYEncodingTypeNSMutableString;
    if ([cls isSubclassOfClass:[NSString class]]) return YYEncodingTypeNSString;
    if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return YYEncodingTypeNSDecimalNumber;
    if ([cls isSubclassOfClass:[NSNumber class]]) return YYEncodingTypeNSNumber;
    if ([cls isSubclassOfClass:[NSValue class]]) return YYEncodingTypeNSValue;
    if ([cls isSubclassOfClass:[NSMutableData class]]) return YYEncodingTypeNSMutableData;
    if ([cls isSubclassOfClass:[NSData class]]) return YYEncodingTypeNSData;
    if ([cls isSubclassOfClass:[NSDate class]]) return YYEncodingTypeNSDate;
    if ([cls isSubclassOfClass:[NSURL class]]) return YYEncodingTypeNSURL;
    if ([cls isSubclassOfClass:[NSMutableArray class]]) return YYEncodingTypeNSMutableArray;
    if ([cls isSubclassOfClass:[NSArray class]]) return YYEncodingTypeNSArray;
    if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return YYEncodingTypeNSMutableDictionary;
    if ([cls isSubclassOfClass:[NSDictionary class]]) return YYEncodingTypeNSDictionary;
    if ([cls isSubclassOfClass:[NSMutableSet class]]) return YYEncodingTypeNSMutableSet;
    if ([cls isSubclassOfClass:[NSSet class]]) return YYEncodingTypeNSSet;
    return YYEncodingTypeNSUnknown;
}

說明2-2:由于類簇的原因暗挑,我們是無法在runtime時(shí)獲取屬性是否是mutable的笋除,所以需要先判斷是否為mutable。

**2) **調(diào)用YYEncodingTypeIsCNumber判斷屬性的類型是否是C數(shù)值類型

static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type) {
    switch (type & YYEncodingTypeMask) {
        case YYEncodingTypeBool:
        case YYEncodingTypeInt8:
        case YYEncodingTypeUInt8:
        case YYEncodingTypeInt16:
        case YYEncodingTypeUInt16:
        case YYEncodingTypeInt32:
        case YYEncodingTypeUInt32:
        case YYEncodingTypeInt64:
        case YYEncodingTypeUInt64:
        case YYEncodingTypeFloat:
        case YYEncodingTypeDouble:
        case YYEncodingTypeLongDouble: return YES;
        default: return NO;
    }
}

說明2-3:將BOOL值結(jié)果賦值給_isCNumber炸裆,方便后面屬性賦值時(shí)候的處理垃它。

五、Dictionary -> JSON (解析關(guān)鍵)####

在解析之前晒衩,獲取了YYModelMeta對(duì)象嗤瞎,調(diào)用yy_modelSetWithDictionary去處理解析。

1听系、從yy_modelSetWithDictionary看解析的過程#####
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {

    if (!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;

    //可以修改dic的值
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    }

    //創(chuàng)建集合上下文
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);

    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);
    }

    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

該函數(shù)處理流程:

1)如果實(shí)現(xiàn)modelCustomWillTransformFromDictionary:這個(gè)函數(shù),就按照實(shí)現(xiàn)方法修改解析的Dic的數(shù)據(jù);

2)創(chuàng)建了一個(gè)模型集合上下文context虹菲,存放了modelMeta對(duì)象靠胜、model對(duì)象、解析的數(shù)據(jù)字典dictionary等數(shù)據(jù)毕源,將context交給ModelSetWithPropertyMetaArrayFunction函數(shù)去解析(給model賦值);

3)解析完成后浪漠,可以實(shí)現(xiàn)modelCustomTransformFromDictionary方法,對(duì)model的屬性進(jìn)行更改霎褐。

說明1-1: 由于modelCustomWillTransformFromDictionary和modelCustomTransformFromDictionary函數(shù)都是交給開發(fā)去自定義實(shí)現(xiàn)的址愿,不是框架處理的事情,我們關(guān)注源碼中是如何解析字典的冻璃。

說明1-2:當(dāng)modelMeta->_keyMappedCount大于等于CFDictionaryGetCount((CFDictionaryRef)dic)的時(shí)候响谓,遍歷字典,設(shè)置并以字典為基準(zhǔn)省艳,設(shè)置模型中與字典相對(duì)應(yīng)的屬性(ModelSetWithDictionaryFunction函數(shù))娘纷,如果_keyPathPropertyMetas不為空,設(shè)置映射到keyPath的屬性跋炕,如果_multiKeysPropertyMetas不為空赖晶,設(shè)置映射到數(shù)組的屬性。否則直接通過_allPropertyMetas設(shè)置所有屬性辐烂。

2遏插、ModelSetWithDictionaryFunction函數(shù)功能#####

在yy_modelSetWithDictionary函數(shù)中捂贿,只有當(dāng)modelMeta->_keyMappedCount大于等于CFDictionaryGetCount((CFDictionaryRef)dic)時(shí),才調(diào)用ModelSetWithDictionaryFunction函數(shù)胳嘲,其實(shí)現(xiàn)如下:

//字典回調(diào)函數(shù)
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) {
        // 映射到同個(gè)key之后眷蜓,這里循環(huán)賦給屬性相同的值
        if (propertyMeta->_setter) {
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}

該函數(shù)處理流程:

1)根據(jù)字典的key從_mapper中獲取對(duì)應(yīng)的_YYModelPropertyMeta

2) 調(diào)用ModelSetValueForProperty設(shè)置屬性值。

3)如果propertyMeta的_next不為空胎围,即表示有多個(gè)屬性被映射到了同一個(gè)key吁系。這樣只需要從字典中取一次value,就可以設(shè)置被映射到同一個(gè)key的所有屬性白魂。

3汽纤、ModelSetWithPropertyMetaArrayFunction函數(shù)功能#####

通過_allPropertyMetas設(shè)置時(shí),則需要對(duì)每個(gè)屬性都對(duì)字典做一次取值操作

//數(shù)組回調(diào)函數(shù)
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
    if (!propertyMeta->_setter) return;
    id value = nil;

    if (propertyMeta->_mappedToKeyArray) {
         // 映射到多個(gè)key
        value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
    } else if (propertyMeta->_mappedToKeyPath) {
        // 映射到keyPath
        value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
    } else {
       // 映射到一個(gè)key
        value = [dictionary objectForKey:propertyMeta->_mappedToKey];
    }

    if (value) {
        __unsafe_unretained id model = (__bridge id)(context->model);
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}

該函數(shù)處理流程:

1)如果一個(gè)屬性映射到多個(gè)JSON key(propertyMeta->_mappedToKeyArray不為空)福荸,那么只取第一個(gè)匹配成功的key蕴坪,后續(xù)的key將會(huì)被略過。

static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {
    id value = nil;
    for (NSString *key in multiKeys) {
        if ([key isKindOfClass:[NSString class]]) {
            value = dic[key];
            if (value) break;
        } else {
            value = YYValueForKeyPath(dic, (NSArray *)key);
            if (value) break;
        }
    }
    return value;
}
  1. 如果映射關(guān)系中敬锐,通過key1.key2來描述JSON Key(propertyMeta->_mappedToKeyPath不為空)背传,那么將映射的keyPath以.為分隔符拆分成多個(gè)字符串,并以數(shù)組的形式存儲(chǔ)台夺,最終用循環(huán)獲取value的方式代替valueForKeyPath:避免從非字典取value時(shí)發(fā)生崩潰径玖。

    static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) {
    id value = nil;
    for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) {
    value = dic[keyPaths[i]];
    if (i + 1 < max) {
    if ([value isKindOfClass:[NSDictionary class]]) {
    dic = value;
    } else {
    return nil;
    }
    }
    }
    return value;
    }

3)獲取value的值,然后交給ModelSetValueForProperty去設(shè)置對(duì)應(yīng)屬性的值颤介。

4梳星、真正設(shè)置屬性值ModelSetValueForProperty函數(shù)#####

在ModelSetValueForProperty中,為屬性賦值分為三步:

1) 處理屬性類型是C的數(shù)值類型的情況

 if (meta->_isCNumber) {
    NSNumber *num = YYNSNumberCreateFromID(value);
    ModelSetNumberToProperty(model, num, meta);
    if (num) [num class]; // hold the number
  }else{
      //...
  }

2) 處理屬性類型是NS系統(tǒng)定義類型的情況

如果是屬性類型是如NSString滚朵、NSNumber這樣的非集合類冤灾、采用objc_msgSend直接調(diào)用setter,給屬性賦值辕近;如果是集合類韵吨,處理比較麻煩,以屬性類型是NSArray或NSMutableArray(_nsType是YYEncodingTypeNSArray或YYEncodingTypeNSMutableArray)的處理源碼為例:

case YYEncodingTypeNSArray:
case YYEncodingTypeNSMutableArray: {
     if (meta->_genericCls) {
        NSArray *valueArr = nil;
        if ([value isKindOfClass:[NSArray class]]) valueArr = value;
        else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects;
        if (valueArr) {
              NSMutableArray *objectArr = [NSMutableArray new];
              for (id one in valueArr) {
              // 已經(jīng)是所要對(duì)象了
              if ([one isKindOfClass:meta->_genericCls]) {
                  [objectArr addObject:one];
              } else if ([one isKindOfClass:[NSDictionary class]]) {
                  // 給的是字典移宅,要自己構(gòu)造
                  Class cls = meta->_genericCls;
                  if (meta->_hasCustomClassFromDictionary) {
                      // 由字典返回對(duì)應(yīng)的類(透?jìng)? <<< 由開發(fā)者實(shí)現(xiàn)
                        cls = [cls modelCustomClassForDictionary:one];
                      if (!cls) cls = meta->_genericCls; // for xcode code coverage
                  }
                  NSObject *newOne = [cls new];
                  // 根據(jù)獲得的類归粉,創(chuàng)建實(shí)例
                  [newOne yy_modelSetWithDictionary:one];
                  if (newOne) [objectArr addObject:newOne];
              }
        }
        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr);
    }
} else {
      if ([value isKindOfClass:[NSArray class]]) {
          if (meta->_nsType == YYEncodingTypeNSArray) {
              ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
        } else {
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                       meta->_setter,
                                                       ((NSArray *)value).mutableCopy);
        }
      } else if ([value isKindOfClass:[NSSet class]]) {
          if (meta->_nsType == YYEncodingTypeNSArray) {
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects);
          } else {
              ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                       meta->_setter,
                                                       ((NSSet *)value).allObjects.mutableCopy);
          }
      }
}

1)在沒有指定集合中的數(shù)據(jù)類型,即_genericCls為nil的情形下吞杭,如果value是NSArray或者NSSet類型盏浇,那么YYModel將value
直接賦給屬性,不做任何解析芽狗。

2)在指定了集合中的數(shù)據(jù)類型,即_genericCls不為nil的情形下绢掰,會(huì)對(duì)每個(gè)元素進(jìn)行解析并構(gòu)造成相應(yīng)的實(shí)例。如果集合元素依然是一個(gè)字典,那么就會(huì)調(diào)用yy_modelSetWithDictionary解析滴劲。在解析的過程中攻晒,可以實(shí)現(xiàn)modelCustomClassForDictionary:方法,重新指定集合中的數(shù)據(jù)類型班挖。

3) 處理屬性類型是非常規(guī)類型

非常規(guī)類型是C數(shù)值類型鲁捏、NS定義的類型之外的類型,包括但不限于自定義Model類萧芙、CG結(jié)構(gòu)體等给梅。主要是自定義Model類,以解析自定義Model類為例双揪,源碼如下:

 case YYEncodingTypeObject: {
       if (isNull) {
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
         } else if ([value isKindOfClass:meta->_cls] || !meta->_cls) {
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value);
        } 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);
            }
        }
  } 

說明:如果屬性是自定義Model類动羽,通過Class實(shí)例化對(duì)象,然后調(diào)用yy_modelSetWithDictionary解析數(shù)據(jù)渔期,給Model實(shí)例各個(gè)屬性賦值运吓。

六、Model -> JSONModel (重點(diǎn))####

分析ModelToJSONObjectRecursive函數(shù)疯趟,可以看出根據(jù)Model的類型來處理的拘哨,Model的類型分:非集合類型、集合類型信峻、自定義Model類

1倦青、model是非集合類型的處理#####
//model是基本類型 : kCFNull、NSString站欺、NSNumber或者nil姨夹,直接返回model
if (!model || model == (id)kCFNull) return model;
if (!model || model == (id)kCFNull) return model;
if ([model isKindOfClass:[NSString class]]) return model;
if ([model isKindOfClass:[NSNumber class]]) return model;

//model類型是NSURL、NSAttributedString矾策、NSDate類型,轉(zhuǎn)成字符串返回峭沦。是NSData就返回nil
if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString;
if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model];
if ([model isKindOfClass:[NSData class]]) return nil;
2贾虽、model是集合類型的處理#####
//如果是NSDictionary,能JSON化就直接返回吼鱼,否則調(diào)用ModelToJSONObjectRecursive遞歸處理蓬豁,最后添加到字典中
if ([model isKindOfClass:[NSDictionary class]]) {
    if ([NSJSONSerialization isValidJSONObject:model]) return model;
    NSMutableDictionary *newDic = [NSMutableDictionary new];
    [((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
        NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
        if (!stringKey) return;
        id jsonObj = ModelToJSONObjectRecursive(obj);
        if (!jsonObj) jsonObj = (id)kCFNull;
        newDic[stringKey] = jsonObj;
    }];
    return newDic;
}

//如果是NSSet、NSArray菇肃,并且元素是基本類型就直接添加到數(shù)組中地粪,否則調(diào)用ModelToJSONObjectRecursive嵌套解析成基本類型琐谤,然后添加到數(shù)組中
if ([model isKindOfClass:[NSSet class]]) {
    NSArray *array = ((NSSet *)model).allObjects;
    if ([NSJSONSerialization isValidJSONObject:array]) return array;
    NSMutableArray *newArray = [NSMutableArray new];
    for (id obj in array) {
        if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
            [newArray addObject:obj];
        } else {
            id jsonObj = ModelToJSONObjectRecursive(obj);
            if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
        }
    }
    return newArray;
}

if ([model isKindOfClass:[NSArray class]]) {
    if ([NSJSONSerialization isValidJSONObject:model]) return model;
    NSMutableArray *newArray = [NSMutableArray new];
    for (id obj in (NSArray *)model) {
        if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
            [newArray addObject:obj];
        } else {
            id jsonObj = ModelToJSONObjectRecursive(obj);
            if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
        }
    }
    return newArray;
}
3蟆技、model是自定義Model類的處理#####

獲取自定義Model類的modelMeta信息,然后遍歷modelMeta._mapper,通過映射關(guān)系獲取屬性值质礼,然后構(gòu)造成字典旺聚。

1) 根據(jù)propertyMet對(duì)象提供的屬性類型相關(guān)信息,處理屬性值value眶蕉,如果value為nil砰粹,放棄當(dāng)前屬性值的處理,去處理下一個(gè)屬性值造挽。

    if (!propertyMeta->_getter) return;
    
    id value = nil;
    if (propertyMeta->_isCNumber) {
        value = ModelCreateNumberFromProperty(model, propertyMeta);
    } else if (propertyMeta->_nsType) {
        id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
        value = ModelToJSONObjectRecursive(v);
    } else {
        switch (propertyMeta->_type & YYEncodingTypeMask) {
            case YYEncodingTypeObject: {
                id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                value = ModelToJSONObjectRecursive(v);
                if (value == (id)kCFNull) value = nil;
            } break;
            case YYEncodingTypeClass: {
                Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                value = v ? NSStringFromClass(v) : nil;
            } break;
            case YYEncodingTypeSEL: {
                SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                value = v ? NSStringFromSelector(v) : nil;
            } break;
            default: break;
        }
    }
    if (!value) return;

2)根據(jù)propertyMeta的_mappedToKeyPath或_mappedToKey構(gòu)造字典

if (propertyMeta->_mappedToKeyPath) {
    NSMutableDictionary *superDic = dic;
    NSMutableDictionary *subDic = nil;
    for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
        NSString *key = propertyMeta->_mappedToKeyPath[i];
        if (i + 1 == max) { // end  { ext = { d = Apple; }; }, 最后的key才賦值, 即superDic[@"d"] = @"Apple"
            if (!superDic[key]) superDic[key] = value;
            break;
        }

        subDic = superDic[key];
        if (subDic) {
            // 說明這一層字典已經(jīng)有鍵值對(duì)了
            if ([subDic isKindOfClass:[NSDictionary class]]) {
                // 拷貝成可變的(沒這一句也可碱璃,因?yàn)閯傞_始時(shí)創(chuàng)建的都是NSMutableDictionary), 方便i + 1 == max時(shí)進(jìn)行賦值
                subDic = subDic.mutableCopy;
                superDic[key] = subDic;
            } else {
                break;
            }
        } else {
            // key下沒有value,創(chuàng)建可變字典賦給當(dāng)前的key
            subDic = [NSMutableDictionary new];
            superDic[key] = subDic;
        }
        // 最頂層的字典(@{@"a" : @{@"b" : @"c"}}饭入,即字典@{@"b" : @"c"})
        superDic = subDic;
        subDic = nil;
    }
} else {
    if (!dic[propertyMeta->_mappedToKey]) {
        dic[propertyMeta->_mappedToKey] = value;
    }
}

七嵌器、QSBaseModel封裝

1、為什么要封裝YYModel

1)YYModel很強(qiáng)大圣拄,可以通過實(shí)現(xiàn)NSObject+YYModel中的相關(guān)方法操作嘴秸,干預(yù)模型轉(zhuǎn)換的過程。由于項(xiàng)目中后臺(tái)提供的數(shù)據(jù)接口格式固定庇谆,JSON解析中最大的需要是可以指定屬性名和JSON Key的映射 以及 指定容器類中的數(shù)據(jù)類型岳掐,為了稍稍限制開發(fā)人員自由,讓大家一起遵循統(tǒng)一的規(guī)范饭耳。

2)通過YYModel的相關(guān)方法重寫Model類的Coding/Copying/hash/equal/description方法串述,只要Model類繼承QSBaseModel就可以很方便地使用這些方法。

3)如果項(xiàng)目后期替換模型轉(zhuǎn)換庫寞肖,對(duì)業(yè)務(wù)也不會(huì)有什么影響纲酗。

2、QSBaseModel的定義和實(shí)現(xiàn)#####
//QSBaseModel.h
@interface QSBaseModel : NSObject<NSCoding,NSCopying>

+ (instancetype)modelFromJSON:(id)json;

- (NSString *)modelToJSONString;

@end

//QSBaseModel.m
#import "YYModel.h"
#import "QSBaseModel.h"

@implementation QSBaseModel

#pragma mark - 模型轉(zhuǎn)換(public)
+ (instancetype)modelFromJSON:(id)json{
    return [self yy_modelWithJSON:json];
}

- (NSString *)modelToJSONString {
    return [self yy_modelToJSONString];
}

#pragma mark - 序列化和反序列化
- (void)encodeWithCoder:(NSCoder *)aCoder{
    [self yy_modelEncodeWithCoder:aCoder];
}

- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
    return [self yy_modelInitWithCoder:aDecoder];
}

#pragma mark - 實(shí)現(xiàn)copy方法(實(shí)現(xiàn)深拷貝)
- (id)copyWithZone:(NSZone *)zone {
    return [self yy_modelCopy];
}

#pragma mark -重寫hash新蟆、isEqual:和description方法
- (NSUInteger)hash {
    return [self yy_modelHash];
}

- (BOOL)isEqual:(id)object {
    return [self yy_modelIsEqual:object];
}

- (NSString *)description{
    return [self yy_modelDescription];
}

@end

說明1:在指定屬性名和JSON Key的映射時(shí)觅赊,原則上不使用keyPath描述JSON Key。

說明2:因?yàn)閅YModel中將JSON轉(zhuǎn)成Model的過程是琼稻,先生成Model實(shí)例吮螺,再用JSON數(shù)據(jù)給Model的屬性賦值,所以會(huì)出現(xiàn)Model中部分屬性值是nil的情況帕翻,使用這些Model的屬性值前需要簡(jiǎn)單校驗(yàn)鸠补。主要是對(duì)屬性類型是字符串、數(shù)組嘀掸、字典紫岩、集合的校驗(yàn)。下面是校驗(yàn)的宏定義睬塌。

#define ISVALID_STRING(x)                       (x && [x isKindOfClass:[NSString class]] && [x length])
#define ISVALID_ARRAY(x)                        (x && [x isKindOfClass:[NSArray class]] && [x count])
#define ISVALID_DICTIONARY(x)                   (x && [x isKindOfClass:[NSDictionary class]] && [x count])
#define ISVALID_SET(x)                          (x && [x isKindOfClass:[NSSet class]] && [x count])

結(jié)束####

參考資料
YYModel
YYModel閱讀小記

其他

這里只是簡(jiǎn)單記錄了YYModel模型轉(zhuǎn)換的過程泉蝌,YYModel有很多值得學(xué)習(xí)的地方歇万,后面再談

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市梨与,隨后出現(xiàn)的幾起案子堕花,更是在濱河造成了極大的恐慌,老刑警劉巖粥鞋,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缘挽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡呻粹,警方通過查閱死者的電腦和手機(jī)壕曼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來等浊,“玉大人腮郊,你說我怎么就攤上這事〕镅啵” “怎么了轧飞?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)撒踪。 經(jīng)常有香客問我过咬,道長(zhǎng),這世上最難降的妖魔是什么制妄? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任掸绞,我火速辦了婚禮,結(jié)果婚禮上耕捞,老公的妹妹穿的比我還像新娘衔掸。我一直安慰自己,他們只是感情好俺抽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布敞映。 她就那樣靜靜地躺著,像睡著了一般磷斧。 火紅的嫁衣襯著肌膚如雪驱显。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天瞳抓,我揣著相機(jī)與錄音,去河邊找鬼伏恐。 笑死孩哑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翠桦。 我是一名探鬼主播横蜒,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼胳蛮,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了丛晌?” 一聲冷哼從身側(cè)響起仅炊,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎澎蛛,沒想到半個(gè)月后抚垄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谋逻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年呆馁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毁兆。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浙滤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出气堕,到底是詐尸還是另有隱情纺腊,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布茎芭,位于F島的核電站揖膜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏骗爆。R本人自食惡果不足惜次氨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摘投。 院中可真熱鬧煮寡,春花似錦、人聲如沸犀呼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽外臂。三九已至坐儿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宋光,已是汗流浹背貌矿。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留罪佳,地道東北人逛漫。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像赘艳,于是被迫代替她去往敵國(guó)和親酌毡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子克握,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 概述 ? iOS源碼解析—YYModel(YYClassInfo)分析了如何根據(jù)OC的Class對(duì)象構(gòu)建...
    egoCogito_panf閱讀 11,510評(píng)論 4 32
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)枷踏,斷路器菩暗,智...
    卡卡羅2017閱讀 134,601評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件旭蠕、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,033評(píng)論 4 62
  • 終于把前面的base文件夾簡(jiǎn)簡(jiǎn)單單的看了一遍停团,終于可以回到正片上來了,保證不爛尾下梢。 項(xiàng)目天天用yymodel解析數(shù)...
    充滿活力的早晨閱讀 1,358評(píng)論 1 0