YYWebImage 源碼剖析:線程處理與緩存策略

系列文章:
YYText 源碼剖析:CoreText 與異步繪制
YYAsyncLayer 源碼剖析:異步繪制
YYCache 源碼剖析:一覽亮點(diǎn)
YYModel 源碼剖析:關(guān)注性能
YYImage 源碼剖析:圖片處理技巧
YYWebImage 源碼剖析:線程處理與緩存策略

引言

在 iOS 開發(fā)中,異步網(wǎng)絡(luò)圖片下載框架可以說是很大的解放了生產(chǎn)力穿香,通常情況下開發(fā)者只需要簡單的代碼就能將網(wǎng)絡(luò)圖片異步下載并顯示到手機(jī)屏幕上亭引,并且還帶有緩存優(yōu)化。

業(yè)界名氣最高的異步圖片下載框架是 SDWebImage扔水,而后 ibireme 前輩開源了 YYWebImage痛侍,對(duì)性能有所優(yōu)化。之前有粗略的瀏覽過 SDWebImage 的源碼,對(duì)比 YYWebImage 源碼過后主届,實(shí)際上筆者更喜歡 YYWebImage赵哲,因?yàn)槠浯a風(fēng)格很簡潔、代碼結(jié)構(gòu)更清晰君丁。

技術(shù)層面來看枫夺,兩者對(duì)線程處理的處理方式有所不同,緩存策略也有細(xì)節(jié)上的差異绘闷,雖然筆者的理解來看 YYWebImage 性能更為優(yōu)越橡庞,但是并沒有充分的測試用例來驗(yàn)證。有些遺憾的是印蔗,YYWebImage 似乎挺久沒有維護(hù)了扒最,作者在 這條 issues 說過計(jì)劃會(huì)將NSURLConnection替換為NSURLSession,到現(xiàn)在都沒有動(dòng)作??华嘹。

所以實(shí)際開發(fā)中為了穩(wěn)定性可能還是會(huì)首選 SDWebImage吧趣,但是這絲毫不影響我們學(xué)習(xí) YYWebImage 的優(yōu)秀源碼,本文主要是分析 YYWebImage 的核心思路和亮點(diǎn)耙厚。

源碼版本:1.0.5

一强挫、框架總覽

//包含所有文件的頭文件
YYWebImage.h
//緩存相關(guān)
YYImageCache.h (.m)
//請(qǐng)求任務(wù)預(yù)處理類
_YYWebImageSetter.h (.m)
//請(qǐng)求任務(wù)管理類
YYWebImageManager.h (.m)
//自定義請(qǐng)求類(繼承自NSOperation)
YYWebImageOperation.h (.m)
//方便業(yè)務(wù)調(diào)用的分類
CALayer+YYWebImage.h (.m)
MKAnnotationView+YYWebImage.h (.m)
UIButton+YYWebImage.h (.m)
UIImage+YYWebImage.h (.m)
UIImageView+YYWebImage.h (.m)

上面這些方便業(yè)務(wù)調(diào)用的分類,它們的實(shí)現(xiàn)大同小異薛躬,使用最多的是UIImageView+YYWebImage.h俯渤,完全可以以其為入口探究框架的原理。

正如作者框架的簡短說明:

Asynchronous image loading framework.

該框架的核心就是異步下載網(wǎng)絡(luò)圖片型宝。

  • 既然是異步下載八匠,就涉及到線程的高效調(diào)度問題,由于在業(yè)務(wù)場景中下載圖片的任務(wù)可能是繁重的诡曙,所以線程處理的性能至關(guān)重要臀叙。
  • 圖片下載成功過后,為了避免顯示圖片時(shí)在主線程解壓价卤,框架做了異步解壓,對(duì)于gif渊涝、APNG慎璧、WebP等都有支持,這部分功能是基于作者的另一個(gè)框架 YYImage跨释,筆者之前寫過源碼分析:YYImage 源碼剖析:圖片處理技巧胸私。
  • 為了不重復(fù)下載和重復(fù)解壓,框架做了緩存優(yōu)化鳖谈,至于是否緩存解壓過后的圖片岁疼,可以由開發(fā)者選擇,當(dāng)然,緩存分內(nèi)存緩存和磁盤緩存捷绒,讀寫速度一般也是內(nèi)存大于磁盤瑰排,這部分功能是基于作者的另一個(gè)框架 YYCache,筆者之前也寫過源碼分析:YYCache 源碼剖析:一覽亮點(diǎn)暖侨。

二椭住、重復(fù)下載請(qǐng)求處理

該處理主要是基于_YYWebImageSetter.h下的一個(gè)屬性:

@property (nonatomic, readonly) int32_t sentinel;

UIImageView+YYWebImage.h的一個(gè)方法看起:

- (void)yy_setImageWithURL:(NSURL *)imageURL
               placeholder:(UIImage *)placeholder
                   options:(YYWebImageOptions)options
                   manager:(YYWebImageManager *)manager
                  progress:(YYWebImageProgressBlock)progress
                 transform:(YYWebImageTransformBlock)transform
                completion:(YYWebImageCompletionBlock)completion {
    ...
    //第一步:為 UIImageView 綁定一個(gè) _YYWebImageSetter 對(duì)象
    _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
    if (!setter) {
        setter = [_YYWebImageSetter new];
        objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    int32_t sentinel = [setter cancelWithNewURL:imageURL];
    
    _yy_dispatch_sync_on_main_queue(^{
        ...
        __weak typeof(self) _self = self;
        dispatch_async([_YYWebImageSetter setterQueue], ^{
            ...
    //第二步:開始下載任務(wù)
            newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
            weakSetter = setter;
        });
    });
}

筆者省略了大部分代碼,不用在意這些線程操作字逗,現(xiàn)在只關(guān)注重復(fù)請(qǐng)求的處理京郑。

第一步 中,利用 runtime 為UIImageView綁定一個(gè)_YYWebImageSetter對(duì)象葫掉,然后調(diào)用了一個(gè)方法cancelWithNewURL:些举,該方法實(shí)現(xiàn)如下:

- (int32_t)cancelWithNewURL:(NSURL *)imageURL {
    int32_t sentinel;
    dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
    if (_operation) {
        [_operation cancel];
        _operation = nil;
    }
    _imageURL = imageURL;
    sentinel = OSAtomicIncrement32(&_sentinel);
    dispatch_semaphore_signal(_lock);
    return sentinel;
}

可以看到作者取消了_operation任務(wù),對(duì)于同一個(gè)UIImageView的重復(fù)請(qǐng)求時(shí)俭厚,取消_operation任務(wù)也就是取消上一次請(qǐng)求的任務(wù)户魏。

然后有一句至關(guān)重要的代碼:sentinel = OSAtomicIncrement32(&_sentinel);,使用原子自增保證全局變量_sentinel的線程安全和讀取性能套腹。也就是說绪抛,對(duì)于同一個(gè)UIImageView每次調(diào)用yy_setImageWithURL: ...方法都會(huì)取消上次的請(qǐng)求并且將其_sentinel加一。

這么做的意義电禀,往下面看幢码。

第二步 中,調(diào)用了_YYWebImageSettersetOperationWithSentinel: ...方法:

- (int32_t)setOperationWithSentinel:(int32_t)sentinel
                                url:(NSURL *)imageURL
                            options:(YYWebImageOptions)options
                            manager:(YYWebImageManager *)manager
                           progress:(YYWebImageProgressBlock)progress
                          transform:(YYWebImageTransformBlock)transform
                         completion:(YYWebImageCompletionBlock)completion {
//1尖飞、判斷當(dāng)前請(qǐng)求是否是最新請(qǐng)求
    if (sentinel != _sentinel) {
        if (completion) completion(nil, imageURL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
        return _sentinel;
    }
    
    NSOperation *operation = ... //省略實(shí)際網(wǎng)絡(luò)請(qǐng)求邏輯
    
//2症副、判斷當(dāng)前請(qǐng)求是否是最新請(qǐng)求
    dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
    if (sentinel == _sentinel) {
        if (_operation) [_operation cancel];
        _operation = operation;
        sentinel = OSAtomicIncrement32(&_sentinel);
    } else {
        [operation cancel];
    }
    dispatch_semaphore_signal(_lock);
    return sentinel;
}

可以看到兩個(gè)地方都有 判斷當(dāng)前請(qǐng)求是否是最新請(qǐng)求 的邏輯。對(duì)于第 1 個(gè)地方政基,因?yàn)樵谠摲椒ㄈ霔5臅r(shí)候贞铣,可能該UIImageView的下一次yy_setImageWithURL: ...又一次入棧,也就是說_sentinel可能已經(jīng)加一了沮明,那么這里就沒有必要繼續(xù)下面的網(wǎng)絡(luò)請(qǐng)求邏輯了(代碼已省略)辕坝;對(duì)于第 2 個(gè)地方,也是同樣的考慮荐健,若此刻_sentinel已經(jīng)加一了酱畅,就取消掉當(dāng)前已經(jīng)創(chuàng)建好的NSOperation,若此刻_sentinel沒變江场,就取消掉上一次的_operation纺酸,然后_sentinel自增。

值得注意的是址否,這里的信號(hào)量使用是為了保證_operation讀寫安全餐蔬,而不是為了保護(hù)_sentinel(因?yàn)樵幼栽霰旧砭褪蔷€程安全的)。

大致重復(fù)請(qǐng)求的處理就是如此,若看得有些費(fèi)解建議多看幾遍源碼里面完整的代碼樊诺。

三仗考、線程的處理

1、下載任務(wù)的預(yù)處理

同樣是在UIImageView+YYWebImage.h下的入口方法:

- (void)yy_setImageWithURL:(NSURL *)imageURL
               placeholder:(UIImage *)placeholder
                   options:(YYWebImageOptions)options
                   manager:(YYWebImageManager *)manager
                  progress:(YYWebImageProgressBlock)progress
                 transform:(YYWebImageTransformBlock)transform
                completion:(YYWebImageCompletionBlock)completion {
    ...
    
    _yy_dispatch_sync_on_main_queue(^{
        ...
//第一步:在主線程讀取內(nèi)存緩存
        // get the image from memory as quickly as possible
        UIImage *imageFromMemory = nil;
        if (manager.cache &&
            !(options & YYWebImageOptionUseNSURLCache) &&
            !(options & YYWebImageOptionRefreshImageCache)) {
            imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
        }
        if (imageFromMemory) {
            if (!(options & YYWebImageOptionAvoidSetImage)) {
                self.image = imageFromMemory;
            }
            if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
            return;
        }
        ...
        __weak typeof(self) _self = self;
//第二步:在異步線程做下載任務(wù)的預(yù)處理
        dispatch_async([_YYWebImageSetter setterQueue], ^{
            ...
            newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
            weakSetter = setter;
        });
    });
}

第一步

可以看到作者的一句英文注釋啄骇,也就是盡可能快的從內(nèi)存讀取緩存 (如果有)痴鳄,這里是一個(gè)很有意思的優(yōu)化點(diǎn)。了解 YYCache 框架的讀者應(yīng)該知道缸夹,作者是使用 雙向鏈表+hash 的方式實(shí)現(xiàn)的內(nèi)存緩存痪寻,直接查找的開銷比切換后臺(tái)線程查找而后返回主線程的開銷要小。

第二步

下載任務(wù)的預(yù)處理是在一個(gè)[_YYWebImageSetter setterQueue]隊(duì)列虽惭,代碼如下:

+ (dispatch_queue_t)setterQueue {
    static dispatch_queue_t queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        queue = dispatch_queue_create("com.ibireme.webimage.setter", DISPATCH_QUEUE_SERIAL);
        dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
    });
    return queue;
}

可以看到這是一個(gè)串行的隊(duì)列橡类,優(yōu)先級(jí)為DISPATCH_QUEUE_PRIORITY_DEFAULT,小于主隊(duì)列芽唇。

可能有朋友會(huì)疑問顾画,下載任務(wù)在異步隊(duì)列?那豈不是同一時(shí)刻只有一個(gè)下載任務(wù)執(zhí)行匆笤?

哈哈研侣,注意看清筆者的描述:下載任務(wù)的預(yù)處理。這里面包含了任務(wù)的創(chuàng)建炮捧、重復(fù)請(qǐng)求處理等邏輯庶诡,并沒有耗時(shí)過多的操作,使用一個(gè)異步的線程來處理也是為了減輕主線程的壓力咆课。下載任務(wù)的線程處理后面會(huì)講到末誓,并不是此處的串行隊(duì)列。

2书蚪、下載任務(wù)的處理

該框架使用了NSURLConnection處理下載任務(wù)喇澡,姑且不談它的用法,畢竟已經(jīng)淘汰了殊校。它的代理線程是如此創(chuàng)建的:

/// Network thread entry point.
+ (void)_networkThreadMain:(id)object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"com.ibireme.webimage.request"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}
/// Global image request network thread, used by NSURLConnection delegate.
+ (NSThread *)_networkThread {
    static NSThread *thread = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        thread = [[NSThread alloc] initWithTarget:self selector:@selector(_networkThreadMain:) object:nil];
        if ([thread respondsToSelector:@selector(setQualityOfService:)]) {
            thread.qualityOfService = NSQualityOfServiceBackground;
        }
        [thread start];
    });
    return thread;
}

這段代碼在老版本的 AFNetwork 和 SDWebImage 里面都出現(xiàn)過晴玖,創(chuàng)建一個(gè)常駐線程來處理下載任務(wù)的回調(diào),通過添加一個(gè) NSMachPort 端口保證該線程的 runloop 的正常運(yùn)行不退出为流,由于手動(dòng)創(chuàng)建的線程不包含自動(dòng)釋放池窜醉,所以作者加了一個(gè)。

這里的亮點(diǎn)其實(shí)是這么一句方法:thread.qualityOfService = NSQualityOfServiceBackground;艺谆。

作者很細(xì)心的將線程的優(yōu)先級(jí)設(shè)置為NSQualityOfServiceBackground,這是一個(gè)比較低的優(yōu)先級(jí)拜英,作者希望圖片的下載回調(diào)相關(guān)處理不會(huì)和其他線程競爭 CPU 的資源(比如操作 UI 的主線程等)静汤。

3、圖片讀取和解壓處理

圖片從磁盤中讀取、寫入虫给、解壓等操作都是在下面這個(gè)隊(duì)列處理的(圖片處理具體原理可看YYImage 源碼剖析:圖片處理技巧):

+ (dispatch_queue_t)_imageQueue {
    #define MAX_QUEUE_COUNT 16
    static int queueCount;
    static dispatch_queue_t queues[MAX_QUEUE_COUNT];
    static dispatch_once_t onceToken;
    static int32_t counter = 0;
    dispatch_once(&onceToken, ^{
        queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
        queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
        if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
            for (NSUInteger i = 0; i < queueCount; i++) {
                dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
                queues[i] = dispatch_queue_create("com.ibireme.image.decode", attr);
            }
        } else {
            for (NSUInteger i = 0; i < queueCount; i++) {
                queues[i] = dispatch_queue_create("com.ibireme.image.decode", DISPATCH_QUEUE_SERIAL);
                dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0));
            }
        }
    });
    int32_t cur = OSAtomicIncrement32(&counter);
    if (cur < 0) cur = -cur;
    return queues[(cur) % queueCount];
    #undef MAX_QUEUE_COUNT
}

創(chuàng)建與處理器相同的串行隊(duì)列模擬并發(fā)控制藤抡,具體的原理分析可以看筆者的一篇文章:YYAsyncLayer 源碼剖析:異步繪制 中對(duì)線程的討論,這種并發(fā)線程的處理是作者的一個(gè)常規(guī)思路抹估,不多說缠黍。

四、緩存策略

在該框架中的體現(xiàn)药蜻,上層的業(yè)務(wù)邏輯是這樣的:

  1. 優(yōu)先查找內(nèi)存緩存瓷式,若找到則返回
  2. 若內(nèi)存緩存未找到,會(huì)異步從磁盤查找緩存语泽,若找到則返回贸典,并且寫入內(nèi)存緩存方便下次查找
  3. 若磁盤緩存仍然未找到,發(fā)起網(wǎng)絡(luò)請(qǐng)求
  4. 網(wǎng)絡(luò)請(qǐng)求成功踱卵,同時(shí)寫入磁盤緩存和內(nèi)存緩存

實(shí)際上這個(gè)邏輯和 SDWebImage 基本一致廊驼。值得注意的是,是否查找內(nèi)存或磁盤緩存惋砂、是否需要緩存妒挎、緩存的大小限制等都有自定義的方法。

上層的核心邏輯就是如此西饵,關(guān)于內(nèi)存緩存和磁盤緩存的底層實(shí)現(xiàn)酝掩,可以查看YYCache 源碼剖析:一覽亮點(diǎn)

五罗标、加載指示器的處理

加載指示器是在YYWebImageManager.m中處理的庸队,其他代碼就不貼出來了

@interface _YYWebImageApplicationNetworkIndicatorInfo : NSObject
@property (nonatomic, assign) NSInteger count;
@property (nonatomic, strong) NSTimer *timer;
@end

+ (_YYWebImageApplicationNetworkIndicatorInfo *)_networkIndicatorInfo {
    return objc_getAssociatedObject(self, @selector(_networkIndicatorInfo));
}
+ (void)_setNetworkIndicatorInfo:(_YYWebImageApplicationNetworkIndicatorInfo *)info {
    objc_setAssociatedObject(self, @selector(_networkIndicatorInfo), info, OBJC_ASSOCIATION_RETAIN);
}
...

綁定到YYWebImageManager的一個(gè)類變量_YYWebImageApplicationNetworkIndicatorInfo,也就是說變量的timercount都是全局的闯割。

彻消。處理指示器本質(zhì)是容易的,但是作者的思路挺有意思宙拉。

一是作者通過一個(gè)NSTimer來延時(shí) 1/30 秒開啟或者關(guān)閉加載指示器宾尚。

二是作者通過“計(jì)數(shù)”來控制指示器是否顯示,也就是上面的count谢澈,當(dāng)有網(wǎng)絡(luò)任務(wù)開始的時(shí)候計(jì)數(shù)加一煌贴,當(dāng)有網(wǎng)絡(luò)任務(wù)結(jié)束或者異常取消時(shí)計(jì)數(shù)減一,那么锥忿,只要count大于零就顯示指示器牛郑,否則就隱藏。

這思路確實(shí)挺巧妙敬鬓。

六淹朋、框架的性能瓶頸

YYWebImageOperation.m下的-connectionDidFinishLoading:代理方法中可以看到圖片的解壓邏輯笙各,它是在_imageQueue中執(zhí)行的,解壓完成就緩存起來方便顯示础芍。

雖然解壓的過程是在異步線程杈抢,通常情況下不會(huì)影響到主線程,但是當(dāng)解壓的圖片過多或者圖片分辨率過大時(shí)仑性,解壓和緩存會(huì)占用大量的內(nèi)存惶楼,導(dǎo)致內(nèi)存峰值飆升。

所以诊杆,需要開發(fā)者做一些性能上的優(yōu)化歼捐,不過可喜的是可以通過YYWebImageOptionsYYWebImageOptionIgnoreImageDecoding值禁止下載成功后的解壓和緩存邏輯,以此降低內(nèi)存峰值刽辙。

七窥岩、框架中的一些小 tips

1、自動(dòng)釋放池

可以看到框架中使用了大量的自動(dòng)釋放池來避免內(nèi)存峰值宰缤,可能有開發(fā)者感覺如此頻繁的使用自動(dòng)釋放池是否會(huì)造成性能問題颂翼,實(shí)際上影響不大。了解自動(dòng)釋放池的底層原理的朋友都知道慨灭,添加一個(gè)自動(dòng)釋放池不過是添加一個(gè)標(biāo)識(shí)(哨兵)朦乏,需要管理對(duì)象加入自動(dòng)釋放池可以看做是入棧操作,當(dāng)棧頂?shù)倪@個(gè)自動(dòng)釋放池結(jié)束氧骤,會(huì)自動(dòng)給池內(nèi)對(duì)象發(fā)送release消息(這里池內(nèi)就是棧頂?shù)健吧诒钡姆秶?/p>

2呻疹、鎖的使用

YYWebImageOperation.m中使用了遞歸鎖NSRecursiveLock避免多次獲取鎖而導(dǎo)致死鎖,當(dāng)然筹陵,筆者認(rèn)為這里使用pthread_mutex_t互斥鎖的遞歸實(shí)現(xiàn)處理性能應(yīng)該更好刽锤。

在操作少量的、耗時(shí)少的代碼時(shí)朦佩,使用dispatch_semaphore_t信號(hào)量保證線程安全并思,有性能優(yōu)勢。

在對(duì)int32_t類型變量進(jìn)行安全保護(hù)時(shí)语稠,使用OSAtomicIncrement32()原子方法無疑是很好的選擇宋彼。

3、避免循環(huán)引用

框架中通過一個(gè)中間類的消息轉(zhuǎn)發(fā)來達(dá)到避免循環(huán)引用的目的:

@interface _YYWebImageWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation _YYWebImageWeakProxy
- (instancetype)initWithTarget:(id)target {
    _target = target;
    return self;
}
+ (instancetype)proxyWithTarget:(id)target {
    return [[_YYWebImageWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

關(guān)于具體的分析可以看筆者的文章YYImage 源碼剖析:圖片處理技巧有相應(yīng)的解析仙畦。

結(jié)語

不得不說输涕,框架都是有套路的。在閱讀 YYKit 系列的代碼中慨畸,也懂了作者的套路莱坎,所以筆者在閱讀 YYWebImage 源碼時(shí)非常快寸士,幾乎沒有卡殼型奥,可能這就是“厚積薄發(fā)”的小小體現(xiàn)吧瞳收。

考慮到篇幅和碼字太累,筆者的分析文章都是剝繭抽絲的厢汹,若讀者朋友閱讀有障礙,請(qǐng)沉下心來谐宙,多結(jié)合源碼烫葬,多思考??。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凡蜻,一起剝皮案震驚了整個(gè)濱河市搭综,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌划栓,老刑警劉巖兑巾,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異忠荞,居然都是意外死亡蒋歌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門委煤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堂油,“玉大人,你說我怎么就攤上這事碧绞「颍” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵讥邻,是天一觀的道長迫靖。 經(jīng)常有香客問我,道長兴使,這世上最難降的妖魔是什么系宜? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮鲫惶,結(jié)果婚禮上蜈首,老公的妹妹穿的比我還像新娘。我一直安慰自己欠母,他們只是感情好欢策,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赏淌,像睡著了一般踩寇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上六水,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天俺孙,我揣著相機(jī)與錄音辣卒,去河邊找鬼。 笑死睛榄,一個(gè)胖子當(dāng)著我的面吹牛荣茫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播场靴,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼啡莉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了旨剥?” 一聲冷哼從身側(cè)響起咧欣,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎轨帜,沒想到半個(gè)月后魄咕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蚌父,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年哮兰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梢什。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奠蹬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗡午,到底是詐尸還是另有隱情囤躁,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布荔睹,位于F島的核電站狸演,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏僻他。R本人自食惡果不足惜宵距,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吨拗。 院中可真熱鬧满哪,春花似錦、人聲如沸劝篷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娇妓。三九已至像鸡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哈恰,已是汗流浹背只估。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工志群, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蛔钙。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓锌云,卻偏偏與公主長得像,于是被迫代替她去往敵國和親夸楣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宾抓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • 1、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_x閱讀 15,968評(píng)論 3 119
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,321評(píng)論 8 265
  • 今天上午聽了河南省師范學(xué)院劉鳳山老師的書法講座豫喧,真是受益匪淺。劉鳳山幢泼,河南師范大學(xué)美術(shù)學(xué)院副教授紧显,碩士生導(dǎo)師...
    湯湯人閱讀 790評(píng)論 0 3
  • 優(yōu)勢 1、不需要插件支持缕棵; 2孵班、良好的用戶體驗(yàn); 3招驴、提高web程序的性能篙程; 4、減輕服務(wù)器和帶寬的負(fù)擔(dān)别厘。 不足 ...
    杜小黑妞閱讀 240評(píng)論 0 0
  • 夜垂下黑色的幕布 群星閃爍 似在窺探人間的秘密 街道上高樓林立 窗戶里亮著的燈 越來越少了 我那盞固執(zhí)的燈 拖著常...
    梓姝zs閱讀 302評(píng)論 3 7