iOS-KVO淺談

上一篇:iOS-KVC淺談

前言:KVO 作為 KVC 的同袍兄弟拂玻,功能更強(qiáng)大,聊聊 KVO。

一策治、KVO 簡介

1.1 KVO 概述

1.KVO 是鍵值觀察者(key-value-observing)首装。
2.KVO提供了一種觀察者的機(jī)制创夜,通過對某個(gè)對象的某個(gè)屬性添加觀察者,當(dāng)該屬性改變仙逻,就會調(diào)用"observeValueForKeyPath:"方法驰吓,為我們提供一個(gè)“對象值改變了!”的時(shí)機(jī)進(jìn)行一些操作系奉。
3.KVO 是一個(gè)觀察者模式檬贰。觀察一個(gè)對象的屬性,注冊一個(gè)指定的路徑缺亮,若這個(gè)對象的的屬性被修改翁涤,則 KVO 會自動(dòng)通知觀察者。
4.基本思想:一個(gè)目標(biāo)對象管理所有依賴于它的觀察者對象萌踱,并在它自身的狀態(tài)改變時(shí)主動(dòng)通知觀察者對象葵礼。這個(gè)主動(dòng)通知通常是通過調(diào)用各觀察者對象所提供的接口方法來實(shí)現(xiàn)的。觀察者模式較完美地將目標(biāo)對象與觀察者對象解耦并鸵。
5.任何對象都允許觀察其他對象的屬性鸳粉,并且可以接收其他對象狀態(tài)變化的通知。
6.Objective-C 中有兩種使用鍵值觀察的方式:手動(dòng)或自動(dòng)园担,此外還支持注冊依賴鍵(即一個(gè)鍵依賴于其他鍵届谈,其他鍵的變化也會作用到該鍵)枯夜。

二、KVO 應(yīng)用

2.1 基本使用

    1. 注冊觀察者疼约,實(shí)施監(jiān)聽卤档;
[self.person addObserver:self
              forKeyPath:@"age"
                 options:NSKeyValueObservingOptionNew
                 context:nil];
    1. 回調(diào)方法,在這里處理屬性發(fā)生的變化程剥;
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context
    1. 移除觀察者劝枣;
[self removeObserver:self forKeyPath:@"age"];

代碼示例

_person = [[Person alloc] init];
    
/**
 *  添加觀察者
 *
 *  @param observer 觀察者
 *  @param keyPath  被觀察的屬性名稱
 *  @param options  觀察屬性的新值、舊值等的一些配置(枚舉值织鲸,可以根據(jù)需要設(shè)置舔腾,例如這里可以使用兩項(xiàng))
 *  注: options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 返回未改變之前的值和改變之后的值    context可以為空
 *  @param context  上下文,可以為nil搂擦。
 */
[_person addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
             context:nil];
/**
 *  KVO回調(diào)方法
 *
 *  @param keyPath 被修改的屬性
 *  @param object  被修改的屬性所屬對象
 *  @param change  屬性改變情況(新舊值)
 *  @param context context傳過來的值
 */
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context
{
    NSLog(@"%@對象的%@屬性改變了:%@",object,keyPath,change);
 }
/**
 *  移除觀察者
 */
- (void)dealloc
{
    [self.person removeObserver:self forKeyPath:@"age"];
}

2. KVO的使用場景

KVO用于監(jiān)聽對象屬性的改變稳诚。
  (1)下拉刷新瀑踢、下拉加載監(jiān)聽UIScrollView的contentoffsize扳还;
  (2)webview混排監(jiān)聽contentsize橱夭;
 “本唷(3)監(jiān)聽模型屬性實(shí)時(shí)更新UI;
 〖印(4)監(jiān)聽控制器frame改變俏让,實(shí)現(xiàn)抽屜效果。

監(jiān)聽 ScrollView 的 contentOffSet 屬性:

[scrollview addObserver:self
             forKeyPath:@"contentOffset"                   
                options:NSKeyValueObservingOptionNew
                context:nil];

三茬暇、鍵值觀察

3.1 運(yùn)用鍵值觀察

1.注冊與解除注冊

如果我們已經(jīng)有了包含可供鍵值觀察屬性的類首昔,那么就可以通過在該類的對象(被觀察對象)上調(diào)用名為 NSKeyValueObserverRegistration 的 category 方法將觀察者對象與被觀察者對象注冊與解除注冊:

  • Foundation/NSKeyValueObserving.h 中,NSObject糙俗,NSArray勒奇,NSSet均實(shí)現(xiàn)了以下方法,因此我們不僅可以觀察普通對象巧骚,還可以觀察數(shù)組或結(jié)合類對象撬陵。
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
  • NSObject 還實(shí)現(xiàn)了 NSKeyValueObserverNotification 的 category 方法:
    這兩個(gè)方法在手動(dòng)實(shí)現(xiàn)鍵值觀察時(shí)會用到
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
  • 注:不要忘記解除注冊,否則會導(dǎo)致資源泄露网缝。
2.設(shè)置屬性

將觀察者與被觀察者注冊好之后,就可以對觀察者對象的屬性進(jìn)行操作蟋定,這些變更操作就會被通知給觀察者對象粉臊。注意,只有遵循 KVO 方式來設(shè)置屬性驶兜,觀察者對象才會獲取通知扼仲,也就是說遵循使用屬性的 setter 方法远寸,或通過key-path來設(shè)置:

[target setAge:30];
[target setValue:[NSNumber numberWithInt:30] forKey:@"age"];
3.處理變更通知

觀察者需要實(shí)現(xiàn)名為 NSKeyValueObserving 的 category 方法來處理收到的變更通知:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

在這里,change 這個(gè)字典保存了變更信息屠凶,具體是哪些信息取決于注冊時(shí)的 NSKeyValueObservingOptions驰后。

5.代碼示例

在實(shí)現(xiàn)處理變更通知方法observeValueForKeyPath時(shí),要將不能處理的 key 轉(zhuǎn)發(fā)給 super 的 observeValueForKeyPath 來處理矗愧。

// Observer.h
@interface Observer : NSObject
@end

// Observer.m
#import "Observer.h"
#import <objc/runtime.h>
#import "Target.h"

@implementation Observer

- (void) observeValueForKeyPath:(NSString *)keyPath
                       ofObject:(id)object 
                         change:(NSDictionary *)change
                        context:(void *)context
{
    if ([keyPath isEqualToString:@"age"])
    {
        Class classInfo = (Class)context;
        NSString * className = [NSString stringWithCString:object_getClassName(classInfo)
                                                  encoding:NSUTF8StringEncoding];
        NSLog(@" >> class: %@, Age changed", className);
        NSLog(@" old age is %@", [change objectForKey:@"old"]);
        NSLog(@" new age is %@", [change objectForKey:@"new"]);
    }
    else
    {
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

@end

調(diào)用示例

Observer * observer = [[[Observer alloc] init] autorelease];
Target * target = [[[Target alloc] init] autorelease];
[target addObserver:observer
         forKeyPath:@"age"
            options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
            context:[Target class]];
[target setAge:30];
//[target setValue:[NSNumber numberWithInt:30] forKey:@"age"];
[target removeObserver:observer forKeyPath:@"age"];

輸出結(jié)果:

class: Target, Age changed
old age is 10
new age is 30

3.2 手動(dòng)實(shí)現(xiàn)鍵值觀察

首先灶芝,需要手動(dòng)實(shí)現(xiàn)屬性的 setter 方法,并在設(shè)置操作的前后分別調(diào)用 willChangeValueForKey: 和 didChangeValueForKey方法唉韭,這兩個(gè)方法用于通知系統(tǒng)該 key 的屬性值即將和已經(jīng)變更了夜涕;
其次,要實(shí)現(xiàn)類方法 automaticallyNotifiesObserversForKey属愤,并在其中設(shè)置對該 key 不自動(dòng)發(fā)送通知(返回 NO 即可)女器。這里要注意,對其它非手動(dòng)實(shí)現(xiàn)的 key住诸,要轉(zhuǎn)交給 super 來處理驾胆。

@interface Target : NSObject
{
    int age;
}
// for manual KVO - age
- (int) age;
- (void) setAge:(int)theAge;
@end

@implementation Target

- (id) init
{
    self = [super init];
    if (nil != self)
    {
        age = 10;
    }
    return self;
}

// for manual KVO - age
- (int) age
{
    return age;
}

- (void) setAge:(int)theAge
{
    [self willChangeValueForKey:@"age"];
    age = theAge;
    [self didChangeValueForKey:@"age"];
}

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"age"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}
@end

3.3 自動(dòng)實(shí)現(xiàn)鍵值觀察

自動(dòng)實(shí)現(xiàn)鍵值觀察就非常簡單了,只要使用了自動(dòng)屬性即可贱呐。

@interface Target : NSObject
// for automatic KVO - age
@property (nonatomic, readwrite) int age;
@end

@implementation Target
@synthesize age; // for automatic KVO - age

- (id) init
{
    self = [super init];
    if (nil != self)
    {
        age = 10;
    }
    
    return self;
}
@end

3.4 鍵值觀察依賴鍵

有時(shí)候一個(gè)屬性的值依賴于另一對象中的一個(gè)或多個(gè)屬性丧诺,如果這些屬性中任一屬性的值發(fā)生變更,被依賴的屬性值也應(yīng)當(dāng)為其變更進(jìn)行標(biāo)記吼句。因此锅必,object 引入了依賴鍵。

1. 觀察依賴鍵

觀察依賴鍵的方式與前面描述的一樣惕艳,下面先在 Observer 的 observeValueForKeyPath:ofObject:change:context: 中添加處理變更通知的代碼:

#import "TargetWrapper.h"

- (void) observeValueForKeyPath:(NSString *)keyPath
                       ofObject:(id)object 
                         change:(NSDictionary *)change
                        context:(void *)context
{
    if ([keyPath isEqualToString:@"age"])
    {
        Class classInfo = (Class)context;
        NSString * className = [NSString stringWithCString:object_getClassName(classInfo)
                                                  encoding:NSUTF8StringEncoding];
        NSLog(@" >> class: %@, Age changed", className);

        NSLog(@" old age is %@", [change objectForKey:@"old"]);
        NSLog(@" new age is %@", [change objectForKey:@"new"]);
    }
    else if ([keyPath isEqualToString:@"information"])
    {
        Class classInfo = (Class)context;
        NSString * className = [NSString stringWithCString:object_getClassName(classInfo)
                                                  encoding:NSUTF8StringEncoding];
        NSLog(@" >> class: %@, Information changed", className);
        NSLog(@" old information is %@", [change objectForKey:@"old"]);
        NSLog(@" new information is %@", [change objectForKey:@"new"]);
    }
    else
    {
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}
2.實(shí)現(xiàn)依賴鍵

在這里搞隐,觀察的是 TargetWrapper 類的 information 屬性,該屬性是依賴于 Target 類的 age 和 grade 屬性远搪。為此劣纲,我在 Target 中添加了 grade 屬性:

@interface Target : NSObject
@property (nonatomic, readwrite) int grade;
@property (nonatomic, readwrite) int age;
@end

@implementation Target
@synthesize age; // for automatic KVO - age
@synthesize grade;
@end

TragetWrapper 中的依賴鍵屬性是實(shí)現(xiàn):

@class Target;

@interface TargetWrapper : NSObject
{
@private
    Target * _target;
}

@property(nonatomic, assign) NSString * information;
@property(nonatomic, retain) Target * target;

-(id) init:(Target *)aTarget;

@end

#import "TargetWrapper.h"
#import "Target.h"

@implementation TargetWrapper

@synthesize target = _target;

-(id) init:(Target *)aTarget
{
    self = [super init];
    if (nil != self) {
        _target = [aTarget retain];
    }
    
    return self;
}

-(void) dealloc
{
    self.target = nil;
    [super dealloc];
}

- (NSString *)information
{
    return [[[NSString alloc] initWithFormat:@"%d#%d", [_target grade], [_target age]] autorelease];
}

- (void)setInformation:(NSString *)theInformation
{
    NSArray * array = [theInformation componentsSeparatedByString:@"#"];
    [_target setGrade:[[array objectAtIndex:0] intValue]];
    [_target setAge:[[array objectAtIndex:1] intValue]];
}

+ (NSSet *)keyPathsForValuesAffectingInformation
{
    NSSet * keyPaths = [NSSet setWithObjects:@"target.age", @"target.grade", nil];
    return keyPaths;
}

//+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
//{
//    NSSet * keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
//    NSArray * moreKeyPaths = nil;
//    
//    if ([key isEqualToString:@"information"])
//    {
//        moreKeyPaths = [NSArray arrayWithObjects:@"target.age", @"target.grade", nil];
//    }
//    
//    if (moreKeyPaths)
//    {
//        keyPaths = [keyPaths setByAddingObjectsFromArray:moreKeyPaths];
//    }
//    
//    return keyPaths;
//}

@end
  • 首先,要手動(dòng)實(shí)現(xiàn)屬性 information 的 setter/getter 方法谁鳍,在其中使用 Target 的屬性來完成其 setter 和 getter癞季。
  • 其次,要實(shí)現(xiàn) keyPathsForValuesAffectingInformation 或 keyPathsForValuesAffectingValueForKey: 方法來告訴系統(tǒng) information 屬性依賴于哪些其他屬性倘潜,這兩個(gè)方法都返回一個(gè)key-path 的集合绷柒。如果選擇實(shí)現(xiàn) keyPathsForValuesAffectingValueForKey,要先獲取 super 返回的結(jié)果 set涮因,然后判斷 key 是不是目標(biāo) key废睦,如果是就將依賴屬性的 key-path 結(jié)合追加到 super 返回的結(jié)果 set 中,否則直接返回 super的結(jié)果养泡。
  • information 屬性依賴于 target 的 age 和 grade 屬性嗜湃,target 的 age/grade 屬性任一發(fā)生變化奈应,information 的觀察者都會得到通知。
3.使用示例
Observer * observer = [[[Observer alloc] init] autorelease];
Target * target = [[[Target alloc] init] autorelease];

TargetWrapper * wrapper = [[[TargetWrapper alloc] init:target] autorelease];
[wrapper addObserver:observer
          forKeyPath:@"information"
             options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
             context:[TargetWrapper class]];

[target setAge:30];
[target setGrade:1];
[wrapper removeObserver:observer forKeyPath:@"information"];

輸出結(jié)果:
class: TargetWrapper, Information changed
old information is 0#10
new information is 0#30
class: TargetWrapper, Information changed
old information is 0#30
new information is 1#30

四购披、KVO 原理

Key-Value Observing Implementation Details
Automatic key-value observing is implemented using a technique called isa-swizzling.
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
You should never rely on the isa pointer to determine class membership. Instead, you should use the [class](http://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html#//apple_ref/occ/intfm/NSObject/class) method to determine the class of an object instance.

  • 當(dāng)某個(gè)類的對象第一次被觀察時(shí)杖挣,系統(tǒng)就會在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類
  • 在這個(gè)派生類中重寫基類中任何被觀察屬性的 setter 方法。
  • 派生類在被重寫的 setter 方法實(shí)現(xiàn)真正的通知機(jī)制刚陡,就如前面手動(dòng)實(shí)現(xiàn)鍵值觀察那樣惩妇。
  • 基于設(shè)置屬性會調(diào)用 setter 方法,而通過重寫就獲得了 KVO 需要的通知機(jī)制橘荠。
  • 前提是要通過遵循 KVO 的屬性設(shè)置方式來變更屬性值屿附,如果僅是直接修改屬性對應(yīng)的成員變量,是無法實(shí)現(xiàn) KVO 的哥童。
  • 同時(shí)派生類還重寫了 class 方法以“欺騙”外部調(diào)用者它就是起初的那個(gè)類挺份。
  • 系統(tǒng)將這個(gè)對象的 isa 指針指向這個(gè)新誕生的派生類,因此這個(gè)對象就成為該派生類的對象了贮懈,因而在該對象上對 setter 的調(diào)用就會調(diào)用重寫的 setter匀泊,從而激活鍵值通知機(jī)制。
  • 派生類還重寫了 dealloc 方法來釋放資源朵你。
  • 當(dāng)一個(gè)觀察者注冊對象的一個(gè)屬性 isa 觀察對象的指針被修改各聘,指著一個(gè)中間類而不是在真正的類。
  • isa 指針的作用:每個(gè)對象都有 isa 指針抡医,指向該對象的類躲因,它告訴 runtime 系統(tǒng)這個(gè)對象的類是什么。

4.1 派生類 NSKVONotifying_Person 剖析:

在這個(gè)過程忌傻,被觀察對象的 isa 指針從指向原來的 Person 類大脉,被 KVO 機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類 NSKVONotifying_Person 類,來實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽水孩。

所以當(dāng)我們從應(yīng)用層面上看來镰矿,完全沒有意識到有新的類出現(xiàn),這是系統(tǒng)“隱瞞”了對 KVO 的底層實(shí)現(xiàn)過程俘种,讓我們誤以為還是原來的類秤标。但是此時(shí)如果我們創(chuàng)建一個(gè)新的名為 NSKVONotifying_Person 的類(),就會發(fā)現(xiàn)系統(tǒng)運(yùn)行到注冊 KVO 的那段代碼時(shí)程序就崩潰宙刘,因?yàn)橄到y(tǒng)在注冊監(jiān)聽的時(shí)候動(dòng)態(tài)創(chuàng)建了名為 NSKVONotifying_Person 的中間類辜腺,并指向這個(gè)中間類了秋忙。

因而在該對象上對 setter 的調(diào)用就會調(diào)用已重寫的 setter容达,從而激活鍵值通知機(jī)制蹂随。這也是 KVO 回調(diào)機(jī)制,為什么都俗稱 KVO 技術(shù)為黑魔法的原因之一吧:內(nèi)部神秘、外觀簡潔屈嗤。

4.2 子類 setter 方法剖析:

1.KVO 在調(diào)用存取方法之前總是調(diào)用 willChangeValueForKey:,通知系統(tǒng)該 keyPath 的屬性值即將變更吊输。
2.當(dāng)改變發(fā)生后饶号,didChangeValueForKey:被調(diào)用,通知系統(tǒng)該 keyPath 的屬性值已經(jīng)變更季蚂。
3.之后茫船,observeValueForKey:ofObject:change:context:也會被調(diào)用。

重寫觀察屬性的 setter 方法這種方式是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的扭屁。 KVO 為子類的觀察者屬性重寫調(diào)用存取方法的工作原理在代碼中相當(dāng)于:

- (void)setName:(NSString *)newName
{
    [self willChangeValueForKey:@"name"];    // KVO在調(diào)用存取方法之前總調(diào)用
    [super setValue:newName forKey:@"name"]; // 調(diào)用父類的存取方法
    [self didChangeValueForKey:@"name"];     // KVO在調(diào)用存取方法之后總調(diào)用
}
KVO原理圖

總結(jié):
KVO 的本質(zhì)就是監(jiān)聽對象的屬性進(jìn)行賦值的時(shí)候有沒有調(diào)用 setter 方法

  • 系統(tǒng)會動(dòng)態(tài)創(chuàng)建一個(gè)繼承于 Person 的 NSKVONotifying_Person
  • person 的 isa 指針指向的類 Person 變成 NSKVONotifying_Person算谈,所以接下來的 person.age = newAge 的時(shí)候,他調(diào)用的不是 Person 的 setter 方法料滥,而是 NSKVONotifying_Person(子類)的 setter 方法
  • 重寫NSKVONotifying_Person的setter方法:[super setName:newName]
  • 通知觀察者告訴屬性改變然眼。
補(bǔ)充
  • Apple 使用了 isa 混寫(isa-swizzling)來實(shí)現(xiàn)KVO
  • 使用setter方法改變值 KVO生效
  • 使用setValue:forKey: 改變值 KVO生效
  • 成員變量直接修改值 KVO失效,必須手動(dòng)添加方法才生效
  • 系統(tǒng)利用運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建一個(gè) NSKVONotifying_Person的子類葵腹,改寫isa指針的指向高每,并重寫子類的setter方法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市践宴,隨后出現(xiàn)的幾起案子鲸匿,更是在濱河造成了極大的恐慌,老刑警劉巖阻肩,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件带欢,死亡現(xiàn)場離奇詭異,居然都是意外死亡烤惊,警方通過查閱死者的電腦和手機(jī)乔煞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撕氧,“玉大人瘤缩,你說我怎么就攤上這事÷啄啵” “怎么了剥啤?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長不脯。 經(jīng)常有香客問我府怯,道長,這世上最難降的妖魔是什么防楷? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任牺丙,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冲簿。我一直安慰自己粟判,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布峦剔。 她就那樣靜靜地躺著档礁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吝沫。 梳的紋絲不亂的頭發(fā)上呻澜,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音惨险,去河邊找鬼羹幸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辫愉,可吹牛的內(nèi)容都是我干的栅受。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼一屋,長吁一口氣:“原來是場噩夢啊……” “哼窘疮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起冀墨,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤闸衫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后诽嘉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔚出,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年虫腋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骄酗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悦冀,死狀恐怖趋翻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情盒蟆,我是刑警寧澤踏烙,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站历等,受9級特大地震影響讨惩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寒屯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一荐捻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦处面、人聲如沸厂置。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽农渊。三九已至,卻和暖如春或颊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背传于。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工囱挑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沼溜。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓平挑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親系草。 傳聞我的和親對象是個(gè)殘疾皇子通熄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

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