一個iOS流暢性優(yōu)化工具

簡介

  • LNAsyncKit是一個異步渲染工具,它提供了便捷的方法幫助你將多個元素(Element)異步渲染到一張圖片上,讓這個過程代替UIKit的視圖構(gòu)建過程,進(jìn)而優(yōu)化App性能;Prender提供預(yù)加載策略幫助你在Feed流中彌補(bǔ)異步渲染帶來的延時;除構(gòu)建視圖外,Transaction提供更優(yōu)雅的方式讓主線程與子線程交互,并能根據(jù)機(jī)器狀態(tài)控制并發(fā)數(shù)和主線程回調(diào)時機(jī)。

  • LNAsyncKit借(ji)鑒(cheng)了很多YYKit和Texture,如果對它們不是很了解可以戳這個比較詳細(xì)的文章,這篇文章的作者是YY大神:iOS保持頁面流暢的技巧。流暢性優(yōu)化的思想基本上都如這篇文章所述。

它可以提供哪些幫助

  • 還沒有找到方案優(yōu)化圓角、邊框、漸變的優(yōu)化方案,LNAsyncKit可以異步解決這些。
  • Feed流需要預(yù)加載策略,LNAsyncKit提供預(yù)加載區(qū)域計算方案(這個方案也用來預(yù)合成)。
  • 提供一種與UIKit十分接近的方式構(gòu)建需要預(yù)合成的圖層,讓你的復(fù)雜圖層構(gòu)建都放在子線程進(jìn)行,且不會創(chuàng)建那么多UIView。
  • Demo展示了使用:AFNetworking/SDWebImage/IGListKit/YYModel/MJRefresh + LNAsyncKit搭建feed流的方法。除去LNAsyncKit,前面5個構(gòu)成的這套體系已經(jīng)比較完整,Demo中也提供了沒有使用LNAsyncKit構(gòu)建的Feed。因此,需要快速學(xué)習(xí)如何搭建一套Feed流的初學(xué)者可以參考這套三方。

作為一個開發(fā)者,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:413038000,不管你是大牛還是小白都?xì)g迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗(yàn),討論技術(shù), 大家一起交流學(xué)習(xí)成長!

Github鏈接

你可以直接下載這個鏈接并運(yùn)行上面的Demo參考代碼實(shí)現(xiàn)自己的異步列表,也可以直接使用Cocoapods??

pod 'LNAsyncKit'
復(fù)制代碼

流暢性優(yōu)化

網(wǎng)絡(luò)上已經(jīng)有了很多流暢性優(yōu)化的文章,再逐一復(fù)述這些優(yōu)化點(diǎn)意義不大;這個文章是為了表達(dá)如何在Feed流中實(shí)現(xiàn)那些優(yōu)化思想,并把這個過程簡化;所以,不再贅述這些優(yōu)化點(diǎn)為什么好、好多少,只談怎么實(shí)現(xiàn)它們;如果對這些優(yōu)化點(diǎn)有疑問可以參考上面鏈接的文章,以下這些觀點(diǎn)成立:

  • 圖層少的列表比圖層多的列表好。
  • 沒有圓角、邊框、漸變等復(fù)雜圖層的比有的好。
  • 圖片尺寸和控件尺寸一樣大的好。
  • 模型解析放在子線程比放在主線程好。
  • 布局計算放在子線程比放在主線程好。
  • 有預(yù)加載比沒有預(yù)加載好(見仁見智,也有喜歡無預(yù)加載列表的)。
  • Layer比View好(無手勢時)。
  • 不透明圖層比透明圖層好。

在業(yè)務(wù)復(fù)雜度不變的前提下讓這些優(yōu)化工作變簡單、自由就是LNAsyncKit的目標(biāo)。

優(yōu)化一個Cell

我們將一個Cell視為Feed流的最小優(yōu)化單元,以一個Bilibili推薦Feed流中一個常規(guī)的Cell為例:

這樣一個小Cell中包含了:封面圖、人數(shù)圖標(biāo)、人數(shù)Label、主播昵稱、直播間名、[直播]、直播內(nèi)容分類、負(fù)反饋按鈕8個元素;除了這些元素外,還包括封面圖底部一個黑色漸變的圖層、[直播]的圓角、邊框和整個Cell的圓角(好像還有些陰影);這個小Cell已經(jīng)包含比較多的小元素了,我們在Demo中嘗試復(fù)原一下并查看視圖層級大致如下:

具體構(gòu)建代碼這里不贅述了,使用LNAsyncKit可以簡化這個Cell為如下這個樣子:

(右下角反饋Bug需要響應(yīng)事件,通常這種控件會保持獨(dú)立)

以“直播”標(biāo)簽為例,視圖構(gòu)建方式區(qū)別如下:

UIKit:

    self.liveTagLabel.layer.cornerRadius = 3.f;
    self.liveTagLabel.layer.borderColor = [UIColor colorWithRed:239.f/255.f green:91.f/255.f blue:156.f/255.f alpha:1.f].CGColor;
    self.liveTagLabel.layer.borderWidth = 1.f;
    self.liveTagLabel.text = @"直播";
    self.liveTagLabel.font = [UIFont systemFontOfSize:12.f];
    self.liveTagLabel.textColor = [UIColor colorWithRed:239.f/255.f green:91.f/255.f blue:156.f/255.f alpha:1.f];
    self.liveTagLabel.textAlignment = NSTextAlignmentCenter;
    [self.cellContentView addSubview:self.liveTagLabel];
復(fù)制代碼

LNAsyncKit:

    LNAsyncTextElement *liveTagElement = [[LNAsyncTextElement alloc] init];
    liveTagElement.cornerRadius = 3.f;
    liveTagElement.borderColor = [UIColor colorWithRed:239.f/255.f green:91.f/255.f blue:156.f/255.f alpha:1.f];
    liveTagElement.borderWidth = 1.f;
    liveTagElement.text = @"直播";
    liveTagElement.font = [UIFont systemFontOfSize:12.f];
    liveTagElement.textColor = [UIColor colorWithRed:239.f/255.f green:91.f/255.f blue:156.f/255.f alpha:1.f];
    liveTagElement.textAligment = NSTextAlignmentCenter;
    [cellContentElement addSubElement:liveTagElement];
復(fù)制代碼

經(jīng)過LNAsyncKit渲染出與需要展示視圖面積一樣大的一張完整圖片,復(fù)雜渲染邏輯全部被子線程消化,反饋到主線程只表現(xiàn)為一張與目標(biāo)控件大小一致的圖片。

原理

與UIKit類似,LNAsyncKit也使用視圖樹構(gòu)建最終視圖。區(qū)別是:

A. Element繼承自NSObject,這些Element可以在子線程創(chuàng)建、渲染、銷毀。可以將Element理解為“一個需要繪制圖層”的描述物,它并不是一個實(shí)體,它與UIView/CALayer的區(qū)別就好像:UIView是你要買的一件物品;Element則是下單信息,里面包含這件物品的各種描述信息,多大、什么顏色等。

B. 所有的Element都是臨時的,這些信息在構(gòu)建出結(jié)果后就會被銷毀,你可以在進(jìn)入子線程之后創(chuàng)建這些Element,在渲染出真正的圖片后銷毀這些Element,然后在主線程返回需要的圖片,像這樣:

    dispatch_queue_t queue = dispatch_queue_create(0, 0);
    dispatch_async(queue, ^{
        LNAsyncElement *contentElement = [weakSelf rebuildElements];
        [LNAsyncRenderer traversalElement:contentElement];
        UIImage *image = contentElement.renderResult;
        contentElement.renderResult = nil;
        dispatch_async(dispatch_get_main_queue(), ^{
            weakSelf.imageView.image = image;
        });
    });
復(fù)制代碼

rebuildElement的過程可以構(gòu)建出很復(fù)雜的一棵樹,但對主線程來說,這并不會造成問題!不在主線程出現(xiàn)Element也是LNAsyncKit推薦的使用方法(拿到resultImage后,把Element.resultImage置為空),當(dāng)然,出現(xiàn)了一般也無所謂,NSObject的消耗相對于UIView來講是很小的。

C.Element是逐層渲染的:實(shí)際上是后續(xù)遍歷,把A的子Element先渲染出來,然后渲染A,再把A當(dāng)做一個子節(jié)點(diǎn)渲染父節(jié)點(diǎn),LNAsyncRendererTraversalStack就是遍歷時使用的棧、LNAsyncRenderer.traversal函數(shù)是遍歷方法。遍歷中自帶了環(huán)檢測,不會渲染重復(fù)Element,像這樣:

    LNAsyncRendererTraversalStack *stack = [[LNAsyncRendererTraversalStack alloc] init];
    [stack pushElements:@[element]];

    NSMutableSet <LNAsyncElement *> *repeatDetectMSet = [[NSMutableSet alloc] init];
    while (!stack.isEmpty) {
        LNAsyncElement *topElement = [stack top];
        if (topElement.getSubElements.count > 0 && (![repeatDetectMSet containsObject:topElement])) {
            [repeatDetectMSet addObject:topElement];
            [stack pushElements:topElement.getSubElements.reverseObjectEnumerator.allObjects];
        } else {
            [stack pop];
            [self renderElement:topElement];
            for (LNAsyncElement *subElement in topElement.getSubElements) {
                subElement.renderResult = nil;
            }
        }
    }
復(fù)制代碼

LNAsync自帶了一些Element:

  • LNAsyncElement: 對應(yīng)于UIKit的UIView,是其他Element的基類,包含了背景色、frame、和常用的邊界、圓角等屬性。
  • LNASyncImageElement: 對應(yīng)于UIImageView,渲染一張圖片、提供三種填充方式。
  • LNAsyncTextElement: 對應(yīng)于UILabel,渲染一段文字,提供常規(guī)文字屬性、支持折行。
  • LNAsyncLinerGradientElement: 對應(yīng)于CAGradientLayer,渲染一段漸變色。

自定義Element:

除原生Element外,我們也推薦封裝自己的Element,例如:一個AvatarElement,可以將用戶頭像、VIP標(biāo)識、頭像邊框等修飾物渲染在一起,重寫- (void)renderSelfWithContext:(CGContextRef)context,在這個方法中分別繪制這三個元素。

自定義Element的意義在于,所有自定義過的Element都是可復(fù)用、可組合的,這樣方便保持整個App風(fēng)格統(tǒng)一,也會適當(dāng)減少開發(fā)成本。

Feed流

上面我們已經(jīng)介紹過單個Cell、單張圖片如何異步渲染以優(yōu)化性能,但性能問題往往不是單張圖片所能引發(fā),LNAsyncKit更傾向于性能敏感的場景:Feed流;渲染Feed流相比渲染單個視圖需要考慮的事情要多一些:Cell復(fù)用、渲染好的圖片緩存、多張圖片下載和結(jié)果合并等問題;除此之外,也考慮使用預(yù)加載、預(yù)渲染功能來優(yōu)化用戶體驗(yàn)。

使用到的三方庫:

  • AFNetworking 網(wǎng)絡(luò)
  • IGListKit Feed流框架,可以拆分各個模塊業(yè)務(wù)
  • SDWebImage 圖片下載
  • YYModel 字典轉(zhuǎn)模型
  • MJRefresh 上拉/下拉刷新組件
  • 一位大佬寫的免費(fèi)API ,雖然我不認(rèn)識這位大佬,但這些接口確實(shí)非常方便,在這里面朝空氣感謝一下~

這些都是非常成熟的三方框架,直接拿來用會減少不少開發(fā)時間;這里主要是介紹如何將LNAsyncKit融進(jìn)這個體系中去。Demo中已經(jīng)提供了默認(rèn)的Feed流和異步的Feed流代碼,如果遇到了一些奇怪的Bug可以參考Demo中的實(shí)現(xiàn),目前這兩個Demo都可以正常運(yùn)行。

  • 默認(rèn)Demo:我們用此Demo展示一個常規(guī)Feed流實(shí)現(xiàn)過程,沒有使用任何修飾手法或設(shè)計思想,可以理解為實(shí)現(xiàn)一個Feed流所需要做的最少工作。
  • 異步Demo:我們用此Demo將使用LNAsyncKit實(shí)現(xiàn)Feed流時與通常情況下的實(shí)現(xiàn)的進(jìn)行對比,了解從普通Feed轉(zhuǎn)異步Feed的修改點(diǎn)和差異之處。

默認(rèn)Feed流實(shí)現(xiàn):

  1. ViewDidLoad中使用AFNetworking請求一頁數(shù)據(jù),使用YYModel解析成Model類型數(shù)據(jù),賦值給VC。
  2. VC調(diào)用CollectionView/IGList刷新列表,將Model賦值到Cell內(nèi)部。
  3. Cell內(nèi)部賦值懶加載的Label、ImageView調(diào)用sd_setImage下載圖片展示。

異步Feed流的優(yōu)化:

1.圖片下載放在Model中進(jìn)行

A.因?yàn)楫惒紽eed不僅僅需要下載圖片,也需要將多個原始圖片進(jìn)行預(yù)合成,所以這個過程在Model中進(jìn)行可以保證不會因Cell復(fù)用問題導(dǎo)致同一時間合成多次,如果你在Cell中異步進(jìn)行圖層合成,那可能每次賦值Model都會合成一次,但在Model中合成后可以一直存放在Model中(Model只持有弱引用,存在全局的NSCache中)。

B.考慮預(yù)加載,我們認(rèn)為圖層的預(yù)加載和預(yù)合成是兩種優(yōu)先級的事情,通常距離屏幕焦點(diǎn)區(qū)域較遠(yuǎn)的區(qū)域只需要進(jìn)行圖片預(yù)下載,而距離較近的地方則需要預(yù)合成,不論是哪種方式,Cell通常只會在展示在屏幕上的時間點(diǎn)附近才能拿到,如果圖片下載放在Cell中進(jìn)行,是很難實(shí)現(xiàn)“預(yù)”的。

MVC中Model的職責(zé)之一是提供View展示需要的數(shù)據(jù),所以在Model中下載圖片并非錯誤或不恰當(dāng)?shù)淖龇ā?/p>

2.模型解析和布局計算視為網(wǎng)絡(luò)請求的一部分

通常,在使用AFNetworking進(jìn)行網(wǎng)絡(luò)請求時,我們通常在成功回調(diào)中進(jìn)行模型解析和列表刷新,列表刷新時走CollectionView的dataSource協(xié)議計算布局。

異步列表不推薦這樣做:模型解析的過程沒有想象中的那樣簡單,通常進(jìn)行模型解析時需要逐層遍歷Dictionary,然后創(chuàng)建大量Model和子Model,雖然單個NSObject開銷不大,但列表視圖的模型總是堆積起來的,創(chuàng)建如此多的對象也是個不小的開銷。

計算布局的耗時是公認(rèn)的,所以一般表視圖優(yōu)化都推薦緩存行高,但即便緩存行高,第一次在主線程中的計算也是有一定耗時的。

我們推薦在AFNetworking回調(diào)中異步進(jìn)行模型解析和布局計算,將這兩個操作視為網(wǎng)絡(luò)請求的一部分,這并不會對網(wǎng)絡(luò)請求的整體響應(yīng)時間有較大的影響,因?yàn)榫W(wǎng)絡(luò)回調(diào)時間單位通常要比屏幕刷新時間單位高出一個數(shù)量級。況且,預(yù)加載技術(shù)完全可以彌補(bǔ)這段小延時。

在請求回調(diào)中賦值給Model的LayoutObj就是對這個過程的封裝,像這樣:

- (void)transferFeedData:(NSDictionary *)dic comletion:(DemoFeedNetworkCompletionBlock)completion
{
    LNAsyncTransaction *transaction = [[LNAsyncTransaction alloc] init];

    [transaction addOperationWithBlock:^id _Nullable{
        DemoFeedModel *feedModel = [DemoFeedModel yy_modelWithDictionary:dic];
        for (DemoFeedItemModel *item in feedModel.result) {
            DemoAsyncFeedDisplayLayoutObjInput *layoutInput = [[DemoAsyncFeedDisplayLayoutObjInput alloc] init];
            layoutInput.contextString = item.title;
            layoutInput.hwScale = 0.3f + ((random()%100)/100.f)*0.5f; 
            DemoAsyncFeedDisplayLayoutObj *layoutObj = [[DemoAsyncFeedDisplayLayoutObj alloc] initWithInput:layoutInput];
            item.layoutObj = layoutObj;
        }
        return feedModel;
    } priority:1 queue:_transferQueue completion:^(id  _Nullable value, BOOL canceled) {
        if (completion) {
            completion(YES, value, nil);
        }
    }];

    [transaction commit];
}
復(fù)制代碼
3.在Model中布局

這聽起來有點(diǎn)詭異,在Model中下載圖片也就算了,為什么視圖操作也在Model中進(jìn)行?

我們已經(jīng)解釋了Element的職責(zé),它只是負(fù)責(zé)描述的類。使用element構(gòu)建視圖的過程就是:Model想好要怎么構(gòu)建(Element),把想法交付LNAsyncRenderer,renderer交付我們image,Model把image反回給View顯示出來。就像我們在開始的時候講述的那樣。

4.預(yù)加載

預(yù)加載主要內(nèi)容包括兩個方面:預(yù)加載下一頁信息和預(yù)加載圖片。這里提到的預(yù)加載主要是指預(yù)加載圖片:

根據(jù)上面我們提到了圖片加載都是在Model中進(jìn)行的,所以,每個Model都需要一個必要的參數(shù)來標(biāo)記自身所持有的資源已經(jīng)到了那種緊急的程度了,如果距離當(dāng)前用戶焦點(diǎn)還很遠(yuǎn),說明自己的資源目前不是很緊急,可以先靜觀其變;如果距離用戶焦點(diǎn)有點(diǎn)近了,說明自己可能需要開始考慮先把圖片下載下來;如果距離用戶焦點(diǎn)已經(jīng)相當(dāng)近了,就要立刻開始準(zhǔn)備把已有資源預(yù)合成了。類似這樣:

- (void)setStatus:(DemoFeedItemModelStatus)status
{
    if (status > _status) {
        _status = status;
    }
    [self checkCurrentStatus];
}

- (void)checkCurrentStatus
{
    if (self.status >= DemoFeedItemModelStatusPreload) {
        //需要預(yù)加載圖片
        [self preloadImage];
    }

    if (self.status >= DemoFeedItemModelStatusDisplay) {
         //需要渲染視圖
         [self renderView];
    }
}
復(fù)制代碼

LNAsyncCollectionViewPrender提供了一套資源緊急程度標(biāo)記策略,將距離當(dāng)前屏幕中心較遠(yuǎn)的資源標(biāo)記為不緊急,較近的資源標(biāo)記為緊急,Model受緊急程度標(biāo)記影響自主進(jìn)行預(yù)加載或預(yù)渲染。

這套智能預(yù)加載機(jī)制來自于Texture,非常的實(shí)用,我將它修改為Objective-C實(shí)現(xiàn),并做了簡化處理。你甚至可以參照這個區(qū)間計算思路制作一個滾動列表曝光打點(diǎn)類,來計算那些更符合用戶視距的曝光區(qū)間,而不是僅僅簡單依賴cell/View的生命周期,說到這不得不提一嘴:我曾經(jīng)見過一個埋點(diǎn)系統(tǒng)迭代了兩年多依然沒啥卵用。

5.圖片一致性校驗(yàn)

異步Cell渲染圖片回調(diào)設(shè)置圖片需要進(jìn)行渲染的模型與當(dāng)前模型是否一致的校驗(yàn),復(fù)用可能會導(dǎo)致一個Cell先后被設(shè)置兩個Model,這樣兩個Model在異步渲染結(jié)束后都可能通知Cell刷新數(shù)據(jù),所以需要一致性校驗(yàn)。同步不存在這個問題,后來的內(nèi)容總是會覆蓋掉先來的圖片。像這樣:

    NSObject *model = self.model;
    __weak DemoAsyncFeedCell *weakSelf = self;
    [self.model demoAsyncFeedItemLoadRenderImage:^(BOOL isCanceled, UIImage * _Nullable resultImage) {
        if (!isCanceled && resultImage && model == weakSelf.model) {
            weakSelf.contentView.layer.contents = (__bridge id)resultImage.CGImage;
        }
    }];
復(fù)制代碼
6.渲染緩存

與SDWebImage下載的原生Image不同,渲染后的圖片存儲在額外的一個渲染緩存中,Model弱引用持有,緩存內(nèi)部使用LRU管理;不能使用Model強(qiáng)引用,因?yàn)橛行〧eed流是常駐的,我們不希望內(nèi)存浪費(fèi)在不是主要消費(fèi)場景的常駐頁面中。LNAsyncCache是統(tǒng)一的存放的地方,你可以在渲染成功后把圖片存在這里,使用弱指針指向它,如果被刪除了,就重新渲染、存儲。

7.減少渲染次數(shù)

SD下載圖片時附帶AvoidDecode參數(shù),因?yàn)楹铣蛇^程會將Image渲染到一塊內(nèi)存中,這個過程本身就包含了解碼,且也是在子線程中進(jìn)行;使用這個參數(shù)可以減少圖片剛下載好時的那次渲染。像這樣:

[[SDWebImageManager sharedManager] loadImageWithURL:[NSURL URLWithString:weakSelf.image]
                                            options:SDWebImageAvoidDecodeImage 
                                           progress:nil 
                                          completed:nil];
復(fù)制代碼

總結(jié)

LNAsyncKit優(yōu)化的內(nèi)容就如上所述:

  • 從主線程的角度來看:除了刷新CollectionView和計算預(yù)加載區(qū)域外基本上沒有耗時工作,布局計算和模型解析轉(zhuǎn)移到了子線程統(tǒng)一進(jìn)行,Element創(chuàng)建銷毀操作主線程基本上沒有感知。
  • 從CPU的角度來看:圓角、邊框、漸變等工作都在圖層合成的時候異步消化了,返回的圖片大小和Layer控件大小也是一致的,圖層的復(fù)雜層級也被子線程異步消化。
  • 從子線程角度看:子線程有很多。

寫異步Feed流比普通Feed流難度要稍微大一些,平均開發(fā)的時間成本也會有所上升;從效率上來講,每個需求的開發(fā)效率確實(shí)降低了,但這將會省去在未來單獨(dú)成立一個性能優(yōu)化小組進(jìn)行優(yōu)化的效率要高得多。平臺類型的開發(fā)人員往往沒有業(yè)務(wù)開發(fā)對業(yè)務(wù)更熟悉,因此需要頻繁交流確認(rèn)優(yōu)化點(diǎn)、改動范圍、影響等等。而且,有時遇到優(yōu)化點(diǎn)時業(yè)務(wù)受限,可能不敢大刀闊斧地糾正,導(dǎo)致優(yōu)化后的結(jié)果和優(yōu)化前對比并不明顯。LNAsyncKit讓業(yè)務(wù)線從一開始做需求時就考慮到優(yōu)化內(nèi)容,從而省去了專項優(yōu)化的時間。當(dāng)然,如果App整體不考慮性能問題,選擇正常的開發(fā)方式就好。

雜談

iPhone手機(jī)硬件越來越強(qiáng),常規(guī)業(yè)務(wù)不進(jìn)行優(yōu)化一般也能達(dá)到流暢性標(biāo)準(zhǔn),端內(nèi)的卡頓只要不是特別嚴(yán)重產(chǎn)品經(jīng)理通常也都能接受;我在需求中使用了類似的方式進(jìn)行性能優(yōu)化,開發(fā)時間確實(shí)很緊。當(dāng)然,如果你的公司只考慮需求產(chǎn)出,他們通常不會給你這些時間,你可以在自己的編碼追求和實(shí)際情況之間決定是否要額外做這些事。

LNAsyncKit可以直接使用,也可以將它當(dāng)做你更深層次了解性能優(yōu)化、Texture的墊腳石;總之,它能起到任何幫助,我都將十分榮幸。

作為一個開發(fā)者,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:413038000,不管你是大牛還是小白都?xì)g迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗(yàn),討論技術(shù), 大家一起交流學(xué)習(xí)成長!

作者:BangRaJun
鏈接:https://juejin.cn/post/6934720152546050078
來源:掘金

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,627評論 2 380

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