SDWebImage源碼閱讀筆記(一)

在做iOS開發(fā)中加載圖片是經(jīng)常性工作,一種是使用UIImage加載本地圖片皇钞,使用[UIImage imageNamed:@""]酱畅,[UIImage imageWithContentsOfFile:@""]等方法,各有側(cè)重優(yōu)劣孕索,不是本篇重點(diǎn)不必贅述夯巷。另一種實(shí)時(shí)從網(wǎng)絡(luò)加載赛惩,其中一種方法是從服務(wù)端獲取圖片的二進(jìn)制數(shù)據(jù),客戶端將其轉(zhuǎn)化為NSData *類型趁餐,再通過UIImage加載喷兼,這種方式適合小批量的圖片加載,安全性好實(shí)用簡便后雷,另一種方法就是服務(wù)端先提供一個(gè)圖片的URL季惯,客戶端再通過URL加載圖片,這個(gè)適合大批量獲取圖片的場景臀突,在iOS工程中也是廣泛實(shí)用的場景勉抓,也是本篇討論的重點(diǎn)。

如果不使用第三方框架候学,最簡單的方法便是先調(diào)用[NSData dataWithContentsOfURL:url]藕筋,再使用[UIImage imageWithData:data]加載圖片,通常這個(gè)過程可以使用GCD等異步加載方式防止阻塞UI主線程梳码。但通常情況下為了使用方便和提高性能隐圾,通常要使用一些封裝的框架,這其中就有大名鼎鼎的SDWebImage掰茶,這也是本篇文章要介紹的暇藏。

首先,還是來大概瀏覽一下其結(jié)構(gòu):

大概可以看出大概分為loader下載器濒蒋,cache管理盐碱,圖片加載等部分,下面沿襲之前風(fēng)格沪伙,還是通過一個(gè)在工程中的簡單調(diào)用來分析其工作原理瓮顽。

下面就從一個(gè)UIImageView加載一個(gè)圖片的URL開始:

以上就是一個(gè)一個(gè)在UITableViewcell中一個(gè)普通調(diào)用,可以看出這個(gè)框架主要使用了類別來實(shí)現(xiàn)焰坪。

下面進(jìn)入API查看:

發(fā)現(xiàn)其APIUIImageView+WebCache的類別中:

最終調(diào)用方法中參數(shù):url為圖片網(wǎng)絡(luò)地址,placeholder為占位圖聘惦,options為下載處理選擇項(xiàng)默認(rèn)為SDWebImageRetryFailed(也就是說加載失敗這個(gè)URL就會被拉入黑名單不會重復(fù)加載)某饰,operationKey為網(wǎng)絡(luò)操作標(biāo)識符儒恋,setImageBlockprogressBlockcompletedBlock這三個(gè)block會在不同時(shí)間調(diào)用黔漂,這些在后續(xù)分析中都會著重解說诫尽。

再繼續(xù)點(diǎn)進(jìn)去就到了UIView+WebCache這個(gè)類別:

這一步多了一個(gè)context參數(shù),這個(gè)會死一個(gè)字典類型炬守,主要管理一些dispatch_group_t牧嫉,后面會分析。

再繼續(xù)就開始脫去層層外衣見識真相了:

下面開始逐行代碼分析:

NSString *validOperationKey = operationKey ?:NSStringFromClass([selfclass]);如果上邊的參數(shù)operationKey為空的話减途,就創(chuàng)建這個(gè)key值酣藻,但是用來做什么呢?向下看:

[self sd_cancelImageLoadOperationWithKey:validOperationKey];從字面意思上看是要通過這個(gè)key值來撤銷一些操作鳍置×删纾可以進(jìn)去詳細(xì)看:

由上可以看出,UIView+WebCacheOperation這個(gè)類別維護(hù)了一個(gè)NSMapTable *類型的屬性税产,NSMapTable類似于字典吧怕轿,這個(gè)字典就是以operationKeykey值,遵守協(xié)議<SDWebImageOperation>的對象為value值辟拷,這行代碼就是通過key找到這個(gè)operation撞羽,將其撤銷,并從字典中刪除衫冻,書中代言operation就是用來做圖片依次IO的操作诀紊,后邊還會詳細(xì)介紹。

objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);這一步是將url保存為這個(gè)UIView+WebCache的一個(gè)關(guān)聯(lián)類羽杰,類似于屬性渡紫。

dispatch_group_t group = context[SDWebImageInternalSetImageGroupKey];這個(gè)是用來做一個(gè)任務(wù)隊(duì)列管理。

這一步是根據(jù)options值決定是否立即顯示占位圖考赛,

這一步就知道setImageBlock這個(gè)參數(shù)是用來做什么的惕澎,

這里可以看出這個(gè)block其實(shí)目的是用來將image給對應(yīng)的控件一般是UIImageView賦值的,如果為空就構(gòu)造一個(gè)finalSetImageBlock颜骤,最終在這一步

調(diào)用這個(gè)block給相應(yīng)圖片顯示控件賦值唧喉。

繼續(xù)向下走:

如果url存在,會通過urlIO圖片忍抽,如果不存在也會有個(gè)錯(cuò)誤處理八孝。

現(xiàn)在開始具體分析url存在下的圖片加載和緩存機(jī)制,這個(gè)是重點(diǎn):

self.sd_imageProgress.totalUnitCount = 0;

self.sd_imageProgress.completedUnitCount = 0;

這兩行從字面就可以看出是重置加載進(jìn)度鸠项。

到這一步干跛,SDWebImageManager浮出水面,SDWebImageManager也是一個(gè)很重要的部分祟绊,相當(dāng)于這個(gè)框架的一個(gè)管理類楼入。

進(jìn)去可以看到哥捕,這個(gè)類的全局單例對象負(fù)責(zé)管理緩存、加載嘉熊、失敗處理和運(yùn)行operation(上文有提到過)遥赚。

這一步可看出是對progressBlock的處理,從字面可看出是對加載進(jìn)度的一個(gè)處理阐肤,如果工程中需要對加載進(jìn)度進(jìn)行處理凫佛,可實(shí)現(xiàn)這個(gè)block

這一步就是具體的operation操作孕惜。也是即將需要大量筆墨分析的部分愧薛。

展開這部分代碼:

可看出在completedblock中是operation結(jié)束后的處理,也就是說在調(diào)用這個(gè)block時(shí)诊赊,image已經(jīng)完成加載和緩存了厚满,是最后一步,既然是最后一步碧磅,暫且不談押后處理碘箍。

下面先重點(diǎn)分析一下operation的構(gòu)造方法:

可以看到operation是遵守<SDWebImageOperation>協(xié)議的SDWebImageCombinedOperation類的對象,這個(gè)方法的調(diào)用者便是SDWebImageManager類的單例對象鲸郊。

這部分是對url的一個(gè)處理以及和operation的創(chuàng)建丰榴。

manager維護(hù)了一個(gè)failedURLs的數(shù)組,從字面可看出failedURLs是一個(gè)加載失敗的url的數(shù)組秆撮,這里會判斷四濒,如果url為空,或者符合花括號中的條件职辨,就會走這個(gè)方法[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];展開這個(gè)方法:

可以看出這個(gè)方法就是處理上文提到過的構(gòu)造方法中最后一個(gè)block參數(shù)的盗蟆,在這里調(diào),說明整個(gè)operation也就結(jié)束了舒裤,當(dāng)然是以加載失敗告終喳资。

這一步將operation加入到manager維護(hù)的runningOperations數(shù)組中,LOCK鎖是用信號量dispatch_semaphore_t實(shí)現(xiàn)的腾供。然后通過url得到key值仆邓,在

中,可以看出我們可以對url加自定義的過濾器來獲取key值伴鳖。

這一步是緩存一個(gè)SDImageCacheOptions的緩存加載查詢策略類型的cacheOptions节值。

緊接著是構(gòu)建一個(gè)緩存的operation,

可以看出SDWebImageCombinedOperation實(shí)際上是一個(gè)對operation的管理類榜聂,cacheOperation才是具體圖片IOoperation搞疗。

從構(gòu)造方法中,可看出done后邊的block應(yīng)該是最后一步執(zhí)行的block须肆,暫且不論匿乃,先進(jìn)去看cacheOperation的具體構(gòu)造方法脐往。

這個(gè)構(gòu)造方法是由manager維護(hù)的SDImageCache *類型的imageCache來完成的。imageCache是框架的緩存管理類扳埂。

先對key做判斷處理,若為空直接執(zhí)行doneBlock瘤礁。

首先從內(nèi)存緩存中獲取阳懂,如果內(nèi)存中存在且滿足花括號中的條件直接返回。

接著柜思,從硬盤緩存去獲取岩调。由于磁盤IO比較緩慢,所以提供了一個(gè)異步任務(wù)隊(duì)列赡盘,可以選擇異步磁盤獲取号枕,可見作者思慮之周詳。

展開queryDiskBlock:

先從磁盤中獲取到diskData數(shù)據(jù)陨享,若內(nèi)存中已經(jīng)獲取到image便賦值diskImage = image;否則再將diskData解碼后的數(shù)據(jù)賦值給diskImage葱淳,然后調(diào)用[self.memCache setObject:diskImage forKey:key cost:cost];將其存入內(nèi)存緩存,最后再執(zhí)行doneBlock抛姑。

由上邊方法可看出赞厕,從磁盤中獲取數(shù)據(jù)時(shí)解碼是一個(gè)很大的耗時(shí)耗性能操作,恰好SDWebImage能很巧妙的解決這個(gè)問題定硝,其解決方法就是先將其繪制成bitmap數(shù)據(jù)到畫布上皿桑,這些在進(jìn)行網(wǎng)絡(luò)獲取時(shí)還會用到,之后再詳細(xì)講解蔬啡,這也是SDWebImage很經(jīng)典的一部分诲侮。

現(xiàn)在就回來再看doneBlock中部分:

由于block會強(qiáng)持有對象,所以這里要對operationweak處理箱蟆。

如果operation被撤銷沟绪,則會被從runningOperations數(shù)組中安全移除。

根據(jù)加載策略顽腾,是否從緩存中取得圖片等條件確定是否從網(wǎng)絡(luò)下載近零。

如果需要不需要從網(wǎng)絡(luò)下載,也會回調(diào)執(zhí)行最后的completedblock抄肖,并從數(shù)組中安全移除operation久信。

如果需要從網(wǎng)絡(luò)加載,走下面的選擇支:

如果從緩存中加載到圖片漓摩,切加載策略為從網(wǎng)絡(luò)刷新裙士,則先返回執(zhí)行completedBlock,但事沒完管毙,還要繼續(xù)從網(wǎng)絡(luò)加載刷新腿椎,這個(gè)適合可能在后臺同一個(gè)url換另一張圖片的情形桌硫。

設(shè)置一個(gè)網(wǎng)絡(luò)下載策略。

接著構(gòu)造了一個(gè)downloadToken作為屬性賦給了operation啃炸∶可能我們會好奇,downloadToken是啥南用,點(diǎn)進(jìn)去看:

可以看得出膀钠,這個(gè)token的作用是作為一個(gè)下載的獨(dú)立標(biāo)示,這樣一來圖片加載處理的operation可以通過持有的這個(gè)token來進(jìn)行撤銷下載等操作裹虫。

從上邊這整塊代碼來看肿嘲,可以發(fā)現(xiàn)是又manager維護(hù)的imageDownloader下載器負(fù)責(zé)下載圖片的,并返回這個(gè)token筑公。

在這個(gè)方法中傳入了url雳窟、downloaderOptions下載策略、progressBlock匣屡、completedBlock等參數(shù)封救,progressBlock就是前文API過程調(diào)用的blockcompletedBlock從字面上看應(yīng)該也是結(jié)束后調(diào)用的block捣作,暫且不論兴泥,首先來分析這個(gè)下載器的工作:

可以看出SDWebImageDownloader其實(shí)是一個(gè)下載管理類,imageDownloader就是這個(gè)管理類的一個(gè)單例對象虾宇,負(fù)責(zé)在運(yùn)行期間管理圖片下載搓彻,維護(hù)者下載operationURLOperations字典,downloadQueue下載任務(wù)隊(duì)列等嘱朽。

逐步展開來看:

如果url為空旭贬,直接返回空,并接受操作搪泳。

先根據(jù)以urlkey值從self.URLOperations中查找具體的下載operation稀轨。如果不存在或已結(jié)束或已撤銷就創(chuàng)建一個(gè)新的并加入self.URLOperations字典和self.downloadQueue隊(duì)列。如果存在岸军,但并不在執(zhí)行中奋刽,可根據(jù)operation下載策略來調(diào)整下載operation的優(yōu)先級。整個(gè)過程都是在線程安全中進(jìn)行的艰赞。

緊接著構(gòu)造SDWebImageDownloadToken *token返回賦給圖片加載處理的operationdownloadToken佣谐。

下面繼續(xù)深入分析具體的下載operation:

可以看到下載operation其實(shí)是NSOperation的一個(gè)實(shí)例,遵守<SDWebImageDownloaderOperationInterface>協(xié)議方妖。

展開分析:

設(shè)置超時(shí)時(shí)間狭魂,網(wǎng)絡(luò)請求安全策略,構(gòu)造網(wǎng)絡(luò)請求request

根據(jù)request構(gòu)造相應(yīng)的operation雌澄,并根據(jù)下載策略options做優(yōu)先級處理等斋泄。

進(jìn)入operation的構(gòu)造方法:

可以看出operation的類其實(shí)是框架自定義的繼承于NSOperation的類。其任務(wù)主體主要是通過重寫的start函數(shù)實(shí)現(xiàn)的:

首先也是用@synchronized 鎖來鎖住相關(guān)代碼镐牺,這部分代碼就是初始化session炫掐、dataTask部分。

展開這個(gè)鎖:

如果任務(wù)已經(jīng)被撤銷睬涧,重置卒废。

設(shè)置應(yīng)用退入后臺的處理。

初始化session宙地,不過這里的session一般使用的是初始化方法傳進(jìn)來的session,一般是由imageDownloader維護(hù)的一個(gè)框架全局的session逆皮。

根據(jù)下載策略option設(shè)置網(wǎng)絡(luò)請求緩存策略宅粥。

通過sessionrequest構(gòu)造請求任務(wù)dataTask,并將任務(wù)operationexecuting狀態(tài)設(shè)置為執(zhí)行电谣。

根據(jù)下載策略option設(shè)置dataTask的優(yōu)先級秽梅,并運(yùn)行dataTask

回頭發(fā)現(xiàn)imageDownloader的初始化函數(shù)中

sessiondelegate給了imageDownloader剿牺,但同樣SDWebImageDownloader也繼承了<NSURLSessionTaskDelegate, NSURLSessionDataDelegate>協(xié)議企垦,并實(shí)現(xiàn)了具體協(xié)議方法。

而在協(xié)議方法中:

又將回調(diào)方法在下載dataOperation中執(zhí)行晒来,所以再來到dataOperation中實(shí)現(xiàn)的代理方法:

首先在線程安全下降dataTask置空钞诡,緊接著就開始處理返回的圖片的二進(jìn)制數(shù)據(jù):

除去錯(cuò)誤處理等,核心代碼是這塊:

這塊主要就是在self.coderQueue異步隊(duì)列中完成圖片解碼湃崩,下面逐步分析:

在這里SDWebImageCodersManager這個(gè)解碼管理類就粉墨登場了荧降,同樣也是實(shí)現(xiàn)了一個(gè)全局的單例實(shí)例來做解碼工作。

首先看一下SDWebImageCodersManager的初始化方法:

初始化中除了實(shí)現(xiàn)安全鎖外攒读,更重要的初始化這個(gè)數(shù)組_coders很重要朵诫,這個(gè)數(shù)組目前只有一個(gè)元素SDWebImageImageIOCoder*類型的實(shí)例。

然后進(jìn)入第一個(gè)解碼方法:

根據(jù)初始化方法會走到SDWebImageImageIOCoder*類型的實(shí)例方法:

第一行代碼是普通的解碼方法薄扁,第二行代碼是框架通過NSData的類別實(shí)現(xiàn)的一個(gè)查看圖片類型的方法不再詳述剪返。

然后獲取圖片的緩存key值从橘,并矯正圖片方向定踱。

然而就此結(jié)束了嗎蹦锋?向下走:

這里有又了一個(gè)解碼方法霍衫,這點(diǎn)可能就會引起大家的困惑董栽,為什么要這樣呢椅野?原來[[UIImage alloc] initWithData:data]這個(gè)方法并沒有實(shí)際解碼鸣峭,只有將image第一次顯示的時(shí)候才會解碼盆犁,并長久滯留內(nèi)存,這并不是一個(gè)很好的處理方法诈铛。這一點(diǎn)也就是SDWebImage在那個(gè)時(shí)代很精華的一部分乙各,通過調(diào)用

這個(gè)方法將圖片繪制到CG畫布上,控件直接加載畫布上的image幢竹,這在當(dāng)時(shí)是一個(gè)巨大的創(chuàng)新耳峦,下面就具體分析這個(gè)方法:

如果下載策略不包含大圖片等比例縮小的話將走到這個(gè)方法:

展開分析:

做個(gè)判斷,如果image為空或是動畫直接返回焕毫。

緊接著就是CG的天地了:

主要思路竟是通過創(chuàng)建一個(gè)bitmap context蹲坷,將圖片繪制到畫布上,才從畫布上獲取image邑飒。

就此解碼工作完成循签,也會產(chǎn)生長久滯留內(nèi)存的圖片信息數(shù)據(jù)。順便提到上文在從硬盤中獲取緩存時(shí)賣的關(guān)子疙咸,從硬盤IO獲取到圖片的二進(jìn)制數(shù)據(jù)也走的是這條線县匠。

就此image加載也就完成了,先回到下載operation的回調(diào)方法中撒轮,還在那個(gè)圖片解碼的隊(duì)列coderQueue的代碼塊中:

進(jìn)去這個(gè)方法:

可以看出是通過下載operation維護(hù)的callbackBlock數(shù)組找到回調(diào)的completedBlock執(zhí)行回調(diào)乞旦,到這里同學(xué)們是否有迷路的,能否找到回家的路呢题山?

回到這個(gè)下載operation的構(gòu)造方法:

可以看出就是在

這個(gè)方法中將過程progressBlock和完成completedBlock傳入下載operation中的兰粉。

到此為止,整個(gè)網(wǎng)絡(luò)下載過程也就分析完了顶瞳,回到SDWebImageManager勒種圖片加載operaion的構(gòu)造方法中玖姑,

現(xiàn)在就到這completedBlock的執(zhí)行部分。

正常的話就會走到這個(gè)選擇支:

先做緩存慨菱,由manger持有的緩存管理imageCache來執(zhí)行:

展開分析:

如果image或key不存在客峭,直接返回。

將解碼后的image放入內(nèi)存緩存抡柿,下次從內(nèi)存中加載就不需要解碼舔琅。

異步磁盤IO,將圖片壓縮后二進(jìn)制數(shù)據(jù)存入硬盤洲劣。

結(jié)束緩存后:

終于到了激動人心的時(shí)候了备蚓,就像一個(gè)走了很多路的孩子終于要回家了,終于回調(diào)框架API中的completionBlock了囱稽。

展開分析郊尝,除去過程處理和重繪處理,主干代碼走到這里:

賦值要處理的imagedata战惊,再走到之前提到到的

方法流昏,給相應(yīng)控件賦值,就此一個(gè)調(diào)用結(jié)束,可以長吁一口氣了况凉。

說了這么多谚鄙,感覺是時(shí)候總結(jié)一下了,

從網(wǎng)路上借張圖:

來自SDWebImage源碼分析 原

按照本篇的分析刁绒,整個(gè)路徑是這樣的:首先調(diào)用加載圖片的UIImageVIew*類型的imageView的分類UIView+WebCacheAPI闷营,在API方法中,通過id<SDWebImageOperation>的對象知市,也就是SDWebImageManager*類型的manager傻盟,通過manager構(gòu)造加載圖片的id <SDWebImageOperation> operation,也就是SDWebImageCombinedOperation *類型的operation嫂丙,并由分類UIView+WebCache持有的sd_operationDictionary字典來管理娘赴。然后通過id <SDWebImageOperation> operation來構(gòu)造并持有cacheOperation來獲取緩存,同時(shí)如果需要從網(wǎng)絡(luò)下載跟啤,便構(gòu)造持有網(wǎng)絡(luò)下載的dataOperation來完成下載以及圖片解碼诽表。這就是大致流程,當(dāng)然在這個(gè)過程中緩存管理腥光,圖片解碼等都有很多可圈可點(diǎn)的地方,再次不再贅述糊秆,可以單獨(dú)開篇講解武福。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市痘番,隨后出現(xiàn)的幾起案子捉片,更是在濱河造成了極大的恐慌,老刑警劉巖汞舱,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伍纫,死亡現(xiàn)場離奇詭異,居然都是意外死亡昂芜,警方通過查閱死者的電腦和手機(jī)莹规,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泌神,“玉大人良漱,你說我怎么就攤上這事』都剩” “怎么了母市?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長损趋。 經(jīng)常有香客問我患久,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任蒋失,我火速辦了婚禮返帕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘高镐。我一直安慰自己溉旋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布嫉髓。 她就那樣靜靜地躺著观腊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪算行。 梳的紋絲不亂的頭發(fā)上梧油,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機(jī)與錄音州邢,去河邊找鬼儡陨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛量淌,可吹牛的內(nèi)容都是我干的骗村。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼呀枢,長吁一口氣:“原來是場噩夢啊……” “哼胚股!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起裙秋,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤琅拌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后摘刑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體进宝,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年枷恕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了党晋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡徐块,死狀恐怖隶校,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛹锰,我是刑警寧澤深胳,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站铜犬,受9級特大地震影響舞终,放射性物質(zhì)發(fā)生泄漏轻庆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一敛劝、第九天 我趴在偏房一處隱蔽的房頂上張望余爆。 院中可真熱鬧,春花似錦夸盟、人聲如沸蛾方。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桩砰。三九已至,卻和暖如春释簿,著一層夾襖步出監(jiān)牢的瞬間亚隅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工庶溶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煮纵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓偏螺,卻偏偏與公主長得像行疏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子套像,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355