YYModel實(shí)現(xiàn)原理探究

預(yù)備知識(shí)

理解Class與MetalClass

下面這張圖很重要野舶,得記住哦。具體的介紹宰衙,還是看權(quán)威的文章吧??平道,參考文章1文章2;

類圖

個(gè)人理解供炼,在我們獲取每一個(gè)類一屋、實(shí)例對(duì)象的Class時(shí)窘疮,實(shí)際上獲取的是isa對(duì)象,正如上圖所示冀墨,下面的示例代碼也說(shuō)明了情況闸衫, 對(duì)object1、object2對(duì)象獲取class時(shí)獲取的是各自的isa對(duì)像得到class1轧苫、class2楚堤,實(shí)際上class1、class2為同一個(gè)對(duì)象含懊,當(dāng)再次獲取class1、class2的class時(shí)獲取的就是元類了:

    NSObject *object1 = [[NSObject alloc] init];
    NSObject *object2 = [[NSObject alloc] init];
    Class class1 = [object1 class];
    Class class2 = [object2 class];
    NSLog(@"class1:%p, class2:-%p", class1, class2); // class1:0x1b7af3ea0, class2:-0x1b7af3ea0
    
    Class metaClass1 = object_getClass(class1);
    Class metaClass2 = object_getClass(class2);
    NSLog(@"metaClass1:%p, metaClass2:-%p", metaClass1, metaClass2); // metaClass1:0x1b7af3ec8, metaClass2:-0x1b7af3ec8
    
    Class metaClass3 = object_getClass(metaClass1);
    Class metaClass4 = object_getClass(metaClass2);
    NSLog(@"metaClass3:%p, metaClass4:-%p", metaClass3, metaClass4); // metaClass3:0x1b7af3ec8, metaClass4:-0x1b7af3ec8

Method

Method本身是一個(gè)objc_method類型的結(jié)構(gòu)體指針即方法的具體表示衅胀,objc_method是一個(gè)結(jié)構(gòu)體岔乔, 其定義如下:

struct objc_method {
    SEL _Nonnull method_name                                方法名;
    char * _Nullable method_types                           方法類型, 即參數(shù)、返回值類型;
    IMP _Nonnull method_imp                                 方法對(duì)應(yīng)的函數(shù)指針;
}   
// 獲取類所有的方法
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount);
method_getName(method) // 獲取方法的名稱滚躯,返回的是一個(gè)SEL類型
method_getImplementation(method) // 獲取方法的函數(shù)指針
sel_getName(_sel) // 將SEL類型轉(zhuǎn)化為C字符串
method_getTypeEncoding(method) // 獲取方法的參數(shù)雏门、返回值類型,例如:“v16@0:8”
method_copyReturnType(method) // 獲取方法的返回值類型掸掏,例如:“v”
method_getNumberOfArguments(method) // 獲取方法參數(shù)的個(gè)數(shù)
method_copyArgumentType(method, i) // 獲取方法指定參數(shù)的類型茁影, 例如“@”

objc_property、類型編碼

objc_property_t本身是一個(gè)objc_property類型的結(jié)構(gòu)體指針即屬性的具體表示丧凤,我承認(rèn)我沒(méi)有找到objc_property的定義募闲,但是找到一個(gè)objc_property_attribute_t結(jié)構(gòu)體,它是屬性的attribute愿待,也就是其實(shí)是對(duì)屬性的詳細(xì)描述浩螺,包括屬性名稱、屬性編碼類型仍侥、原子類型/非原子類型等要出,可以參考類型編碼的文章。它的定義如下:


/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

// 通過(guò)property_copyAttributeList(property, &attrCount)函數(shù)可以獲取一個(gè)存有該結(jié)構(gòu)體的數(shù)組农渊。

該結(jié)構(gòu)體的name和value均為char型的指針患蹂,也就是一個(gè)C字符串,在YYModel中可以看到對(duì)于他們的使用是獲取name[0]砸紊、*name的值传于,也就是獲取C字符串的第一個(gè)字符。需要注意的是,當(dāng)name[0]為‘T’時(shí)批糟,證明此時(shí)的objc_property_attribute_t結(jié)構(gòu)體描述的是屬性對(duì)應(yīng)的數(shù)據(jù)類型格了,是BOOL還是NSString類型等。再比如name[0]為’N‘時(shí)表明該結(jié)構(gòu)體描述的是屬性的原子性徽鼎,即為nonatomic盛末。另外當(dāng)name[0]為’G‘弹惦、’S‘時(shí)分別表示該屬性自定義了getter、setter方法悄但。

// 獲取所有的屬性
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
property_getName(property)  // 獲取屬性的名稱
property_copyAttributeList(property, &attrCount) // 獲取屬性的詳細(xì)信息,返回一個(gè)數(shù)組

備注:所有通過(guò)C函數(shù)所獲取的數(shù)據(jù)棠隐,都應(yīng)該手動(dòng)調(diào)用free()函數(shù)釋放內(nèi)存

Ivar

Ivar本身是一個(gè)objc_ivar類型的結(jié)構(gòu)體指針即實(shí)例變量的具體表示,objc_ivar是一個(gè)結(jié)構(gòu)體檐嚣, 其定義如下:

struct objc_ivar {
    char * _Nullable ivar_name                               變量名;
    char * _Nullable ivar_type                               變量類型;
    int ivar_offset                                          基地址偏移字節(jié);
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
} 
ivar_getName(ivar) // 獲取實(shí)例變量名稱
ivar_getOffset(ivar) // 獲取基地址偏移字節(jié)
ivar_getTypeEncoding(ivar) // 獲取變量類型

NSScanner

NSScanner是一個(gè)類助泽,用于在字符串中掃描指定的字符,尤其是把它們翻譯/轉(zhuǎn)換為數(shù)字和別的字符串嚎京∥撕兀可以在創(chuàng)建NSScaner時(shí)指定它的string屬性,然后scanner會(huì)按照你的要求從頭到尾地掃描這個(gè)字符串的每個(gè)字符鞍帝。
對(duì)于查詢字符串而言诫睬,這真是一個(gè)強(qiáng)大的類,下面簡(jiǎn)單學(xué)習(xí)下YYModel是如何利用該類進(jìn)行類型編碼解析的:

// 該類用于解析屬性的數(shù)據(jù)類型帕涌,YYModel考慮得很全面摄凡,連遵循協(xié)議的id類型都有考慮

// 如果解析到是一個(gè)NSObject對(duì)象則進(jìn)行如下詳細(xì)的解析,例如:NSString類型時(shí)_typeEncoding為@"NSString"
            if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
            
                        // 用需要掃描的_typeEncoding字符串蚓曼,初始化scanner對(duì)象
                        NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
                        // 我們的目的是拿到NSString亲澡,所以需要更改scanner掃描的起始位置為字符N所在的位置
                        if (![scanner scanString:@"@\"" intoString:NULL]) continue;
                        
                        NSString *clsName = nil;
                        //  開(kāi)始掃描_typeEncoding直到遇到字符”或者<,遇到了則返回YES纫版,并將掃描到的字符串存入clsName床绪。
                        //  需要注意的是NSCharacterSet其實(shí)設(shè)置的是停止掃描的字符,如果該字符出現(xiàn)在NSScanner掃描的起始位置捎琐,該方法會(huì)返回NO会涎,clsName也會(huì)為null
                        if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
                            if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
                        }
                        
                        
                        // 當(dāng)上面返回NO,進(jìn)行協(xié)議的捕獲
                        NSMutableArray *protocols = nil;
                        while ([scanner scanString:@"<" intoString:NULL]) {
                            NSString* protocol = nil;
                            if ([scanner scanUpToString:@">" intoString: &protocol]) {
                                if (protocol.length) {
                                    if (!protocols) protocols = [NSMutableArray new];
                                    [protocols addObject:protocol];
                                }
                            }
                            [scanner scanString:@">" intoString:NULL];
                        }
                        _protocols = protocols;
                    }

objc_msgSend

YYModel中設(shè)置和獲取屬性的值均經(jīng)過(guò)objc_msgSend函數(shù)實(shí)現(xiàn)瑞凑,我們要知道末秃,所有OC的方法的調(diào)用(消息的接收)在編譯之后均是通過(guò)調(diào)用objc_msgSend函數(shù)完成的。關(guān)于objc_msgSend的使用可參考鏈接籽御。要想驗(yàn)證編譯后的OC代碼练慕,可以在終端下使用Clang命令進(jìn)行驗(yàn)證。具體可以參考該文章.

實(shí)現(xiàn)解析

重要的類

  • _YYModelMeta —————— 完成Model屬性技掏、方法铃将、實(shí)例變量的解析,并生成與數(shù)據(jù)源相對(duì)應(yīng)的字典映射哑梳;
  • YYClassInfo —————— 存儲(chǔ)Model解析后的各種信息劲阎;
  • YYClassMethodInfo —————— 存儲(chǔ)方法的信息;
  • YYClassIvarInfo —————— 存儲(chǔ)實(shí)例變量的信息鸠真;
  • YYClassPropertyInfo —————— 存儲(chǔ)屬性的信息悯仙;
  • _YYModelPropertyMeta —————— 該類包含了屬性的信息和設(shè)置屬性時(shí)所需要的信息龄毡,有上述解析得到的信息和自定義的信息合成,用于第二步中的映射關(guān)系生成锡垄。

實(shí)現(xiàn)步驟

1沦零、解析實(shí)體信息

自定義的Model類調(diào)用modelWithDictionary:modelWithJSON:進(jìn)行Model的初始化货岭,接下來(lái)_YYModelMeta類將會(huì)為我們完成解析的工作路操。解析的目的是獲取Model類的方法、屬性千贯、實(shí)例變量信息屯仗,這些信息將保存在YYClassInfo中。

在解析之初會(huì)首先檢查是否存在緩存搔谴,如果有緩存則直接返回緩存的_YYModelMeta對(duì)象祭钉。通常情況下,每一個(gè)類的屬性己沛、實(shí)例變量的解析只會(huì)進(jìn)行一次,成功解析一次后的數(shù)據(jù)將會(huì)被緩存起來(lái)距境,只有當(dāng)設(shè)置了YYClassInfo_needUpdate才會(huì)進(jìn)行新的解析申尼,也就是進(jìn)行動(dòng)態(tài)的添加屬性、修改方法后需要更新垫桂。緩存數(shù)據(jù)保存在一個(gè)靜態(tài)的CFMutableDictionaryRef字典當(dāng)中师幕,并通過(guò)dispatch_semaphore_t信號(hào)量來(lái)確保字典讀取的線程安全性。

沒(méi)有緩存诬滩,則創(chuàng)建_YYModelMetaYYClassInfo對(duì)象霹粥,其中YYClassInfo也加入了緩存處理,個(gè)人覺(jué)得這里是沒(méi)有必要的疼鸟,因?yàn)?code>_YYModelMeta已經(jīng)有屬性引用了YYClassInfo后控。

YYClassInfo_update會(huì)為我們完成具體的解析,該方法依次對(duì)方法空镜、屬性浩淘、實(shí)例變量進(jìn)行了解析,個(gè)人發(fā)現(xiàn)方法吴攒、實(shí)例變量解析的信息并未用到张抄。所以這里著重說(shuō)一下屬性的解析,通過(guò)class_copyPropertyList獲取到屬性列表洼怔,并為每一個(gè)屬性生成一個(gè)YYClassPropertyInfo對(duì)象署惯,該對(duì)象會(huì)保存在YYClassInfo_propertyInfos數(shù)組中。生成的YYClassPropertyInfo對(duì)象主要包含了屬性名镣隶、setter极谊、getter诡右、屬性數(shù)據(jù)類型等,這些信息都有對(duì)應(yīng)的runtime方法怀酷。其中核心在于利用類型編碼完成屬性數(shù)據(jù)類型的解析稻爬。

解析屬性的核心代碼:

/// 1.獲取Model類的屬性列表
 unsigned int propertyCount = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
    if (properties) {
        NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
        // 將生成的YYClassPropertyInfo對(duì)象保存在該數(shù)組中
        _propertyInfos = propertyInfos;
        for (unsigned int i = 0; i < propertyCount; i++) {
            YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
            if (info.name) propertyInfos[info.name] = info;
        }
        // 記得釋放內(nèi)存
        free(properties); 
    }

/// 2.初始化YYClassPropertyInfo對(duì)象
- (instancetype)initWithProperty:(objc_property_t)property {
    if (!property) return nil;
    self = [super init];
    _property = property;
    
    // 獲取屬性的名稱
    const char *name = property_getName(property);
    if (name) {
         // 將屬性名稱有C字符串轉(zhuǎn)化為NSString
        _name = [NSString stringWithUTF8String:name];
    }
    
    YYEncodingType type = 0;
    unsigned int attrCount;
    // 獲取屬性對(duì)應(yīng)的所有描述,即objc_property_attribute_t的結(jié)構(gòu)體數(shù)組蜕依,包括原子性桅锄、數(shù)據(jù)類型、內(nèi)存語(yǔ)義等
    objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
    for (unsigned int i = 0; i < attrCount; i++) {
        // objc_property_attribute_t的name為C字符串样眠,name[0]表示獲取第一個(gè)字符
        switch (attrs[i].name[0]) {
            case 'T': { // 這里將解析數(shù)據(jù)的類型友瘤,比如NSString、BOOL檐束、NSArray等
                if (attrs[i].value) {
                    _typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
                    // 剛方法用于解析具體的數(shù)據(jù)類型
                    type = YYEncodingGetType(attrs[i].value);
                    
                    // 只有當(dāng)數(shù)據(jù)類型為NSObject類辫秧,包括采納了協(xié)議的屬性,才需要如下進(jìn)一步的解析
                    if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
                        NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
                        if (![scanner scanString:@"@\"" intoString:NULL]) continue;
                        
                        NSString *clsName = nil;
                        if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
                             // 將解析到的數(shù)據(jù)類型的類保存在_cls屬性中
                            if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
                        }
                        
                        // 對(duì)于采納協(xié)議的屬性的解析
                        NSMutableArray *protocols = nil;
                        while ([scanner scanString:@"<" intoString:NULL]) {
                            NSString* protocol = nil;
                            if ([scanner scanUpToString:@">" intoString: &protocol]) {
                                if (protocol.length) {
                                    if (!protocols) protocols = [NSMutableArray new];
                                    [protocols addObject:protocol];
                                }
                            }
                            [scanner scanString:@">" intoString:NULL];
                        }
                        _protocols = protocols;
                    }
                }
            } break;
            case 'V': { // Instance variable
                if (attrs[i].value) {
                    _ivarName = [NSString stringWithUTF8String:attrs[i].value];
                }
            } break;
            case 'R': {
                type |= YYEncodingTypePropertyReadonly;
            } break;
            case 'C': {
                type |= YYEncodingTypePropertyCopy;
            } break;
            case '&': {
                type |= YYEncodingTypePropertyRetain;
            } break;
            case 'N': {
                type |= YYEncodingTypePropertyNonatomic;
            } break;
            case 'D': {
                type |= YYEncodingTypePropertyDynamic;
            } break;
            case 'W': {
                type |= YYEncodingTypePropertyWeak;
            } break;
            case 'G': { // 獲取自定義的getter方法
                type |= YYEncodingTypePropertyCustomGetter;
                if (attrs[i].value) {
                    _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
                }
            } break;
            case 'S': { // 獲取自定義的setter方法
                type |= YYEncodingTypePropertyCustomSetter;
                if (attrs[i].value) {
                    _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
                }
            } // break; commented for code coverage in next line
            default: break;
        }
    }
    if (attrs) {
        free(attrs);
        attrs = NULL;
    }
    
    // 保存獲取的type以及驗(yàn)證_setter被丧、_getter是否已經(jīng)獲取到盟戏,這三個(gè)要素在設(shè)置屬性值時(shí)將會(huì)被用到
    _type = type;
    if (_name.length) {
        if (!_getter) {
            _getter = NSSelectorFromString(_name);
        }
        if (!_setter) {
            _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
        }
    }
    return self;
}

2、生成映射關(guān)系

通過(guò)生成的YYClassInfo來(lái)建立與數(shù)據(jù)源之間的映射關(guān)系甥桂,也就是生成了一個(gè)以數(shù)據(jù)源字典的key為key柿究,以_YYModelPropertyMeta對(duì)象為值的字典_mapper,核心類為_YYModelPropertyMeta黄选,該類包含了屬性的信息和用戶自定義的信息蝇摸,例如屬性的setter、getter办陷、對(duì)應(yīng)數(shù)據(jù)源的key貌夕、子類容器等。

在生成_mapper的過(guò)程中包括白名單民镜、黑名單啡专、容器類、自定義數(shù)據(jù)源的key與屬性名映射關(guān)系的處理殃恒,具體可參見(jiàn)_YYModelMeta類的initWithClass:方法植旧。

3、設(shè)置屬性值

上述兩個(gè)步驟均是在_YYModelMeta中完成的离唐,第三部回到NSObject+YYModel分類中病附,用modelSetWithDictionary:方法完成屬性值的設(shè)置。

核心代碼:

/// 使用下面的函數(shù)亥鬓,遍歷字典并通過(guò)ModelSetWithDictionaryFunction函數(shù)完成Model屬性值的設(shè)置
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);

/// context為ModelSetContext結(jié)構(gòu)體
typedef struct {
    void *modelMeta;  ///  為步驟1完沪、2完成后生成的_YYModelMeta對(duì)象
    void *model;      ///  要生成的實(shí)體類,相當(dāng)于id類型
    void *dictionary; ///  數(shù)據(jù)源
} ModelSetContext;

/// 屬性值的設(shè)置最終通過(guò)objc_msgSend函數(shù)實(shí)現(xiàn),例如下列對(duì)NSString類型的屬性進(jìn)行設(shè)置
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);
                        }
                    }

問(wèn)題

  1. 為何要做metaclass的處理和判斷覆积?
  2. 獲取方法列表和實(shí)例變量列表的意義在哪听皿?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宽档,隨后出現(xiàn)的幾起案子尉姨,更是在濱河造成了極大的恐慌,老刑警劉巖吗冤,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件又厉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡椎瘟,警方通過(guò)查閱死者的電腦和手機(jī)覆致,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肺蔚,“玉大人煌妈,你說(shuō)我怎么就攤上這事⌒颍” “怎么了璧诵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)仇冯。 經(jīng)常有香客問(wèn)我腮猖,道長(zhǎng),這世上最難降的妖魔是什么赞枕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮坪创,結(jié)果婚禮上炕婶,老公的妹妹穿的比我還像新娘。我一直安慰自己莱预,他們只是感情好柠掂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著依沮,像睡著了一般涯贞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上危喉,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天宋渔,我揣著相機(jī)與錄音,去河邊找鬼辜限。 笑死皇拣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播氧急,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼颗胡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了吩坝?” 一聲冷哼從身側(cè)響起毒姨,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钉寝,沒(méi)想到半個(gè)月后弧呐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瘩蚪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年泉懦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疹瘦。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡崩哩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出言沐,到底是詐尸還是另有隱情邓嘹,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布险胰,位于F島的核電站汹押,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏起便。R本人自食惡果不足惜棚贾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望榆综。 院中可真熱鬧妙痹,春花似錦、人聲如沸鼻疮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)判沟。三九已至耿芹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挪哄,已是汗流浹背吧秕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留迹炼,地道東北人寇甸。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拿霉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吟秩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354