SDWebImage源碼解析(一)

SDWebImage是一個(gè)圖片下載的開源項(xiàng)目逢艘,由于它提供了簡(jiǎn)介的接口以及異步下載與緩存的強(qiáng)大功能,深受“猿媛“的喜愛。截止到本篇文章開始,項(xiàng)目的star數(shù)已經(jīng)超過1.6k了掖疮。今天我就對(duì)項(xiàng)目的源碼做個(gè)閱讀筆記,一方面歸納總結(jié)自己的心得颗祝,另一方面給準(zhǔn)備閱讀源碼的童鞋做點(diǎn)鋪墊工作浊闪。代碼最新版本為3.8恼布。

正如項(xiàng)目的第一句介紹一樣:

Asynchronous image downloader with cache support as a UIImageView category

SDWebImage是個(gè)支持異步下載與緩存的UIImageView擴(kuò)展。項(xiàng)目主要提供了一下功能:

  • 擴(kuò)展UIImageView, UIButton, MKAnnotationView搁宾,增加網(wǎng)絡(luò)圖片與緩存管理折汞。
  • 一個(gè)異步的圖片加載器
  • 一個(gè)異步的 內(nèi)存 + 磁盤 圖片緩存,擁有自動(dòng)的緩存過期處理機(jī)制盖腿。
  • 支持后臺(tái)圖片解壓縮處理
  • 確保同一個(gè) URL 的圖片不被多次下載
  • 確保虛假的 URL 不會(huì)被反復(fù)加載
  • 確保下載及緩存時(shí)爽待,主線程不被阻塞
  • 使用 GCD 與 ARC

項(xiàng)目支持的圖片格式包括PNG,JEPG,GIF,WebP等等。

先看看SDWebImage的項(xiàng)目組織架構(gòu):

SDWebImage組織架構(gòu).png

SDWebImageDownloader負(fù)責(zé)維持圖片的下載隊(duì)列翩腐;
SDWebImageDownloaderOperation負(fù)責(zé)真正的圖片下載請(qǐng)求堕伪;
SDImageCache負(fù)責(zé)圖片的緩存;
SDWebImageManager是總的管理類栗菜,維護(hù)了一個(gè)SDWebImageDownloader實(shí)例和一個(gè)SDImageCache實(shí)例欠雌,是下載與緩存的橋梁;
SDWebImageDecoder負(fù)責(zé)圖片的解壓縮;
SDWebImagePrefetcher負(fù)責(zé)圖片的預(yù)雀沓铩富俄;
UIImageView+WebCache和其他的擴(kuò)展都是與用戶直接打交道的。

其中而咆,最重要的三個(gè)類就是SDWebImageDownloader霍比、SDImageCacheSDWebImageManager暴备。接下來(lái)我們就分別詳細(xì)地研究一下這些類各自具體做了哪些事悠瞬,又是怎么做的。

為了便于大家從宏觀上有個(gè)把握涯捻,我這里先給出項(xiàng)目的框架結(jié)構(gòu):

Paste_Image.png

UIImageView+WebCacheUIButton+WebCache直接為表層的 UIKit框架提供接口, 而 SDWebImageManger負(fù)責(zé)處理和協(xié)調(diào)SDWebImageDownloaderSDWebImageCache, 并與 UIKit層進(jìn)行交互浅妆。SDWebImageDownloaderOperation真正執(zhí)行下載請(qǐng)求;最底層的兩個(gè)類為高層抽象提供支持障癌。
我們按照從上到下執(zhí)行的流程來(lái)研究各個(gè)類

UIImageView+WebCache

這里凌外,我們只用UIImageView+WebCache來(lái)舉個(gè)例子,其他的擴(kuò)展類似涛浙。
常用的場(chǎng)景是已知圖片的url地址康辑,來(lái)下載圖片并設(shè)置到UIImageView上。UIImageView+WebCache提供了一系列的接口:

- (void)setImageWithURL:(NSURL *)url;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
- (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock;

這些接口最終會(huì)調(diào)用

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock轿亮;

方法的第一行代碼[self sd_cancelCurrentImageLoad]是取消UIImageView上當(dāng)前正在進(jìn)行的異步下載疮薇,確保每個(gè) UIImageView 對(duì)象中永遠(yuǎn)只存在一個(gè) operation,當(dāng)前只允許一個(gè)圖片網(wǎng)絡(luò)請(qǐng)求我注,該 operation 負(fù)責(zé)從緩存中獲取 image 或者是重新下載 image按咒。具體執(zhí)行代碼是:

// UIView+WebCacheOperation.m
// Cancel in progress downloader from queue
NSMutableDictionary *operationDictionary = [self operationDictionary];
id operations = [operationDictionary objectForKey:key];
if (operations) {
    if ([operations isKindOfClass:[NSArray class]]) {
        for (id <SDWebImageOperation> operation in operations) {
            if (operation) {
                [operation cancel];
            }
        }
    } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
        [(id<SDWebImageOperation>) operations cancel];
    }
    [operationDictionary removeObjectForKey:key];
}

實(shí)際上,所有的操作都是由一個(gè)operationDictionary字典維護(hù)的,執(zhí)行新的操作之前仓手,先cancel所有的operation胖齐。這里的cancel是SDWebImageOperation協(xié)議里面定義的。

//預(yù)覽 占位圖
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            self.image = placeholder;
        });
    }

是一種占位圖策略嗽冒,作為圖片下載完成之前的替代圖片呀伙。dispatch_main_async_safe是一個(gè)宏,保證在主線程安全執(zhí)行添坊,最后再講剿另。
然后判斷url,url為空就直接調(diào)用完成回調(diào)贬蛙,報(bào)告錯(cuò)誤信息雨女;否則,用SDWebImageManager單例的

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock

方法下載圖片阳准。下載完成之后刷新UIImageView的圖片氛堕。

//圖像的繪制只能在主線程完成
dispatch_main_sync_safe(^{
    if (!wself) return;
        if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
        {//延遲設(shè)置圖片,手動(dòng)處理
            completedBlock(image, error, cacheType, url);
            return;
        } else if (image) {
             //直接設(shè)置圖片
             wself.image = image;
             [wself setNeedsLayout];
        } else {
            //image== nil,設(shè)置占位圖
            if ((options & SDWebImageDelayPlaceholder)) {
                wself.image = placeholder;
                [wself setNeedsLayout];
            }
    }
    if (completedBlock && finished) {
        completedBlock(image, error, cacheType, url);
    }
});

最后野蝇,把返回的id <SDWebImageOperation> operation添加到operationDictionary中讼稚,方便后續(xù)的cancel。

SDWebImageManager

SDWebImageManager.h中是這樣描述SDWebImageManager類的:

The SDWebImageManager is the class behind the UIImageView+WebCache category and likes.It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache).You can use this class directly to benefit from web image downloading with caching in another context than a UIView.

即隱藏在UIImageView+WebCache背后绕沈,用于處理異步下載和圖片緩存的類锐想,當(dāng)然你也可以直接使用 SDWebImageManager 的方法 downloadImageWithURL:options:progress:completed:來(lái)直接下載圖片。

SDWebImageManager.h首先定義了一些枚舉類型的SDWebImageOptions乍狐。關(guān)于這些Options的具體含義可以參考葉孤城大神的解析

然后赠摇,聲明了三個(gè)block:

//操作完成的回調(diào),被上層的擴(kuò)展調(diào)用浅蚪。
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
//被SDWebImageManager調(diào)用藕帜。如果使用了SDWebImageProgressiveDownload標(biāo)記,這個(gè)block可能會(huì)被重復(fù)調(diào)用惜傲,直到圖片完全下載結(jié)束耘戚,finished=true,再最后調(diào)用一次這個(gè)block。
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
//SDWebImageManager每次把URL轉(zhuǎn)換為cache key的時(shí)候調(diào)用操漠,可以刪除一些image URL中的動(dòng)態(tài)部分收津。
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);

定義了SDWebImageManagerDelegate協(xié)議:

@protocol SDWebImageManagerDelegate <NSObject>

@optional

/**
 * Controls which image should be downloaded when the image is not found in the cache.
 *
 * @param imageManager The current `SDWebImageManager`
 * @param imageURL     The url of the image to be downloaded
 *
 * @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
 * 控制在cache中沒有找到image時(shí) 是否應(yīng)該去下載。
 */
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;

/**
 * Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
 * NOTE: This method is called from a global queue in order to not to block the main thread.
 *
 * @param imageManager The current `SDWebImageManager`
 * @param image        The image to transform
 * @param imageURL     The url of the image to transform
 *
 * @return The transformed image object.
 * 在下載之后浊伙,緩存之前轉(zhuǎn)換圖片撞秋。在全局隊(duì)列中操作,不阻塞主線程
 */
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;

@end

SDWebImageManager是單例使用的嚣鄙,分別維護(hù)了一個(gè)SDImageCache實(shí)例和一個(gè)SDWebImageDownloader實(shí)例吻贿。 類方法分別是:

//初始化SDWebImageManager單例,在init方法中已經(jīng)初始化了cache單例和downloader單例哑子。
- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader;
//下載圖片
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
//緩存給定URL的圖片
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
//取消當(dāng)前所有的操作
- (void)cancelAll;
//監(jiān)測(cè)當(dāng)前是否有進(jìn)行中的操作
- (BOOL)isRunning;
//監(jiān)測(cè)圖片是否在緩存中舅列, 先在memory cache里面找  再到disk cache里面找
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
//監(jiān)測(cè)圖片是否緩存在disk里
- (BOOL)diskImageExistsForURL:(NSURL *)url;
//監(jiān)測(cè)圖片是否在緩存中,監(jiān)測(cè)結(jié)束后調(diào)用completionBlock
- (void)cachedImageExistsForURL:(NSURL *)url
                     completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//監(jiān)測(cè)圖片是否緩存在disk里,監(jiān)測(cè)結(jié)束后調(diào)用completionBlock
- (void)diskImageExistsForURL:(NSURL *)url
                   completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//返回給定URL的cache key
- (NSString *)cacheKeyForURL:(NSURL *)url;

我們主要研究

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock

首先肌割,監(jiān)測(cè)url 的合法性:

if ([url isKindOfClass:NSString.class]) {
    url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
    url = nil;
}

第一個(gè)判斷條件是防止很多用戶直接傳遞NSString作為NSURL導(dǎo)致的錯(cuò)誤,第二個(gè)判斷條件防止crash帐要。

if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        dispatch_main_sync_safe(^{
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
            completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
        });
        return operation;
    }

集合failedURLs保存之前失敗的urls把敞,如果url為空或者url之前失敗過且不采用重試策略,直接調(diào)用completedBlock返回錯(cuò)誤榨惠。

@synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }

runningOperations是一個(gè)可變數(shù)組奋早,保存所有的operation,主要用來(lái)監(jiān)測(cè)是否有operation在執(zhí)行赠橙,即判斷running 狀態(tài)耽装。

SDWebImageManager會(huì)首先在memory以及disk的cache中查找是否下載過相同的照片,即調(diào)用imageCache

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock 

方法期揪。
如果在緩存中找到圖片掉奄,直接調(diào)用completedBlock,第一個(gè)參數(shù)是緩存的image凤薛。

dispatch_main_sync_safe(^{
    __strong __typeof(weakOperation) strongOperation = weakOperation;
    if (strongOperation && !strongOperation.isCancelled) {//為啥這里用strongOperation TODO
        completedBlock(image, nil, cacheType, YES, url);
    }
});

如果沒有在緩存中找到圖片挥萌,或者不管是否找到圖片,只要operation有SDWebImageRefreshCached標(biāo)記枉侧,那么若SDWebImageManagerDelegateshouldDownloadImageForURL方法返回true引瀑,即允許下載時(shí),都使用 imageDownloader

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock

方法進(jìn)行下載榨馁。如果下載有錯(cuò)誤憨栽,直接調(diào)用completedBlock返回錯(cuò)誤,并且視情況將url添加到failedURLs里面翼虫;

dispatch_main_sync_safe(^{
    if (strongOperation && !strongOperation.isCancelled) {
        completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
    }
});

if (error.code != NSURLErrorNotConnectedToInternet
 && error.code != NSURLErrorCancelled
 && error.code != NSURLErrorTimedOut
 && error.code != NSURLErrorInternationalRoamingOff
 && error.code != NSURLErrorDataNotAllowed
 && error.code != NSURLErrorCannotFindHost
 && error.code != NSURLErrorCannotConnectToHost) {
      @synchronized (self.failedURLs) {
          [self.failedURLs addObject:url];
      }
}

如果下載成功屑柔,若支持失敗重試,將url從failURLs里刪除:

if ((options & SDWebImageRetryFailed)) {
    @synchronized (self.failedURLs) {
         [self.failedURLs removeObject:url];
    }
}

如果delegate實(shí)現(xiàn)了珍剑,imageManager:transformDownloadedImage:withURL:方法掸宛,圖片在緩存之前,需要做轉(zhuǎn)換(在全局隊(duì)列中調(diào)用招拙,不阻塞主線程)唧瘾。轉(zhuǎn)化成功切下載全部結(jié)束,圖片存入緩存别凤,調(diào)用completedBlock回調(diào)饰序,第一個(gè)參數(shù)是轉(zhuǎn)換后的image。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

    if (transformedImage && finished) {
        BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
        //將圖片緩存起來(lái)
        [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
    }
    dispatch_main_sync_safe(^{
        if (strongOperation && !strongOperation.isCancelled) {
            completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
        }
    });
});

否則规哪,直接存入緩存求豫,調(diào)用completedBlock回調(diào),第一個(gè)參數(shù)是下載的原始image。

if (downloadedImage && finished) {
    [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}

dispatch_main_sync_safe(^{
    if (strongOperation && !strongOperation.isCancelled) {
        completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
    }
});

存入緩存都是調(diào)用imageCache

- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk

方法蝠嘉。

如果沒有在緩存找到圖片最疆,且不允許下載,直接調(diào)用completedBlock蚤告,第一個(gè)參數(shù)為nil努酸。

dispatch_main_sync_safe(^{
    __strong __typeof(weakOperation) strongOperation = weakOperation;
    if (strongOperation && !weakOperation.isCancelled) {//為啥這里用weakOperation TODO
        completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
    }
});

最后都要將這個(gè)operation從runningOperations里刪除。

@synchronized (self.runningOperations) {
    [self.runningOperations removeObject:operation];
 }

這里再說(shuō)一下上面的operation罩缴,是一個(gè)SDWebImageCombinedOperation實(shí)例:

@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>

@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;

@end

是一個(gè)遵循SDWebImageOperation協(xié)議的NSObject子類。

@protocol SDWebImageOperation <NSObject>

- (void)cancel;

@end

在里面封裝一個(gè)NSOperation层扶,這么做的目的應(yīng)該是為了使代碼更簡(jiǎn)潔箫章。因?yàn)橄螺d操作需要查詢緩存的operation和實(shí)際下載的operation,這個(gè)類的cancel方法可以同時(shí)cancel兩個(gè)operation镜会,同時(shí)還可以維護(hù)一個(gè)狀態(tài)cancelled檬寂。
敬請(qǐng)期待后續(xù)更新!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末戳表,一起剝皮案震驚了整個(gè)濱河市桶至,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌匾旭,老刑警劉巖镣屹,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異价涝,居然都是意外死亡女蜈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門色瘩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)伪窖,“玉大人,你說(shuō)我怎么就攤上這事居兆「采剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵泥栖,是天一觀的道長(zhǎng)簇宽。 經(jīng)常有香客問我,道長(zhǎng)吧享,這世上最難降的妖魔是什么晦毙? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮耙蔑,結(jié)果婚禮上见妒,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好须揣,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布盐股。 她就那樣靜靜地躺著,像睡著了一般耻卡。 火紅的嫁衣襯著肌膚如雪疯汁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天卵酪,我揣著相機(jī)與錄音幌蚊,去河邊找鬼。 笑死溃卡,一個(gè)胖子當(dāng)著我的面吹牛溢豆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瘸羡,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼漩仙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了犹赖?” 一聲冷哼從身側(cè)響起队他,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎峻村,沒想到半個(gè)月后麸折,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡粘昨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年磕谅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雾棺。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膊夹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捌浩,到底是詐尸還是另有隱情放刨,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布尸饺,位于F島的核電站进统,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏浪听。R本人自食惡果不足惜螟碎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望迹栓。 院中可真熱鬧掉分,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至不从,卻和暖如春惜姐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背椿息。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工歹袁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寝优。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓条舔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親倡勇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子逞刷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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