iOS 定時器(NSTimer硕舆、dispatch_source_t和CADisplayLink)

本文約100行代碼秽荞,讀完大概用時5-10分鐘,理解的話看個人知識掌握程度抚官。

App在開發(fā)的過程中扬跋,經常會遇到倒計時等等與時間計算有關的需求,這時就需要我們去使用定時器了凌节,本篇我們就來盤點盤點iOS中的三大定時器:NSTimer钦听、dispatch_source_t和CADisplayLink。

一倍奢、NSTimer

1.NSTimer的介紹

NSTimer應該是新手最耳熟能詳?shù)亩〞r器了朴上,通過Apple開發(fā)文檔的描述 A timer that fires after a certain time interval has elapsed, sending a specified message to a target object. 我們可以看到它是通過間隔一定的時間,向目標對象發(fā)送指定的消息(OC中調用方法在底層就是發(fā)送消息)來實現(xiàn)定時器的功能的卒煞。NSTimer在使用的過程中其實是有很多小細節(jié)需要注意的痪宰,下面都會講到。

2.NSTimer的方法

NSTimer有3個timerWith類方法(初始化):

/// @param ti 定時器的時間間隔
/// @param invocation 方法調用
/// @param yesOrNo 是否重復執(zhí)行
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
/// @param ti 定時器的時間間隔
/// @param aTarget 目標對象(一般是self)
/// @param aSelector 方法調用
/// @param yesOrNo 是否重復執(zhí)行
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
/// @param interval 定時器的時間間隔
/// @param repeats 是否重復執(zhí)行
/// @param block 方法調用(代碼塊的形式)
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;

這3種類方法需要你手動將timer對象添加到runloop中畔裕;

// 在主runloop上添加
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];

和3個scheduledTimer類方法(初始化):

/// @param ti 定時器的時間間隔
/// @param invocation 方法調用
/// @param yesOrNo 是否重復執(zhí)行
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
/// @param ti 定時器的時間間隔
/// @param aTarget 目標對象(一般是self)
/// @param aSelector 方法調用
/// @param yesOrNo 是否重復執(zhí)行
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
/// @param interval 定時器的時間間隔
/// @param repeats 是否重復執(zhí)行
/// @param block 方法調用(代碼塊的形式)
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;

這3種類方法需要會自動將timer對象添加到當前runloop(默認是主runloop)中衣撬,并且mode為NSDefaultRunLoopMode;

以及2個initWith實例方法(初始化):

/// 需要手動將timer對象添加到runloop中
/// @param date 開始執(zhí)行的日期
/// @param interval 定時器的時間間隔
/// @param repeats 是否重復執(zhí)行
/// @param block 方法調用(代碼塊的形式)
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
/// 需要手動將timer對象添加到runloop中
/// @param date 開始執(zhí)行的日期
/// @param ti 定時器的時間間隔
/// @param t 目標對象(一般是self)
/// @param s 方法調用
/// @param ui 可自定義的參數(shù)
/// @param rep 是否重復執(zhí)行
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep;

以上8個初始化方法扮饶,scheduledTimer方法會自動添加timer到當前runloop具练,其他則不會

8個初始化方法的區(qū)別圖示

使用timerWithinitWith 時需要手動添加timer到runloop

2個常用的實例方法:

// 開始執(zhí)行
- (void)fire;
// 銷毀
- (void)invalidate;

fire會立即調用方法,在執(zhí)行完后甜无,如果不是重復的timer扛点,會立即 invalidate ;
invalidate會停止重復的timer(不重復的執(zhí)行一次后會自動invalidate)岂丘,并將其從runloop中remove陵究。

3.NSTimer的使用示例

3.1 在一個普通的VC中使用:

@property (nonatomic, strong) NSTimer *timer; //作為屬性一般使用strong修飾,因為timer是一個對象元潘,需要被持有者強引用以防提前釋放

// 這里使用weakSelf來避免循環(huán)引用從而導致內存泄漏
__weak typeof(self) weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
   [weakSelf changeLabelText];
}];

值得注意的是畔乙,當我們使用 target: selector: 的方式時,target后面使用weakSelf 并不能避免循環(huán)引用翩概,此時timer依然會對self進行強引用牲距,會導致內存泄漏,下面的代碼是錯誤的:

_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(changeLabelText) userInfo:nil repeats:YES];

如果此時頁面上有scrollView或者tableView等在滑動時钥庇,需要手動更改timer的mode:

/* 當scrollView滾動的時候牍鞠,當前的 MainRunLoop 會處于 UITrackingRunLoopMode 的模式下,
在這個模式下评姨,是不會處理 NSDefaultRunLoopMode 的任務的 */
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];

3.2 立即執(zhí)行timer的方法調用:

// 執(zhí)行fire方法后难述,會立即執(zhí)行本來需要時間間隔后才執(zhí)行的指定方法調用
// 對于重復的timer萤晴,它是一次額外的操作,并且不會打破正常的schedule
// 對于不重復的timer胁后,它一觸發(fā)完后店读,timer就被invalidate,就不管原來設定的時間間隔了
[_timer fire];

3.3 timer的釋放與銷毀:

if ([_timer isValid]) {
    [_timer invalidate]; //對于不重復的timer可以不寫攀芯,因為不重復的timer在執(zhí)行完后就自動invalidate了
    _timer = nil;
}

PS:看到很多文章說timer的銷毀不能放在dealloc中屯断,要放在- (void)viewDidDisappear:(BOOL)animated中,因為在dealloc中并不會去執(zhí)行侣诺。這種說法一般是不對的殖演,首先,dealloc中不會去執(zhí)行大概率是出現(xiàn)了循環(huán)引用年鸳,此時VC仍然被timer強引用趴久,導致VC沒法dealloc,那么timer當然不會去執(zhí)行銷毀搔确;其次彼棍,viewDidDisappear時去銷毀那么你在跳往下一級頁面而不是返回上一級頁面的時候,此時當前頁面一般是要繼續(xù)存在的妥箕,這么做就將當前頁面的timer銷毀了滥酥,肯定是不對的,正確的做法是使用上面timer的block API + weakSelf來避免循環(huán)使用畦幢。

4.NSTimer的更多使用技巧

4.1 暫停和啟動:

// 讓timer的fire時間為“遙遠的未來”,那么它就“暫屠虏酰”了
[_timer setFireDate:[NSDate distantFuture]];

// 讓timer的fire時間為“馬上”或“遠古”宇葱,那么它就啟動了
[self.timer setFireDate:[NSDate date]];
[self.timer setFireDate:[NSDate distantPast]];

4.2 非固定時間間隔執(zhí)行timer

- (void)randomTimeTimer {
    // 此處先將timer的timeInterval設置為無窮大,這樣它便不會執(zhí)行
    _timer =  [NSTimer timerWithTimeInterval:MAXFLOAT target:self selector:@selector(randomTimeFireMethod) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer: _timer forMode:NSDefaultRunLoopMode];
}

- (void)randomTimeFireMethod {
    static int timeExecute = 0;
    
    // 這里的4是你想要timer執(zhí)行的次數(shù)
    if (timeExecute < 4) {
        // 不定長執(zhí)行
        NSTimeInterval timeInterval = [self.randomTime[timeExecute] doubleValue];
        timeExecute++;
        // 使用fireDate來控制timer以達到不定長執(zhí)行
        _timer.fireDate = [NSDate dateWithTimeIntervalSinceNow:timeInterval];
    }
}

OC中并沒有NSTimer的暫停刊头、啟動和非固定時間間隔的方法黍瞧,我們可以使用這種奇思妙想來達到這個目的。

4.3 在子線程中使用NSTimer

5.NSTimer的注意事項

5.1 為什么NSTimer的銷毀需要invalidate + =nil 原杂?
在OC中印颤,一般我們銷毀強引用,會直接使用 =nil 穿肄,但是NSTimer不可以年局。我們來看看ARC中的NSTimer創(chuàng)建到銷毀的過程中具體的引用關系變化:

  • VC創(chuàng)建NSTimer后,此時VC對timer強引用咸产,再之后timer加入到runloop后矢否,系統(tǒng)也會強引用timer


    NSTimer的創(chuàng)建過程
  • =nil 后,VC解除了對timer的強引用脑溢,但此時系統(tǒng)依然對timer有強引用
    =nil
  • 調用 invalidate 方法后僵朗,系統(tǒng)解除對timer的強引用
    invalidate后

    綜合以上,我們需要對timer invalidate + =nil ,才能真正的銷毀NSTimer验庙。

5.2 NSTimer不是實時的 / NSTimer可以設置Tolerance(容忍度)顶吮。

  • NSTimer加入的runloop正好處在一個耗時的周期內;
  • NSTimer添加的runloopMode不是當前runloop所處的mode時粪薛,如NSDefaultRunLoopMode的NSTimer在頁面滑動時暫停云矫;
  • Tolerance大概是避免NSTimer在runloop中的不實時帶來的時間偏移的(實際開發(fā)中使用較少,暫時沒怎么研究)汗菜。

二让禀、dispatch_source_t

1. dispatch_source_t的介紹

dispatch_source_t 是眾多DISPATCH_SOURCE種類的一種
針對NSTimer受runloop的影響而不精準的問題,dispatch_source_t是一種相對精準的計時器陨界,并且它天生就可以使用GCD在子線程中執(zhí)行巡揍,解決NSTimer在主線程中導致卡頓的問題,但是它的缺點也比較明顯菌瘪,就是代碼量相對比較多一點腮敌。

2. dispatch_source_t的使用

定義屬性:

// 此處也用strong修飾,雖然沒有 * 俏扩,但是dispatch_source_t也是對象糜工,和普通的對象一樣,strong防止提前釋放
@property (nonatomic, strong) dispatch_source_t timer;

初始化和設定:

- (void)initTimer {
    if (!_gcdTimer) {
        // 創(chuàng)建隊列
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        // 初始化timer(設定source_type录淡,以及隊列)
        _gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        // 設定timer的開始時間
        dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC));
        // 如果timer的間隔時間比較大捌木,那么可以使用dispatch_walltime來創(chuàng)建start,可以避免誤差
        dispatch_time_t start_0 = dispatch_walltime(0, 0);
        // 設定timer的固定時間間隔
        uint64_t interval = (uint64_t)(1 * NSEC_PER_SEC);
        // 設置timer嫉戚,最后一個參數(shù)為leeway刨裆,是用來設置定時器的“期望精度值”,系統(tǒng)會根據這個值延遲或提前觸發(fā)定時器
        dispatch_source_set_timer(_gcdTimer, start, interval, 0);
        // 設定timer的方法調用
        dispatch_source_set_event_handler(_gcdTimer, ^{
            // 如果timer的方法調用是UI方面相關的操作彬檀,需要在主線程中執(zhí)行(線程間通信)
            dispatch_async(dispatch_get_main_queue(), ^{
                [self changeLabelText];
            });
        });
        // 開啟定時器
        dispatch_resume(_gcdTimer);
    }
}

dispatch_source_create方法參數(shù)詳細說明

  • 第一個參數(shù):dispatch_source_type_t type為設置GCD源方法的類型帆啃,前面已經列舉過了。
  • 第二個參數(shù):uintptr_t handle Apple的API介紹說窍帝,暫時沒有使用努潘,傳0即可。
  • 第三個參數(shù):unsigned long mask Apple的API介紹說坤学,使用DISPATCH_TIMER_STRICT疯坤,會引起電量消耗加劇,畢竟要求精確時間拥峦,所以一般傳0即可贴膘,視業(yè)務情況而定。
  • 第四個參數(shù):dispatch_queue_t _Nullable queue 隊列略号,將定時器事件處理的Block提交到哪個隊列,可以傳Null刑峡,默認為全局隊列洋闽。

開啟定時器:

dispatch_resume(_gcdTimer);

暫停定時器:

// 暫停
- (void)pauseTimer {
    if (_gcdTimer) {
        dispatch_suspend(_gcdTimer);
    }
}

// 暫停后的重新開啟
dispatch_resume(_gcdTimer);

銷毀定時器:

dispatch_source_cancel(_gcdTimer);
_gcdTimer = nil;
3. dispatch_source_t的注意事項

timer被dispatch_suspend后是不能釋放的,否則會引起崩潰突梦。因為OC中并沒有dispatch_source_t的暫停和開啟狀態(tài)的記錄诫舅,所以如果我們用到了它的暫停和開啟,則我們必須手動記錄宫患,有dispatch_suspend則必有dispatch_resume刊懈。

4. dispatch_source_t的優(yōu)缺點

優(yōu)點:

  • 性能更好,相對更精確;
  • 自帶暫停娃闲、繼續(xù);
  • 天生適合在子線程中使用虚汛;
  • 不需要加入到runloop中,也不需要管runloop的mode皇帮。

缺點:

  • 每次dispatch_resume都會先執(zhí)行一次卷哩;
  • 本質上也不是完全精確;
  • 代碼量較多属拾。

三将谊、CADisplayLink

1. CADisplayLink的介紹

CADisplayLink是OC中精度最高的定時器,它是根據設備的屏幕刷新頻率來執(zhí)行操作渐白,因此它的使用場景也相對當一尊浓,比較適合用來做UI的繪制、自定義的動畫引擎以及視頻播放的渲染纯衍。

2. CADisplayLink的方法和相關屬性

1個初始化類方法:

/// @param target 目標對象(一般是self)
/// @param sel 方法調用
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;

3個實例方法

/// 將CADisplayLink對象添加到runloop中并指定mode
/// @param runloop 加入的runloop
/// @param mode 指定runloopMode
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
/// 將CADisplayLink對象從runloop指定的mode中移除
/// @param runloop 被移除CADisplayLink對象的runloop
/// @param mode 指定的runloopMode
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
// 將CADisplayLink對象從runloop所有mode中移除
- (void)invalidate;

兩個移除方法的區(qū)別
removeFromRunLoop會將其從指定的runloop的指定mode中移除栋齿,此方法在runloop或者mode任一不匹配的情況下都無效,而且remove時需要進行判斷托酸,如果指定的mode中不存在褒颈,那么將會引起crash,原因是重復over-release励堡;
invalidate是從runloop的所有模式中移除,并取消和target的關聯(lián)關系堡掏,此方法可以多次調用应结,不會引起crash。

3個只讀屬性:

// 當前屏幕上顯示幀率的時間戳
@property(readonly, nonatomic) CFTimeInterval timestamp;
// 定時器的時間間隔
@property(readonly, nonatomic) CFTimeInterval duration;
// 客戶端針對其渲染的下一個時間戳
@property(readonly, nonatomic) CFTimeInterval targetTimestamp;

3個讀寫屬性:

// 是否暫停泉唁,設置了暫停后定時器將暫停鹅龄,直到設置為false的時候再執(zhí)行
@property(getter=isPaused, nonatomic) BOOL paused;
// 從iOS10開始已廢棄,不要去使用
@property(nonatomic) NSInteger frameInterval;
// 每秒刷新次數(shù)(幀率)
@property(nonatomic) NSInteger preferredFramesPerSecond;
3. CADisplayLink使用示例
// 創(chuàng)建
- (void)initDisplaylink {
    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeLabelText)];
    _displayLink.preferredFramesPerSecond = 0; //每秒刷新次數(shù)亭畜,設置為0時就是默認屏幕的最大刷新幀率
    [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
// 銷毀
[_displayLink invalidate];
_displayLink = nil;
4. CADisplayLink的特性
  • CADisplayLink不能夠繼承
  • 修改幀率
    CADisplayLink的實際幀率是由屏幕最大幀率(maximumFramesPerSecond)和參數(shù)preferredFramesPerSecond一起決定的扮休,規(guī)則為:如果屏幕最大幀率是60,實際幀率只能是15拴鸵、20玷坠、30蜗搔、60中的一種;如果設置大于60的值八堡,屏幕實際幀率為60樟凄。如果設置的是26~35之間的值,實際幀率是30兄渺;如果設置為0缝龄,會使用最高幀率。
  • 在添加進runloop時應當選擇高優(yōu)先級的挂谍,以保證動畫的流暢
5. CADisplayLink防止循環(huán)引用

上面NSTimer在防止循環(huán)引用時使用了NSTimer本身提供的block方法而非傳入target的方式叔壤,但是CADisplayLink本身沒有提供block方法,只有傳入target的方式口叙,那么我們怎么避免循環(huán)引用呢炼绘?
首先我們來看一種錯誤的做法:

__weak typeof(self) weakSelf = self;
_displayLink = [CADisplayLink displayLinkWithTarget:weakSelf selector:@selector(changeLabelText:)];
_displayLink.preferredFramesPerSecond = 10;
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

然后再看另一種錯誤的做法:

// 將displayLink屬性聲明為weak
@property (nonatomic, weak) CADisplayLink *displayLink;

// 初始化
CADisplayLink *temp = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeLabelText:)];
temp.preferredFramesPerSecond = 10;
[temp addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
_displayLink = temp;

以上兩種方法都是錯誤的,在頁面銷毀時庐扫,它們無一例外的定時器都沒有被銷毀饭望,依然在工作,原因在于此時runloop對定時器依然有強引用形庭。

此時正確的做法有兩種:
1.使用NSProxy
創(chuàng)建一個繼承自NSProxy的新類GQProxy

// .h
@interface GQProxy : NSProxy

@property (weak, nonatomic) id target;

+ (instancetype)proxyWithTarget:(id)target;

@end
// .m
#import "GQProxy.h"

@implementation GQProxy

+ (instancetype)proxyWithTarget:(id)target {
    GQProxy *proxy = [GQProxy alloc];
    proxy.target = target;
    return proxy;
}

//返回方法簽名
-(NSMethodSignature*)methodSignatureForSelector:(SEL)sel{
    
    return [self.target methodSignatureForSelector:sel];
}

-(void)forwardInvocation:(NSInvocation *)invocation{
    
    [invocation invokeWithTarget:self.target];
}

@end

在初始化定時器時

_displayLink = [CADisplayLink displayLinkWithTarget:[GQProxy proxyWithTarget:self] selector:@selector(changeLabelText:)];
_displayLink.preferredFramesPerSecond = 10; 
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

這樣就可以在避免循環(huán)引用了铅辞,推薦NSTimer也使用這種方法

2.使用category擴展block方法
新建一個分類 CADisplayLink+GQTool

// .h
#import <QuartzCore/QuartzCore.h>

typedef void(^GQExecuteDisplayLinkBlock) (CADisplayLink *displayLink);

@interface CADisplayLink (GQTool)

@property (nonatomic,copy) GQExecuteDisplayLinkBlock executeBlock;

+ (CADisplayLink *)displayLinkWithExecuteBlock:(GQExecuteDisplayLinkBlock)block;

@end
// .m
#import "CADisplayLink+GQTool.h"
#import <objc/runtime.h>

@implementation CADisplayLink (GQTool)

- (void)setExecuteBlock:(GQExecuteDisplayLinkBlock)executeBlock{

    objc_setAssociatedObject(self, @selector(executeBlock), [executeBlock copy], OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (GQExecuteDisplayLinkBlock)executeBlock{

    return objc_getAssociatedObject(self, @selector(executeBlock));
}

+ (CADisplayLink *)displayLinkWithExecuteBlock:(GQExecuteDisplayLinkBlock)block{

    CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(gq_executeDisplayLink:)];
    displayLink.executeBlock = [block copy];
    return displayLink;
}

+ (void)gq_executeDisplayLink:(CADisplayLink *)displayLink{

    if (displayLink.executeBlock) {
        displayLink.executeBlock(displayLink);
    }
}

@end

在初始化定時器時

__weak typeof(self) weakSelf = self;
_displayLink = [CADisplayLink displayLinkWithExecuteBlock:^(CADisplayLink * _Nonnull displayLink) {
    [weakSelf changeLabelText];
}];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

這樣也可以避免定時器對VC的強引用萨醒,但本質上只是將定時器的target從控制器換成了定時器本身的類斟珊,還是存在循環(huán)引用,只不過對我們的系統(tǒng)沒有影響了富纸。所以推薦使用NSProxy這種方法囤踩。

以上就是關于iOS中三種定時器的詳細介紹,原創(chuàng)不易晓褪,如果您覺得這篇文章對您有用的話堵漱,就順手點個贊+關注吧。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末涣仿,一起剝皮案震驚了整個濱河市勤庐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌好港,老刑警劉巖愉镰,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钧汹,居然都是意外死亡丈探,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門拔莱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碗降,“玉大人隘竭,你說我怎么就攤上這事∫怕啵” “怎么了货裹?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長精偿。 經常有香客問我弧圆,道長,這世上最難降的妖魔是什么笔咽? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任搔预,我火速辦了婚禮,結果婚禮上叶组,老公的妹妹穿的比我還像新娘拯田。我一直安慰自己,他們只是感情好甩十,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布船庇。 她就那樣靜靜地躺著,像睡著了一般侣监。 火紅的嫁衣襯著肌膚如雪鸭轮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天橄霉,我揣著相機與錄音窃爷,去河邊找鬼。 笑死姓蜂,一個胖子當著我的面吹牛按厘,可吹牛的內容都是我干的。 我是一名探鬼主播钱慢,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼逮京,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了束莫?” 一聲冷哼從身側響起造虏,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎麦箍,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陶珠,經...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡挟裂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了揍诽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诀蓉。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡栗竖,死狀恐怖,靈堂內的尸體忽然破棺而出渠啤,到底是詐尸還是另有隱情狐肢,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布沥曹,位于F島的核電站份名,受9級特大地震影響,放射性物質發(fā)生泄漏妓美。R本人自食惡果不足惜僵腺,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望壶栋。 院中可真熱鬧辰如,春花似錦、人聲如沸贵试。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽毙玻。三九已至豌蟋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間淆珊,已是汗流浹背夺饲。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留施符,地道東北人往声。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像戳吝,于是被迫代替她去往敵國和親浩销。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內容