iOS-穿針引線 YYModel全英文注釋解析翻譯+超詳細解析!敢問還有誰比我更詳細!!還有誰!!!

我的英文能力不夠,所以我把作者提供的英文注釋解析通過字典百度自己的理解等方法翻譯后寫下來,再對照自己的翻譯與各方查詢的資料來分析。若覺得不錯,可否給顆star?你的支持將是我的動力。


點我進入Git下載


//mapped to 映射//列子 instancetype 通常也指實例

只有解析,注釋請下載后觀看(麻煩下載前順手star一下,你的支持將是我的動力!),建議最好對比源碼觀看。

由于內容過多,先只說下通過JSON創建并返回一個實例的解析

+(instancetype)ad_modelWithJSON:(id)json:

在其內部其實通過類方法[self _ad_dictionaryWithJSON:json]創建了一個字典,而后通過類方法[self ad_modelWithDictionary:dic]創建并返回所需的實例

+(NSDictionary *)_ad_dictionaryWithJSON:(id)json?

類方法,自定義的實現方法并沒有相應的方法聲明,接收到Json文件后先判斷Json文件是否為空,判斷有兩種方式 if (!json || json == (id)kCFNull) ?

kCFNull: NSNull的單例,也就是空的意思那為什么不用Null、Nil或nil呢?

以下為nil,Nil,Null,NSNull的區別

Nil:對類進行賦空值

ni:對對象進行賦空值

Null:對C指針進行賦空操作,如字符串數組的首地址 char *name = NULL

NSNull:對組合值,如NSArray,Json而言,其內部有值,但值為空

所以判斷條件json不存在或json存在,但是其內部值為空,就直接返回nil。若json存在且其內部有值,則創建一個空字典(dic)與空NSData(jsonData)值而后再判斷,若json是NSDictionary類,就直接賦值給字典。

若是NSString類,就將其強制轉化為NSString,而后用UTF-8編碼處理賦值給jsonData。

若是NSData,就直接賦值給jsonData而后判斷,而jsonData存在就代表json值轉化為二進制NSData。

用官方提供的JSON解析就可獲取到所需的值賦值為dic。若發現解析后取到得值不是NSDictionary,就代表值不能為dict,因為不是同一類型值,就讓dict為nil最后返回dict。

在這個方法里相當于若JSON文件為NSDictionary類型或可解析成dict的NSData、NSString類型就賦值給dict返回,若不能則返回的dict為nil

+(instancetype)ad_modelWithDictionary:(NSDictionary *)dictionary?

類方法,方法內部會先判斷若字典不存在,或字典存在但值為空的情況下直接返回nil而后在判斷若傳過來的字典其實不是NSDictionary類型,則也返回nil。所以,到了下一步時就代表字典存在且值不為空,而且傳過來的字典就是NSDictionary類型,則創建一個類Cls為當前調用方法的類,而后通過自定義方法[_ADModelMeta metaWithClass:Cls]將自身類傳過去。

_ADModelMeta為延展類實現方法里聲明的類,metaWithClass:Cls方法為

+(instancetype)metaWithClass:(Class)cls

類方法,方法返回這個class元素,model緩存在方法內部,會先判斷若cls不存在則返回nil而若存在,則創建三個靜態屬性:CFMutableDictionaryRef cache(static,靜態修飾符,被static修飾的屬性在程序結束前不會被銷毀。CFNSMutableDictionaryRef,可變字符串底層,效率比其高).

dispatch_semaphore_t? lock(同步信號量,一次執行里面讓其值為1則wait時就會通過繼續執行),dispatch_once_t oncetoken(一次執行,配合dispatch_once使用,整個程序只會執行一次)一次執行中創建CFMutableDictionaryRef與dispatch_semaphore_create(1)

其中CFMutableDictionary的創建中四個參數意義為: cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

?@功能 CFDictionaryCreateMutable創建一個新的詞典。

?@參數 CFAllocator 分配器應該用于分配CFAllocator字典的內存及其值的存儲。這 參數可能為空,在這種情況下,當前的默認值CFAllocator使用。如果這個引用不是一個有效的cfallocator,其行為是未定義的。這里是內存及其值的存儲為默認。

?@參數 capacity 暗示值得個數,通過0實現可能忽略這個提示,或者可以使用它來優化各種。這里是忽略。

?@參數 keyCallBacks 指向CFDictionaryKeyCallBacks結構為這本字典使用回調函數初始化在字典中的每一個鍵,初始化規則太多而且看的有點迷糊就不多說了,畢竟不敢亂說。意思應該是對鍵值對中的鍵進行初始化,并有相應的優化方式。?

?@參數 valueCallBacks 指向CFDictionaryValueCallBacks結構為這本詞典使用回調函數初始化字典中的每一個值。對鍵值對中的值進行初始化,并有相應地優化方式。

一次執行之后通過wait判斷可否繼續執行,由于Create值為1所以可通行一次,此時在CF字典cache中獲得值不可變的函數指針cls對應的值_ADModelMeta *meta(const 值不可被更改,void * 函數指針,通過__bridge橋接轉換)而后通過signal操作讓lock鎖的信號加1,再去執行判斷語句。

判斷內容為若meta不存在,或meta存在但其屬性classInfo(ADClassInfo *類型)的needUpdate為YES代表meta需要更新或存儲,則通過_ADModelMeta對象方法(instancetype)initWithClass:(Class)cls 獲取一個實例。

-(instancetype)initWithClass:(Class)cls?

方法內,會先通過[ADClassInfo classInfoWithClass:(Class)cls]獲取到cls的信息而在[ADClassInfo classInfoWithClass:(Class)cls]方法內部,又會先去判斷Cls是否存在,若不存在返回nil若存在,則創建兩個CFMutableDictionaryRef(metaCache與classCache)與一個一次執行,一個同步信號量。

而后通過一次執行初始化兩個CFMutableDictionaryRef與同步信號量的初始化創建完畢后會先判斷同步信號量,由于初始化有值則減一后繼續執行此時創建一個ADClassInfo *info用于保存通過CFDictionaryGetValue獲取到的Class信息。

其中class_isMetaClass判斷指定類是否是一個元類

元類是類對象的類,如[NSArray array]中的NSArray也是一個對象,NSArray通過元類生成,而元類又是一個對象,元類的類是根源類NSObject,NSObject父類為Nil

如果是元類則存儲在metaCache中,所以也是從其中取值,如果不是就存儲在classCache中也就是從classCache中取值,通過一個不可變的函數指針cls取值而后同步信號量加1 若取得到信息info則直接返回,若取不到就借助[[ADClassInfo alloc] initWithClass:cls];去獲取Info。

[[ADClassInfo alloc] initWithClass:cls] 私有方法,未在.h文件中聲明在此方法中會先判斷傳過來的cls是否存在,若不存在返回nil若存在,就先通過父類初始化一次并保存傳過來的_cls = Cls,與Cls的父類(_superCls = class_getSuperclass(cls)獲取父類)以及Cls是否為元類的Bool值( _isMeta =? class_isMetaClass(cls)),若不是元類則獲得其元類并保存(_metaCls = objc_getMetaClass(class_getName(cls))),以及類的名字(_name = NSStringFromClass(cls))而后調用一次_update方法。

在update方法里會先對ivarInfos(ivars)methodInfos(methods) propertyInfos(properties)三個屬性初始化,ivars屬性,methods方法,properties屬性操作并臨時保存self.cls(之前剛賦值)。

聲明一個 保存 方法描述的數組指針 長度值 methodCount。 Method *methods = class_copyMethodList(cls, &methodCount);返回關于對象方法描述的一個數組指針,數組長度保存在methodCount地址里。

如果指針存在,就創建一個NSMutableArray字典存儲方法,并保存此字典_methodInfos = methodInfos; 而后再通過自定義方法[[ADClassMethodInfo alloc] initWithMethod:methods[i]];通過傳過來的方法返回一個實例-(instancetype)initWithMethod:(Method)method通過傳過來的方法返回一個實例。

在此方法里會先判斷方法是否存在,若不存在返回nil,若存在則保存方法method,與方法操作SEL(method_getName(method)),方法實現IMP(method_getImplementation(method)),方法操作的名字( const char *name =sel_getName(_sel),返回值是一個值不可變的字符指針name,字符指針通常指向一個字符數組的首地址,字符數組類似于字符串) 。

若取得到名字則將方法名用UTF-8編碼后保存。方法參數和返回值類型(method_getTypeEncoding(method))若取得到通過UTF-8編碼后保存。方法的返回值類型(method_copyReturnType(method))若取得到則通過UTF-8編碼后保存。

方法的參數個數(method_getNumberOfArguments(method))若個數大于0,則創建一個可變數組,而后去遍歷獲取參數類型(method_copyArgumentType(method, i)獲取方法的指定位置參數的類型字符串),若獲取到的參數類型存在就通過UTF-8編碼后獲取,不存在就為nil,而后保存到可變數組里,若有值則存儲,沒有值用@“”代替,存儲后若獲取到的參數類型存在就free釋放其內存,全部獲取后保存此數組。

最后將保存好信息的self返回此時回到_update方法里,已經通過對象獲得到一個方法的描述信息,判斷方法名(name,方法操作的名字)是否有值,如有值則以此為key,對象為值存儲在可變字典methodInfos里,而methodInfos之前已經被保存。沒有自然就不存了。

全部存儲完畢后釋放methods內存(之前獲取的關于對象方法描述的一個數組指針)。獲取到數組后,去獲取cls的屬性。同理,先通過class_copyPropertyList獲取到屬性個數存儲在一個unsigned int(無符號整型)值中,再獲取屬性描述properties,如果有屬性則屬性描述有值。

此時創建并保存一個可變字典,而后遍歷屬性,并將遍歷時的屬性通過[[ADClassPropertyInfo alloc] initWithProperty:properties[i]];封裝成一個ADClassPropertyInfo對象,以屬性名為Key屬性,對象為值存儲在字典里存儲完后釋放屬性描述內存。

-(instancetype)initWithProperty:(objc_property_t)property

方法中,依舊是先判斷后保存,保存了傳過來的屬性,屬性的名稱,屬性的特性列表指針objc_property_attribute_t *(通常代表其是一個數組,一個結構體包含一個name屬性的描述,一個value屬性值),而后遍歷此指針指向的數組開始一個一個取值保存在新建的objc_property_attribute_t中。

若取出來的name皆是以T開頭后面跟隨屬性類型如字典,屬性是強弱指針(如&為強指針,空為Value)原子性非原子性(空為atomic,N為nonatomic),變量名稱,使用UTF-8編碼后保存。

在使用自定義C語言函數結構體objc_property_attribute_t中的name與Value:

結構體中的name與Value:

屬性類型? name值:T? value:變化

編碼類型? name值:C(copy) &(strong) W(weak) 空(assign) 等 value:無

非/原子性 name值:空(atomic) N(Nonatomic)? value:無

變量名稱? name值:V? value:變化

屬性描述為 T@"NSString",&,V_str 的 str

屬性的描述:T 值:@"NSString"

屬性的描述:& 值:

屬性的描述:V 值:_str2


ADEncodingGetType(返回值為ADEncodingType,接收參數為不可變的字符數組typeEncoding)轉換value值。

在ADEncodingGetType中,先用可變字符數組type保存傳過來的Value值,若值不存在則返回自定義枚舉ADEncodingTypeUnknown,若有值則獲取type長度,若長度為0仍舊返回Unknown,若長度不為0則聲明一個枚舉值qualifier,在設置一個值為true的Bool值用于死循環,在死循環內部從字符數組type的首地址開始一個一個取出內部的字符,若滿足要求則讓qualifier進行位運算(1 | 0 = 1, 1 | 1 = 0)而后指針+1指向下一個值,當值不滿足任何條件跳出while循環。

再獲取剩余字符數組長度,若長度為0則或運算Unknow,若不是則判斷此時的內部為哪種類型與qualifier進行或運算后返回,若為@類型即為block或Object類型,長度為2以?結尾則是Block否則就是Object,如果類型不可知則返回qualifier與Unknow或運算值。

返回值之前的switch選擇中,此時我們得到了value的類型與修飾符類型(如不可變的int16類型),再去判斷若Value有值,其類型又是Object類型,則使用NSScanner(條件判斷)獲取字符串通過scanString:intoString:判斷值是否包含NULL

@\”中 \”轉義符,轉義成“ 所以值為@“,從@“開始遍歷若找到NULL則返回YES

不包含結束此次循環。若包含則創建一個空字符串clsName,而后通過NSCharacterSet(一組Unicode字符,常用與NSScanner,NSString處理)轉換字符串@“\“<”再通轉換后的NSCharterSet掃描scanner中是否包含轉換后的對象,若包含則將傳入的字符串指向的遇到charterSet字符之前內容的指針保存到&clsName地址中。

此時的&clsName代表字符串頭地址的地址,存儲后若地址中有值,則將值通過UTF-8編碼處理后,通過objc_getClass獲取到其isa保存(objc_getClass來獲取對象的isa,isa是一個指針指向對象自身)而后再聲明一個空的可變數組,用于獲取字符串中從“<”到“ >”中的內容,保存在新建的字符串protocol中,若有內容且之前的空數組沒有值(不明白為何多這一步)則添加字符串給可變數組,至此,屬性名為T的操作就結束了。

若name屬性名為V代表是值名稱(如字符串Str,V的值Str),直接使用UTF-8編碼后保存R代表屬性為只讀屬性,則讓type或運算ADEncodingTypePropertyCopyC為Copy,&為Reatin(引用計數加一,形同強指針)

N為nonatomic,D為Dynamic(@dynamic ,告訴編譯器不自動生成屬性的getter、setter方法)

W為weak,G為getter方法,S為setter方法

獲取到屬性后釋放屬性的特性列表,保存已獲取的type值,若屬性名稱存在而屬性的getter、setter方法沒有獲取就通過屬性名稱獲取,至此屬性列表獲取完畢。

接下來就是獲取成員變量了(成員變量與屬性的操作區別在于:成員變量{}中聲明的, 屬性@property聲明的),這個比較簡單。

先獲取成員變量列表、個數,若列表有值則聲明一個可變字典并保存,而后通過自定義方法[[ADClassIvarInfo alloc] initWithIvar:ivars[i]];獲取成員變量。

-(instancetype)initWithIvar:(Ivar)ivar

方法里,依舊先判斷傳過來的是否為空,而后保存傳過來的ivar,獲取成員變量名(ivar_getName(ivar)),獲取得到通過UTF-8編碼后保存,在獲取成員變量首地址的偏移量(ivar_getOffset(ivar)),runtime會計算ivar的地址偏移來找ivar的最終地址,獲取成員變量類型編碼(ivar_getTypeEncoding(ivar)),若獲取得到通過UTF-8編碼后保存,而后再通過自定義C函數ADEncodingGetType獲取type值(之前已經描述)。

至此,方法method,屬性property,成員變量ivar全部獲取。

獲取后判斷若不存在就賦空值,將_needUpdate賦值為NO回到-(instancetype)initWithClass:(Class)cls方法中,更新結束后通過自定義方法[self.class classInfoWithClass:_superCls]獲取父類信息并保存,而后返回self此時回到classInfoWithClass,此時已經獲得Cls的info信息。

若獲得到info信息就執行一次同步信號燈wait操作,而后通過info.isMeta判斷Cls是否為元類來存儲Cls的info,元類存儲在metaCache中,非元類存儲在classCache中,存儲后讓線程同步信號燈加1返回info。

為什么加1?因為下次進入方法后要通過wait,然后才能通過靜態可變CF字典取值。

此時回到initWithClass,獲取到Cls的信息,如果信息不存在返回nil,存在就先通過父類初始化一次,而后判斷并獲取白名單,黑名單里的的屬性。再定義一個可變字典genericMapper用于獲取自定義Class中包含的集合屬性。

先讓Cls執行方法modelContainerPropertyGenericClass(若方法存在執行, 描述:? ? 如果這個property是一個對象容器,列如NSArray/NSSet/NSDictionary? 實現這個方法并返回一個屬性->類mapper,告知哪一個對象將被添加到這個array /set /)獲取返回回來的字典(如:@"shadows" : [ADShadow class],由用戶書寫)

若字典有值則再聲明一個可變字典tmp,而后通過Block遍歷字典genericMapper,若字典Key不是字符串類型返回,若是字典則獲取Key對應值的類,獲取不到則返回,獲取到后判斷是否是元類,如果是元類則tmp字典以genericMapper的key為自身key,以genericMapper的value為自身value,如果不是元類而是字符串類,則以此字符串創建一個類(NSClassFromString(obj))。

若創建成功則以以genericMapper的key為自身key,以創建的類為自身value,遍歷完后保存genericMapper為tmp。而后創建所有的porperty元素,先取得classInfo,如果classInfo存在并且父類不為空(預先解析父類,但忽視根類(NSObject/NSProxy))。

而后遍歷curClassInfo.propertyInfos.allValues(curClassInfo的字典屬性propertyInfos的所有值)保存在ADClassPropertyInfo * propertyInfo中,若propertyInfo.name不存在 或黑名單存在且黑名單包含propertyInfo.name? 或白名單存在且白名單不包含propertyInfo.name? 則結束當前循環。

隨后通過自定義方法[_ADModelPropertyMeta metaWithClassInfo:classInfo propertyInfo:propertyInfo generic:genericMapper[propertyInfo.name]]獲取model對象中的property信息。

+ (instancetype)metaWithClassInfo:(ADClassInfo *)classInfo propertyInfo:(ADClassPropertyInfo *)propertyInfo generic:(Class)generic?

方法下次在描述,感覺本文已經寫得實在有點多,總之就是通過此獲取到model對象中的property信息,包括getter、setter方法,可否使用KVC,可否歸檔解檔,以及Key、keyPath、keyArray映射等。

回到initWithClass方法,若獲取到model對象中的property信息meta不存在,或meta->_name(成員變量不支持點語法)不存在,或meta->_getter、meta->_setter不存在或已保存在字典allPropertyMetas中就結束當前循環,若都不滿足則保存在字典allPropertyMetas中。遍歷結束后保存curClassInfo為其父類,而后再while循環直至根類while結束后若allPropertyMetas有值則保存,而后創建一個可變字典,兩個可變數組。通過modelCustomPropertyMapper方法實現定制元素

+ (nullable NSDictionary*)modelCustomPropertyMapper;

定制屬性元素,描述 如果JSON/Dictionary的key并不能匹配model的property name,實現這個方法并返回額外的元素。

先去判斷能否響應這個方法,如果能,就創建一個字典,聲明是NSDictionary,但是實現時是自定義屬性映射字典,相當于響應了這個方法返回值賦值給字典customMapper,而后Block遍歷次字典,通過之前存儲的allPropertyMetas字典取出對應Key的值賦值給propertyMeta,若取不出則直接return,若取得出則allPropertyMetas刪除Key與Key對應的值

而后判斷customMapper中的Value是否屬于NSString類型,若屬于但長度為0則return,否則保存在propertyMeta中,隨后以"."分割字符串為一個數組keyPath ,而后遍歷數組,若遍歷時的字符串長度為0,則創建一個臨時數組保存keyPath移除@“”對象而后再賦值給KeyPath。遍歷結束后,若此時KeyPath的Count大于1,則保存在propertyMeta中,并將propertyMeta保存在之前新建的可變數組keyPathPropertyMetas中,隨后判斷mapper[mappedToKey](mapper,之前創建的用于映射的可變字典)是否有值,沒有則賦值空,有就賦值給propertyMeta的_next,而后保存propertyMeta。

若值是NSArray類型,則將其強轉成NSArray類型而后字符串oneKey遍歷,遍歷前創建一個可變數組mappedToKeyArray,如果遍歷時oneKey不是字符串或長度為0則結束此次遍歷,如果都不符合則通過“。”分割成一個數組,若數組的數量大于1則保存數組否則保存oneKey,而后判斷若propertyMeta->_mappedToKey不存在則保存其為oneKey,如果數組數量大于1則保存_mappedToKeyPath為數組,若保存失敗則return,隨后保存mappedToKeyArray為_mappedToKeyArray等。

處理完定制屬性之后再去遍歷保存的所有Property元素的可變字典allPropertyMetas,保存字典的Value值的成員變量并保存Value值 為mapper[name],若保存成功則保存mapper,而后保存classInfo,所有property元素的個數,通過自定義強制內聯方法ADClassGetNSType保存Cls的類型,而后保存幾個用戶自定義方法的返回值,代碼中有詳細文檔翻譯。而后返回self,就此initWithClass方法結束。

此時返回到metaWithClass中,我們已經獲取了需要的meta,若獲取成功則線程信號量wait一次操作,并設置其值到緩存中,隨后信號量加1返回Meta.

此時返回到ad_modelWithDictionary方法中,我們已經獲取了modelMeta,若其_hasCustomClassFromDictionary值為YES就讓Cls執行一次+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;(通過字典創建的class,nil指使用當前class,用戶自定義的方法),而后通過Cls創鍵一個對象one,讓One執行方法-(BOOL)ad_modelSetWithDictionary:(NSDictionary *)dic ; 若執行成功返回one否則為nil

-(BOOL)ad_modelSetWithDictionary:(NSDictionary *)dic ;?

這個方法也是下次在解說吧

至此Json轉Model分析完畢。撒花~~~~~~~~


點我進入Git下載?真的,順手給顆star吧,你的支持將是我堅持的動力。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容