iOS UITableView或UICollectionView列表性能優(yōu)化之異步繪制

一扭屁、需求背景

1、現(xiàn)狀

iOS所提供的UIKit框架涩禀,其工作基本是在主線程上進(jìn)行料滥,界面繪制、用戶輸入響應(yīng)交互等等艾船。當(dāng)大量且頻繁的繪制任務(wù)葵腹,以及各種業(yè)務(wù)邏輯同時(shí)放在主線程上完成時(shí),便有可能造成界面卡頓屿岂,丟幀現(xiàn)象践宴,即在16.7ms內(nèi)未能完成1幀的繪制,幀率低于60fps黃金標(biāo)準(zhǔn)爷怀。目前常用的UITableView或UICollectionView阻肩,在大量復(fù)雜文本及圖片內(nèi)容填充后,如果沒(méi)有優(yōu)化處理运授,快速滑動(dòng)的情況下易出現(xiàn)卡頓烤惊,流暢性差問(wèn)題。

2徒坡、需求

不依賴任何第三方pod框架撕氧,主要從異步線程繪制瘤缩、圖片異步下載渲染等方面喇完,盡可能優(yōu)化UITableView的使用,提高滑動(dòng)流暢性,讓幀率穩(wěn)定在60fps锦溪。

(網(wǎng)上有很多優(yōu)秀的性能優(yōu)化博客和開(kāi)源代碼不脯,本方案也是基于前人的經(jīng)驗(yàn),結(jié)合自身的理解和梳理寫成demo刻诊,關(guān)鍵代碼有做注釋防楷,很多細(xì)節(jié)值得推敲和持續(xù)優(yōu)化,不足之處望指正则涯。)

二复局、解決方案及亮點(diǎn)

1、方案概述

? 異步繪制任務(wù)收集與去重;

? 通過(guò)單例監(jiān)聽(tīng)main runloop回調(diào)粟判,執(zhí)行異步繪制任務(wù)亿昏;

? 支持異步繪制動(dòng)態(tài)文本內(nèi)容,減輕主線程壓力档礁,并緩存高度減少CPU計(jì)算角钩;

? 支持異步下載和渲染圖片并緩存,僅在可視區(qū)域渲染呻澜;

? 異步隊(duì)列并發(fā)管理递礼,擇優(yōu)選取執(zhí)行任務(wù);

? 發(fā)現(xiàn)UITableView首次reload會(huì)觸發(fā)3次的系統(tǒng)問(wèn)題羹幸,初始開(kāi)銷增大脊髓,待優(yōu)化;

2栅受、問(wèn)題點(diǎn)

? 異步繪制時(shí)機(jī)及減少重復(fù)繪制供炼;

? 隊(duì)列的并發(fā)和擇優(yōu);

3窘疮、分析過(guò)程

1)異步繪制時(shí)機(jī)及減少重復(fù)繪制

這里簡(jiǎn)單描述下繪制原理:當(dāng)UI被添加到界面后袋哼,我們改變Frame,或更新 UIView/CALayer層次闸衫,或調(diào)用setNeedsLayout/setNeedsDisplay方法涛贯,均會(huì)添加重新繪制任務(wù)。這個(gè)時(shí)候系統(tǒng)會(huì)注冊(cè)一個(gè)Observer監(jiān)聽(tīng)BeforeWaiting(即將進(jìn)入休眠)和Exit(即將退出Loop)事件蔚出,并回調(diào)執(zhí)行當(dāng)前繪制任務(wù)(setNeedsDisplay->display->displayLayer)弟翘,最終更新界面。

由上可知骄酗,我們可以模擬系統(tǒng)繪制任務(wù)的收集稀余,在runloop回調(diào)中去執(zhí)行,并重寫layer的dispaly方法趋翻,開(kāi)辟子線程進(jìn)行異步繪制睛琳,再返回主線程刷新。

當(dāng)同個(gè)UI多次觸發(fā)繪制請(qǐng)求時(shí),怎樣減少重復(fù)繪制师骗,以便減輕并發(fā)壓力比較重要历等。本案通過(guò)維護(hù)一個(gè)全局線程安全的原子性狀態(tài),在繪制過(guò)程中的關(guān)鍵步驟處理前均校驗(yàn)是否要放棄當(dāng)前多余的繪制任務(wù)辟癌。

2)隊(duì)列的并發(fā)和擇優(yōu)

一次runloop回調(diào)寒屯,經(jīng)常會(huì)執(zhí)行多個(gè)繪制任務(wù),這里考慮開(kāi)辟多個(gè)線程去異步執(zhí)行黍少。首選并行隊(duì)列可以滿足寡夹,但為了滿足性能效率的同時(shí)確保不過(guò)多的占用資源和避免線程間競(jìng)爭(zhēng)等待,更好的方案應(yīng)該是開(kāi)辟多個(gè)串行隊(duì)列單線程處理并發(fā)任務(wù)厂置。

接下來(lái)的問(wèn)題是要出,異步繪制創(chuàng)建幾個(gè)串行隊(duì)列合適?

我們知道一個(gè)n核設(shè)備农渊,并發(fā)執(zhí)行n個(gè)任務(wù)患蹂,最多創(chuàng)建n個(gè)線程時(shí),線程之間將不會(huì)互相競(jìng)爭(zhēng)資源砸紊。因此传于,不建議數(shù)量設(shè)置超過(guò)當(dāng)前激活的處理器數(shù),并可根據(jù)項(xiàng)目界面復(fù)雜度以及設(shè)備性能適配醉顽,適當(dāng)限制并發(fā)開(kāi)銷沼溜,文本異步繪制最大隊(duì)列數(shù)設(shè)置如下:

#definekMAX_QUEUE_COUNT6

- (NSUInteger)limitQueueCount {

if(_limitQueueCount ==0) {

// 獲取當(dāng)前系統(tǒng)處于激活狀態(tài)的處理器數(shù)量

NSUInteger processorCount = [NSProcessInfo processInfo].activeProcessorCount;

// 根據(jù)處理器的數(shù)量和設(shè)置的最大隊(duì)列數(shù)來(lái)設(shè)定當(dāng)前隊(duì)列數(shù)組的大小

_limitQueueCount = processorCount >0? (processorCount > kMAX_QUEUE_COUNT ? kMAX_QUEUE_COUNT : processorCount) :1;

}

return_limitQueueCount;

}

文本的異步繪制串行隊(duì)列用GCD實(shí)現(xiàn),圖片異步下載通過(guò)NSOperationQueue實(shí)現(xiàn)游添,兩者最大并發(fā)數(shù)參考SDWebImage圖片下載并發(fā)數(shù)的限制數(shù):6系草。

如何擇優(yōu)選取執(zhí)行任務(wù)?文本異步隊(duì)列的選取唆涝,可以自定義隊(duì)列的任務(wù)數(shù)標(biāo)記找都,在隊(duì)列執(zhí)行任務(wù)前計(jì)算+1,當(dāng)任務(wù)執(zhí)行結(jié)束計(jì)算-1廊酣。這里忽略每次繪制難易度的略微差異能耻,我們便可以判定任務(wù)數(shù)最少接近于最優(yōu)隊(duì)列。圖片異步下載任務(wù)亡驰,交由NSOperationQueue處理并發(fā)晓猛,我們要處理的是,讓同個(gè)圖片在多次并發(fā)下載請(qǐng)求下凡辱,僅生成1個(gè)NSOperation添加到queue戒职,即去重只下載一次并緩存,且在下載完成后返回主線程同步渲染多個(gè)觸發(fā)該下載請(qǐng)求的控件(本案demo僅用一張圖片透乾,所以這種情況必須考慮到)洪燥。

三磕秤、詳細(xì)設(shè)計(jì)

1、設(shè)計(jì)圖

2蚓曼、代碼原理剖析(寫在注釋)

1)設(shè)置runloop監(jiān)聽(tīng)及回調(diào)

/**

runloop回調(diào)亲澡,并發(fā)執(zhí)行異步繪制任務(wù)

*/

staticNSMutableSet *_taskSet =nil;

staticvoidADRunLoopCallBack(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity,void*info) {

if(_taskSet.count==0)return;

NSSet*currentSet = _taskSet;

_taskSet = [NSMutableSetset];

[currentSet enumerateObjectsUsingBlock:^(ADTask *task,BOOL*stop) {

[task excute];

}];

}

/** task調(diào)用函數(shù)

- (void)excute {

((void (*)(id, SEL))[self.target methodForSelector:self.selector])(self.target, self.selector);

}

*/

- (void)setupRunLoopObserver {

// 創(chuàng)建任務(wù)集合

_taskSet = [NSMutableSetset];

// 獲取主線程的runloop

CFRunLoopRefrunloop =CFRunLoopGetMain();

// 創(chuàng)建觀察者钦扭,監(jiān)聽(tīng)即將休眠和退出

CFRunLoopObserverRefobserver =CFRunLoopObserverCreate(CFAllocatorGetDefault(),

kCFRunLoopBeforeWaiting| kCFRunLoopExit,

true,// 重復(fù)

0xFFFFFF,// 設(shè)置優(yōu)先級(jí)低于CATransaction(2000000)

ADRunLoopCallBack,NULL);

CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);

CFRelease(observer);

2)創(chuàng)建纫版、獲取文本異步繪制隊(duì)列,并擇優(yōu)選取

- (ADQueue *)ad_getExecuteTaskQueue {

// 1客情、創(chuàng)建對(duì)應(yīng)數(shù)量串行隊(duì)列處理并發(fā)任務(wù)其弊,并行隊(duì)列線程數(shù)無(wú)法控制

if(self.queueArr.count

ADQueue *q = [[ADQueue alloc] init];

q.index=self.queueArr.count;

[self.queueArraddObject:q];

q.asyncCount+=1;

NSLog(@"queue[%ld]-asyncCount:%ld", (long)q.index, (long)q.asyncCount);

returnq;

}

// 2、當(dāng)隊(duì)列數(shù)已達(dá)上限膀斋,擇優(yōu)獲取異步任務(wù)數(shù)最少的隊(duì)列

NSUIntegerminAsync = [[self.queueArrvalueForKeyPath:@"@min.asyncCount"] integerValue];

__block ADQueue *q =nil;

[self.queueArrenumerateObjectsUsingBlock:^(ADQueue * _Nonnull obj,NSUIntegeridx,BOOL* _Nonnull stop) {

if(obj.asyncCount<= minAsync) {

*stop =YES;

q = obj;

}

}];

q.asyncCount+=1;

NSLog(@"queue[%ld]-excute-count:%ld", (long)q.index, (long)q.asyncCount);

returnq;

}

- (void)ad_finishTask:(ADQueue *)q {

q.asyncCount-=1;

if(q.asyncCount<0) {

q.asyncCount=0;

}

NSLog(@"queue[%ld]-done-count:%ld", (long)q.index, (long)q.asyncCount);

}

3)異步繪制

/**

維護(hù)線程安全的繪制狀態(tài)

*/

@property(atomic,assign) ADLayerStatus status;

- (void)setNeedsDisplay {

// 收到新的繪制請(qǐng)求時(shí)梭伐,同步正在繪制的線程本次取消

self.status= ADLayerStatusCancel;

[supersetNeedsDisplay];

}

- (void)display {

// 標(biāo)記正在繪制

self.status= ADLayerStatusDrawing;

if([self.delegaterespondsToSelector:@selector(asyncDrawLayer:inContext:canceled:)]) {

[selfasyncDraw];

}else{

[superdisplay];

}

}

- (void)asyncDraw {

__block ADQueue *q = [[ADManager shareInstance] ad_getExecuteTaskQueue];

__blockid delegate = (id)self.delegate;

dispatch_async(q.queue, ^{

// 重繪取消

if([selfcanceled]) {

[[ADManager shareInstance] ad_finishTask:q];

return;

}

// 生成上下文context

CGSizesize =self.bounds.size;

BOOLopaque =self.opaque;

CGFloatscale = [UIScreenmainScreen].scale;

CGColorRefbackgroundColor = (opaque &&self.backgroundColor) ?CGColorRetain(self.backgroundColor) :NULL;

UIGraphicsBeginImageContextWithOptions(size, opaque, scale);

CGContextRefcontext =UIGraphicsGetCurrentContext();

if(opaque && context) {

CGContextSaveGState(context); {

if(!backgroundColor ||CGColorGetAlpha(backgroundColor) <1) {

CGContextSetFillColorWithColor(context, [UIColorwhiteColor].CGColor);

CGContextAddRect(context,CGRectMake(0,0, size.width* scale, size.height* scale));

CGContextFillPath(context);

}

if(backgroundColor) {

CGContextSetFillColorWithColor(context, backgroundColor);

CGContextAddRect(context,CGRectMake(0,0, size.width* scale, size.height* scale));

CGContextFillPath(context);

}

}CGContextRestoreGState(context);

CGColorRelease(backgroundColor);

}else{CGColorRelease(backgroundColor);

}// 使用context繪制

[delegate asyncDrawLayer:selfinContext:context canceled:[selfcanceled]];

// 重繪取消

if([selfcanceled]) {

[[ADManager shareInstance] ad_finishTask:q];

UIGraphicsEndImageContext();

return;

}

// 獲取image

UIImage*image =UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

// 結(jié)束任務(wù)

[[ADManager shareInstance] ad_finishTask:q];

// 重繪取消

if([selfcanceled]) {

return;

}

// 主線程刷新

dispatch_async(dispatch_get_main_queue(), ^{

self.contents= (__bridgeid)(image.CGImage);

});

});

}

4)異步下載緩存圖片

#pragma mark - 處理圖片

- (void)ad_setImageWithURL:(NSURL*)url target:(id)target completed:(void(^)(UIImage* _Nullable image,NSError* _Nullable error))completedBlock {

if(!url) {

if(completedBlock) {

NSDictionary*userInfo = @{NSLocalizedFailureReasonErrorKey:NSLocalizedStringFromTable(@"Expected URL to be a image URL",@"AsyncDraw",nil)};

NSError*error = [[NSErroralloc] initWithDomain:kERROR_DOMAIN code:NSURLErrorBadURLuserInfo:userInfo];

completedBlock(nil, error);

}

return;

}

// 1、緩存中讀取

NSString*imageKey = url.absoluteString;

NSData*imageData =self.imageDataDict[imageKey];

if(imageData) {

UIImage*image = [UIImageimageWithData:imageData];

if(completedBlock) {

completedBlock(image,nil);

}

}else{

// 2仰担、沙盒中讀取

NSString*imagePath = [NSStringstringWithFormat:@"%@/Library/Caches/%@",NSHomeDirectory(), url.lastPathComponent];

imageData = [NSDatadataWithContentsOfFile:imagePath];

if(imageData) {

UIImage*image = [UIImageimageWithData:imageData];

if(completedBlock) {

completedBlock(image,nil);

}

}else{

// 3糊识、下載并緩存寫入沙盒

ADOperation *operation = [selfad_downloadImageWithURL:url toPath:imagePath completed:completedBlock];

// 4、添加圖片渲染對(duì)象

[operation addTarget:target];

}

}

}

- (ADOperation *)ad_downloadImageWithURL:(NSURL*)url toPath:(NSString*)imagePath completed:(void(^)(UIImage* _Nullable image,NSError* _Nullable error))completedBlock? {

NSString*imageKey = url.absoluteString;

ADOperation *operation =self.operationDict[imageKey];

if(!operation) {

operation = [ADOperation blockOperationWithBlock:^{

NSLog(@"AsyncDraw image loading~");

NSData*newImageData = [NSDatadataWithContentsOfURL:url];

// 下載失敗處理

if(!newImageData) {

[self.operationDictremoveObjectForKey:imageKey];

NSDictionary*userInfo = @{NSLocalizedFailureReasonErrorKey:NSLocalizedStringFromTable(@"Failed to load the image",@"AsyncDraw",nil)};

NSError*error = [[NSErroralloc] initWithDomain:kERROR_DOMAIN code:NSURLErrorUnknownuserInfo:userInfo];

if(completedBlock) {

completedBlock(nil, error);

}

return;

}

// 緩存圖片數(shù)據(jù)

[self.imageDataDictsetValue:newImageData forKey:imageKey];

}];

// 設(shè)置完成回調(diào)

__block ADOperation *blockOperation = operation;

[operation setCompletionBlock:^{

NSLog(@"AsyncDraw image load completed~");

// 取緩存

NSData*newImageData =self.imageDataDict[imageKey];

if(!newImageData) {

return;

}

// 返回主線程刷新

[[NSOperationQueuemainQueue] addOperationWithBlock:^{

UIImage*newImage = [UIImageimageWithData:newImageData];

// 遍歷渲染同個(gè)圖片地址的所有控件

[blockOperation.targetSetenumerateObjectsUsingBlock:^(id_Nonnull obj,BOOL* _Nonnull stop) {

if([obj isKindOfClass:[UIImageViewclass]]) {

UIImageView*imageView = (UIImageView*)obj;

// ADImageView內(nèi)部判斷“超出可視范圍摔蓝,放棄渲染~”

imageView.image= newImage;

}

}];

[blockOperation removeAllTargets];

}];

// 寫入沙盒

[newImageData writeToFile:imagePath atomically:YES];

// 移除任務(wù)

[self.operationDictremoveObjectForKey:imageKey];

}];

// 加入隊(duì)列

[self.operationQueueaddOperation:operation];

// 添加opertion

[self.operationDictsetValue:operation forKey:imageKey];

}

returnoperation;

}

四赂苗、使用示例

1)文本異步繪制

@implementationADLabel

#pragma mark - Pub MD

- (void)setText:(NSString*)text {

_text = text;

[[ADManager shareInstance] addTaskWith:selfselector:@selector(asyncDraw)];

}

// 綁定異步繪制layer

+ (Class)layerClass {

returnADLayer.class;

}

#pragma mark - Pri MD

- (void)asyncDraw {

[self.layersetNeedsDisplay];

}

#pragma mark - ADLayerDelegate

- (void)layerWillDraw:(CALayer*)layer {

}

- (void)asyncDrawLayer:(ADLayer *)layer inContext:(CGContextRef__nullable)ctx canceled:(BOOL)canceled {

if(canceled) {

NSLog(@"異步繪制取消~");

return;

}

UIColor*backgroundColor = _backgroundColor;

NSString*text = _text;

UIFont*font = _font;

UIColor*textColor = _textColor;

CGSizesize = layer.bounds.size;

CGContextSetTextMatrix(ctx,CGAffineTransformIdentity);

CGContextTranslateCTM(ctx,0, size.height);

CGContextScaleCTM(ctx,1, -1);

// 繪制區(qū)域

CGMutablePathRefpath =CGPathCreateMutable();

CGPathAddRect(path,NULL,CGRectMake(0,0, size.width, size.height));

// 繪制的內(nèi)容屬性字符串

NSDictionary*attributes = @{NSFontAttributeName: font,

NSForegroundColorAttributeName: textColor,

NSBackgroundColorAttributeName: backgroundColor,

NSParagraphStyleAttributeName:self.paragraphStyle?:[NSParagraphStylenew]

};

NSMutableAttributedString*attrStr = [[NSMutableAttributedStringalloc] initWithString:text attributes:attributes];

// 使用NSMutableAttributedString創(chuàng)建CTFrame

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);

CTFrameRef frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0, attrStr.length), path,NULL);

CFRelease(framesetter);

CGPathRelease(path);

// 使用CTFrame在CGContextRef上下文上繪制

CTFrameDraw(frame, ctx);

CFRelease(frame);

}

2)圖片異步下載渲染

@implementationADImageView

#pragma mark - Public Methods

- (void)setUrl:(NSString*)url {

_url = url;

[[ADManager shareInstance] ad_setImageWithURL:[NSURLURLWithString:self.url] target:selfcompleted:^(UIImage* _Nullable image,NSError* _Nullable error) {

if(image) {

self.image= image;

}

}];

}

五、成效舉證

針對(duì)本案制作了AsyncDrawDemo贮尉,是一個(gè)圖文排列布局的UITableView列表拌滋,類似新聞列表,TestTableViewCell.m中有異步繪制和圖片異步下載渲染開(kāi)關(guān)

#define kAsyncDraw true// 異步開(kāi)關(guān)

//#define kOnlyShowText true // 僅顯示文本進(jìn)行測(cè)試

kAsyncDraw開(kāi)啟前后測(cè)試對(duì)比清單:

? 同樣加載1000條數(shù)據(jù)的列表

? 動(dòng)態(tài)文本緩存高度

? 同一設(shè)備:真機(jī)iPhone11 iOS13.5.1

? 操作:列表首次加載完成猜谚,幀率顯示60fps后败砂,快速向上滑動(dòng)至底部

本案通過(guò)YYFPSLabel觀察幀率大致均值變化,以及內(nèi)存/CPU變化截圖如下:

1)未開(kāi)啟異步前:

穩(wěn)定60fps后開(kāi)始快速滑動(dòng)至列表底部的前后對(duì)比(幀率最低到1fps魏铅,滑動(dòng)過(guò)程異巢蹋卡頓,cpu未超過(guò)40%览芳,內(nèi)存占用也不多祭隔,但非常耗電):

2)開(kāi)啟異步后:

穩(wěn)定60fps后開(kāi)始快速滑動(dòng)至列表底部的前后對(duì)比(幀率穩(wěn)定在60fps,滑動(dòng)過(guò)程非常流暢路操,cpu最高超過(guò)90%疾渴,內(nèi)存占用到達(dá)200MB,耗電型驼獭)

通過(guò)以上對(duì)比得出的結(jié)論是:未開(kāi)啟“異步繪制和異步下載渲染”搞坝,雖然cpu、內(nèi)存未見(jiàn)異常魁袜,但列表滑動(dòng)卡頓桩撮,非常耗電敦第;開(kāi)啟后,雖然內(nèi)存占用翻倍店量、cpu也達(dá)到過(guò)90%芜果,但相對(duì)于4G內(nèi)存和6核CPU的iPhone11來(lái)說(shuō)影響不大,流暢性和耗電得到保障融师。由此得出結(jié)論右钾,UITableView性能優(yōu)化的關(guān)鍵在于“系統(tǒng)資源充分滿足調(diào)配的前提下,能異步的盡量異步”旱爆,否則主線程壓力大引起卡頓舀射,丟幀和耗電在所難免。

補(bǔ)充說(shuō)明:當(dāng)打開(kāi)kOnlyShowText開(kāi)關(guān)怀伦,僅顯示文本內(nèi)容進(jìn)行測(cè)試時(shí)脆烟,在未打開(kāi)kAsyncDraw開(kāi)關(guān)前快速滑動(dòng)列表,幀率出現(xiàn)40~50fps房待,可感知快速滑動(dòng)下并不流暢邢羔。雖然UITableView性能優(yōu)化主要體現(xiàn)在大圖異步下載渲染的優(yōu)化,文本高度的緩存對(duì)于多核CPU設(shè)備性能提升效果確實(shí)不明顯桑孩,但文本異步繪制則讓性能更上一層拜鹤。

六、核心代碼范圍

DEMO地址:https://github.com/stkusegithub/AsyncDraw

代碼位于目錄 AsyncDrawDemo/AsyncDrawDemo/Core/下

\---AsyncDraw

+---ADManager.h

+---ADManager.m

+---ADLayer.h

+---ADLayer.m

+---ADTask.h

+---ADTask.m

+---ADQueue.h

+---ADQueue.m

+---ADOperation.h

+---ADOperation.m

\---AsyncUI

+---ADLabel.h

+---ADLabel.m

+---ADImageView.h

+---ADImageView.m

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末洼怔,一起剝皮案震驚了整個(gè)濱河市署惯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镣隶,老刑警劉巖极谊,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異安岂,居然都是意外死亡轻猖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門域那,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咙边,“玉大人,你說(shuō)我怎么就攤上這事次员“苄恚” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵淑蔚,是天一觀的道長(zhǎng)市殷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)刹衫,這世上最難降的妖魔是什么醋寝? 我笑而不...
    開(kāi)封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任搞挣,我火速辦了婚禮,結(jié)果婚禮上音羞,老公的妹妹穿的比我還像新娘囱桨。我一直安慰自己,他們只是感情好嗅绰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布舍肠。 她就那樣靜靜地躺著,像睡著了一般办陷。 火紅的嫁衣襯著肌膚如雪貌夕。 梳的紋絲不亂的頭發(fā)上律歼,一...
    開(kāi)封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天民镜,我揣著相機(jī)與錄音,去河邊找鬼险毁。 笑死制圈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的畔况。 我是一名探鬼主播鲸鹦,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼跷跪!你這毒婦竟也來(lái)了馋嗜?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吵瞻,失蹤者是張志新(化名)和其女友劉穎葛菇,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體橡羞,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眯停,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卿泽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片莺债。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖签夭,靈堂內(nèi)的尸體忽然破棺而出齐邦,到底是詐尸還是另有隱情,我是刑警寧澤第租,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布措拇,位于F島的核電站,受9級(jí)特大地震影響煌妈,放射性物質(zhì)發(fā)生泄漏儡羔。R本人自食惡果不足惜宣羊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汰蜘。 院中可真熱鬧仇冯,春花似錦、人聲如沸族操。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)色难。三九已至泼舱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間枷莉,已是汗流浹背娇昙。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笤妙,地道東北人冒掌。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蹲盘,于是被迫代替她去往敵國(guó)和親股毫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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