KVC/KVO 的使用及原理分析

KVC/KVO

概念

  • KVC : 即 Key-Value-Coding,用于鍵值編碼敬肚。作為 cocoa 的一個(gè)標(biāo)準(zhǔn)化組成部分毕荐,它是基于 NSKeyValueCoding 非正式協(xié)議的機(jī)制。簡單來說艳馒,就是直接通過 key 值對對象的屬性進(jìn)行存取操作憎亚,而不需要調(diào)用明確的存取方法(set 和 get 方法 )∨浚基本上所有的 OC 對象都支持 KVC第美。

  • KVO : 即 Key-Value-Observing ,鍵值觀察陆爽∈餐回調(diào)機(jī)制,當(dāng)指定的對象屬性(內(nèi)存地址/常量改變)被修改后慌闭,對象就會(huì)受到通知别威。使用 KVO 機(jī)制的前提:必須能支持 KVC 機(jī)制躯舔;另外,在 MVC 設(shè)計(jì)架構(gòu)中省古, KVO 適合 在 Model 和 Controller 之間通訊粥庄。

KVC / KVO 的使用

其實(shí),我們經(jīng)常會(huì)用到 KVC 和 KVO 機(jī)制來解決實(shí)際開發(fā)中的好多問題豺妓,尤其是 KVC惜互,因?yàn)檫@種基于運(yùn)行時(shí)的編程方式大大地提高了靈活性,簡化代碼琳拭;比如训堆,我們經(jīng)常會(huì)用如下代碼來修改 textField 的 placeHolder 的字體和顏色等屬性。

   [self setValue:[UIColor whiteColor] forKeyPath:@"_placeholderLabel.textColor"];
   [self setValue:[UIFont boldSystemFontOfSize:20] forKeyPath:@"_placeholderLabel.font"];

下面臀栈,來為大家列舉一下 KVC / KVO 的使用場景

KVC的使用

KVC 的常用的方法:

- (nullable id)valueForKey:(NSString *)key;                          //直接通過Key來取值
- (void)setValue:(nullable id)value forKey:(NSString *)key;          //通過Key來設(shè)值
- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通過KeyPath來取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通過KeyPath來設(shè)值

NSKeyValueCoding 類別中的一些其他方法:

+ (BOOL)accessInstanceVariablesDirectly;
//默認(rèn)返回YES,表示如果沒有找到Set<Key>方法的話挠乳,會(huì)按照_key权薯,_iskey,key睡扬,iskey的順序搜索成員盟蚣,設(shè)置成NO就不這樣搜索

- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供屬性值正確性驗(yàn)證的API,它可以用來檢查set的值是否正確卖怜、為不正確的值做一個(gè)替換值或者拒絕設(shè)置新值并返回錯(cuò)誤原因屎开。

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//這是集合操作的API,里面還有一系列這樣的API马靠,如果屬性是一個(gè)NSMutableArray奄抽,那么可以用這個(gè)方法來返回。

- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在甩鳄,且沒有KVC無法搜索到任何和Key有關(guān)的字段或者屬性逞度,則會(huì)調(diào)用這個(gè)方法,默認(rèn)是拋出異常妙啃。

- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一個(gè)方法一樣档泽,但這個(gè)方法是設(shè)值。

- (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法時(shí)面給Value傳nil揖赴,則會(huì)調(diào)用這個(gè)方法

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//輸入一組key,返回該組key對應(yīng)的Value馆匿,再轉(zhuǎn)成字典返回,用于將Model轉(zhuǎn)到字典燥滑。
  • 動(dòng)態(tài)地取值和設(shè)值

  • 用 KVC 來訪問和修改私有變量
    對于類里的私有屬性渐北,Objective-C是無法直接訪問的,但是KVC是可以的

  • Model 和 字典轉(zhuǎn)換
    其實(shí)铭拧,常見的第三方 模型解析庫就是利用 KVC 和 Objective-C 的 動(dòng)態(tài)性 Runtime 實(shí)現(xiàn)的腔稀。

  • 修改一些控件的內(nèi)部屬性
    利用 KVC 來修改我們無法訪問和修改控件的樣式盆昙,但是 Apple 并沒有為我們提供訪問這些控件的 API,因此我們不好獲取這些屬性名焊虏。所以淡喜,我們又要使用 Runtime 這個(gè)東西了。比如诵闭,我們可以使用以下代碼來獲取 UITableView 的屬性列表炼团。

    unsigned int count = 0;
    objc_property_t *propertyList = class_copyPropertyList([UITableView class], &count);
    NSMutableArray * mutableList_property = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int  i = 0; i < count; i++) {
        const char *propertyName = property_getName(propertyList[I]);
        [mutableList_property addObject:[NSString stringWithUTF8String:propertyName]];
    }
    free(propertyList);
    NSArray * propertylist = [NSArray arrayWithArray:mutableList_property];
    NSLog(@"\n獲取UITableView的屬性列表:%@",propertylist);
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  • 操作集合
    蘋果 對于 KVC 的 valueForKey: 方法做了特殊的實(shí)現(xiàn)。常見的容器類就實(shí)現(xiàn)了這些方法疏尿。
  • 實(shí)現(xiàn)高階消息傳遞
    當(dāng)對容器使用 KVC 時(shí)瘟芝, valueForKey 會(huì)直接唄傳遞給 容器中的每一個(gè)對象,而不只是對容器本身進(jìn)行操作褥琐。結(jié)果會(huì)被添加進(jìn)返回的容器锌俱。
NSArray* arrStr = @[@"english",@"franch",@"chinese"];
NSArray* arrCapStr = [arrStr valueForKey:@"capitalizedString"];
for (NSString* str  in arrCapStr) {
    NSLog(@"%@",str);
}
NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"];
for (NSNumber* length  in arrCapStrLength) {
    NSLog(@"%ld",(long)length.integerValue);
}
打印結(jié)果
2016-04-20 16:29:14.239 KVCDemo[1356:118667] English
2016-04-20 16:29:14.240 KVCDemo[1356:118667] Franch
2016-04-20 16:29:14.240 KVCDemo[1356:118667] Chinese
2016-04-20 16:29:14.240 KVCDemo[1356:118667] 7
2016-04-20 16:29:14.241 KVCDemo[1356:118667] 6
2016-04-20 16:29:14.241 KVCDemo[1356:118667] 7
  • 用 KVC 中的函數(shù)操作集合
    KVC 提供了很復(fù)雜的函數(shù):
    1. 簡單集合運(yùn)算符: @avg @count @max @min @sum (不支持自定義)
    2. 對象運(yùn)算級: @distinctUnionOfObjects @unionOfObjects (比集合運(yùn)算符稍微復(fù)雜,能以數(shù)組的方式返回指定的內(nèi)容)
    3. Array 和 Set 操作符: @distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets

KVO 的使用

對于敌呈,KVO 的使用贸宏,就是觀察鍵值。在 MVC 設(shè)計(jì)架構(gòu)中磕洪, KVO 適合 在 Model 和 Controller 之間通訊吭练。
使用步驟:

  1. 注冊觀察者,實(shí)施監(jiān)聽
//第一個(gè)參數(shù)observer:觀察者 (這里觀察self.myKVO對象的屬性變化)

//第二個(gè)參數(shù)keyPath: 被觀察的屬性名稱(這里觀察self.myKVO中num屬性值的改變)

//第三個(gè)參數(shù)options: 觀察屬性的新值析显、舊值等的一些配置(枚舉值鲫咽,可以根據(jù)需要設(shè)置,例如這里可以使用兩項(xiàng))

//第四個(gè)參數(shù)context: 上下文谷异,可以為kvo的回調(diào)方法傳值(例如設(shè)定為一個(gè)放置數(shù)據(jù)的字典)

//注冊觀察者

[self.myKVO addObserver:self forKeyPath:@"num" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil]; 
  1. 在回調(diào)方法中處理屬性發(fā)生的變化
/keyPath:屬性名稱

//object:被觀察的對象

//change:變化前后的值都存儲(chǔ)在change字典中

//context:注冊觀察者時(shí)分尸,context傳過來的值

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
}
  1. 移除觀察者

注意:
觀察者觀察的是屬性,只有遵循 KVO 變更屬性值的方式才會(huì)執(zhí)行KVO的回調(diào)方法歹嘹,例如是否執(zhí)行了setter方法寓落、或者是否使用了KVC賦值。如果賦值沒有通過setter方法或者KVC荞下,而是直接修改屬性對應(yīng)的成員變量伶选,例如:僅調(diào)用_name = @"newName",這時(shí)是不會(huì)觸發(fā)kvo機(jī)制尖昏,更加不會(huì)調(diào)用回調(diào)方法的仰税。

實(shí)現(xiàn)原理

KVO 是 基于 KVC 實(shí)現(xiàn)的。首先了解 KVC 的機(jī)制抽诉,才能更好的理解 KVO陨簇。

KVC 是如何尋找 Key 值的

  • 當(dāng)調(diào)用 setValue: forKey: 時(shí)
    1. 優(yōu)先調(diào)用 set <Key>:屬性值 方法 ,代碼實(shí)現(xiàn) setter 方法
    2. 如果無 set 方法迹淌, KVC 會(huì)檢查 + (BOOL)accessInstanceVariablesDirectly 有沒有返回 YES(默認(rèn)為 YES河绽,如果重寫使其返回 NO 的話己单,就會(huì)執(zhí)行 setValue:forUndefinedKey:)。KVC 機(jī)制會(huì)按照_key耙饰,_iskey纹笼,key,iskey的順序搜索成員苟跪,對其賦值廷痘。
    3. 如果都不存在,系統(tǒng)會(huì)執(zhí)行該對象的 setValue:forUndefinedKey:方法,默認(rèn)拋出異常。
  • 當(dāng)調(diào)用 valueForKey: 時(shí):
    KVC對key的搜索方式不同于setValue:屬性值 forKey:@”name“七问,其搜索方式如下:

    1. 首先按 get<Key>,<key>,is<Key> 的順序方法查找 getter 方法,找到的話會(huì)直接調(diào)用债沮。如果是BOOL或者Int等值類型, 會(huì)將其包裝成一個(gè)NSNumber對象。
    2. 如果上面的 getter 方法未找到,KVC則會(huì)查找 countOf<Key> , objectIn<Key>AtIndex<Key>AtIndexes 格式的方法枢冤。如果 countOf<Key> 方法和另外兩個(gè)方法中的一個(gè)被找到,那么就會(huì)返回一個(gè)可以響應(yīng) NSArray 所有方法的代理集合(它是 NSKeyValueArray 歼狼,是NSArray的子類)掏导,調(diào)用這個(gè)代理集合的方法享怀,或者說給這個(gè)代理集合發(fā)送屬于NSArray的方法羽峰,就會(huì)以countOf<Key> ,objectIn<Key>AtIndex或<Key>AtIndexes這幾個(gè)方法組合的形式調(diào)用。還有一個(gè)可選的get<Key>: range: 方法添瓷。所以你想重新定義 KVC 的一些功能梅屉,你可以添加這些方法,需要注意的是你的方法名要符合KVC的標(biāo)準(zhǔn)命名方法鳞贷,包括方法簽名坯汤。
    3. 如果上面的方法沒有找到,那么會(huì)同時(shí)查找countOf<Key>搀愧,enumeratorOf<Key>,memberOf<Key>格式的方法惰聂。如果這三個(gè)方法都找到,那么就返回一個(gè)可以響應(yīng)NSSet所的方法的代理集合咱筛,和上面一樣搓幌,給這個(gè)代理集合發(fā)NSSet的消息,就會(huì)以countOf<Key>迅箩,enumeratorOf<Key>,memberOf<Key>組合的形式調(diào)用溉愁。
    4. 如果還未找到,則和先前的設(shè)值一樣饲趋,會(huì)按_<key>,_is<Key>,<key>,is<Key>的順序搜索成員變量名拐揭,這里不推薦這么做撤蟆,因?yàn)檫@樣直接訪問實(shí)例變量破壞了封裝性,使代碼更脆弱堂污。如果重寫了類方法 + (BOOL)accessInstanceVariablesDirectly返回NO的話家肯,那么會(huì)直接調(diào)用valueForUndefinedKey:
    5. 還沒有找到的話,調(diào)用valueForUndefinedKey:
  • 在 KVC 中 使用 keyPath
    用小數(shù)點(diǎn)分割 key,敷鸦,然后再像普通key一樣按照先前介紹的順序搜索下去息楔。如果屬性不存在,同樣會(huì)調(diào)用 valueForUndefinedKey:

  • KVC處理異常
    重寫拋出異常的那兩個(gè)方法扒披,通常情況下值依,KVC不允許你要在調(diào)用setValue:屬性值 forKey:@”name“(或者keyPath)時(shí)對非對象傳遞一個(gè)nil的值。

  • KVC 處理非對象和自定義對象
    valueForKey:總是返回一個(gè)id對象碟案,如果原本的變量類型是值類型或者結(jié)構(gòu)體愿险,返回值會(huì)封裝成NSNumber或者NSValue對象,但是 setValue:forKey:卻不行价说。你必須手動(dòng)將值類型轉(zhuǎn)換成NSNumber或者NSValue類型辆亏,才能傳遞過去。

  • KVC與容器類
    不可變的有序容器屬性(NSArray)和無序容器屬性(NSSet)一般可以使用 valueForKey:來獲取鳖目。如有一個(gè)叫 items 的 NSArray 的屬性扮叨,你可以使用 valuleaForKey:@"items" 來獲取這個(gè)屬性。
    當(dāng)對象的屬性是可變的容器時(shí)领迈,對于有序容器彻磁,可以使用如下方法:
    - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
    該方法的返回值是一個(gè)可變的有序數(shù)組,如果調(diào)用該方法狸捅,KVC 的搜索順序如下:
    1. 搜索 insertObject: in<Key>AtIndex: , removeObjectFrom<Key>AtIndex: 或者 insert<Key>AtIndexes衷蜓,remove<Key>AtIndexes 格式的方法。如果至少找到一個(gè) insert 方法和一個(gè) remove 方法尘喝,那么同樣返回一個(gè)可以響應(yīng) NSMutableArray 所有方法代理集合(類名是 NSKeyValueFastMutableArray2 )磁浇,那么給這個(gè)代理集合發(fā)送NSMutableArray 的方法,就會(huì)以 insertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex: 或者 insert<Key>AdIndexes , remove<Key>AtIndexes組合的形式調(diào)用朽褪。還有兩個(gè)可選實(shí)現(xiàn)的接口:replaceOnjectAtIndex:withObject:,replace<Key>AtIndexes:with<Key>:置吓。

    1. 如果上步的方法沒有找到,則搜索set<Key>: 格式的方法缔赠,如果找到衍锚,那么發(fā)送給代理集合的 NSMutableArray 最終都會(huì)調(diào)用set<Key>:方法。 也就是說橡淑,mutableArrayValueForKey:取出的代理集合修改后构拳,用set<Key>: 重新賦值回去去。這樣做效率會(huì)低很多。所以推薦實(shí)現(xiàn)上面的方法置森。
    2. 如果上一步的方法還還沒有找到斗埂,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回 YES (默認(rèn)行為),會(huì)按 _<key>,<key>,的順序搜索成員變量名凫海,如果找到呛凶,那么發(fā)送的 NSMutableArray 消息方法直接交給這個(gè)成員變量處理。
    3. 如果還未找到行贪,則調(diào)用 valueForUndefinedKey:崭捍,默認(rèn)拋出異常 橄浓。

除了用于有序的可變?nèi)萜魍猓?code>mutableArrayValueForKey:一般還用于對 NSMutableArray 添加 Observer 上准给。如果對象屬性是個(gè) 可變?nèi)萜黝愋蜁r(shí)叼旋,你給他添加 KVO時(shí),你會(huì)發(fā)現(xiàn)當(dāng)你添加或移除元素時(shí)并不會(huì)接受到變化。因?yàn)?KVO 的本質(zhì)是系統(tǒng)監(jiān)測到某個(gè)屬性的內(nèi)存地址或者常量改變時(shí),會(huì)添加上- (void)willChangeValueForKey:(NSString *)key- (void)didChangeValueForKey:(NSString *)key方法來發(fā)送通知,所以一種方法是手動(dòng)調(diào)用這兩個(gè)方法慧瘤,一種是使用上述的 mutableArrayValueForKey: 方法怔匣。
對于無序容器時(shí)拴疤,可以使用下面的方法:
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
該方法返回一個(gè)可變的無序數(shù)組,如果調(diào)用該方法独泞,KVC 的搜索順序除了檢查 receiver 是 ManagedObject 以外呐矾,其搜索順序和 mutableArrayValueForKey 基本一致。

  • KVC與字典
    當(dāng)對NSDictionary對象使用KVC時(shí)懦砂,valueForKey:的表現(xiàn)行為和objectForKey:一樣蜒犯。所以使用valueForKeyPath:用來訪問多層嵌套的字典是比較方便的。

KVO 的原理

我們之前就說了 KVO 的實(shí)現(xiàn)依靠的是 Objective-C 強(qiáng)大的 Runtime荞膘。那么具體的罚随,我們是如何實(shí)現(xiàn)這一機(jī)制的呢?下面讓我們共同來探討一下羽资。

基本原理:
當(dāng)觀察對象 A 時(shí)淘菩, KVO 機(jī)制動(dòng)態(tài)的創(chuàng)建了一個(gè)對象 A 當(dāng)前的子類,并為這個(gè)新的子類重寫了被觀察屬性 keyPath 的 setter 方法屠升。在 setter 方法中潮改,我們通知觀察對象屬性的改變狀態(tài)。

進(jìn)一步剖析:
其實(shí) Apple 使用了 isa 混寫腹暖,即 isa-swizzling 來實(shí)現(xiàn) KVO汇在。

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 method to determine the class of an object instance.

那么,我們通過下面的代碼來看看脏答,isa-swizzling 的真面目到底是什么糕殉?

// 在 PQCPeople.m 中,我們模擬 KVO 的過程亩鬼,打印 isa 指針指向的類,以及 setter 方法的的函數(shù)指針
- (void)printUserInfo {
    NSLog(@"isa:%@,supperclass:%@",NSStringFromClass(object_getClass(self)),
          class_getSuperclass(object_getClass(self)));
    NSLog(@"self:%@, [self superclass]:%@", self, [self superclass]);
    NSLog(@"name setter function pointer:%p", class_getMethodImplementation(object_getClass(self), @selector(setNamestr:)));
    NSLog(@"printInfo function pointer:%p", class_getMethodImplementation(object_getClass(self), @selector(printUserInfo)));
}

在運(yùn)行過程中阿蝶,在添加Observer前辛孵,添加Observer以及刪除Observer后分別打印出該類的信息。

    PQCPeople *person = [[PQCPeople alloc]init];
    NSLog(@"Before add observer————————————————————————–");
    [person printUserInfo];
    [person addObserver:self forKeyPath:@"namestr" options:NSKeyValueObservingOptionNew context:nil];
    NSLog(@"After add observer————————————————————————–");
    [person printUserInfo];
    [person removeObserver:self forKeyPath:@"namestr"];
    NSLog(@"After remove observer————————————————————————–");
    [person printUserInfo];

以下是打印的信息:

2017-11-14 13:06:51.927605+0800 KVC&KVO[851:92433] Before add observer————————————————————————–
2017-11-14 13:06:51.927701+0800 KVC&KVO[851:92433] isa:PQCPeople,supperclass:NSObject
2017-11-14 13:06:51.927814+0800 KVC&KVO[851:92433] self:<PQCPeople: 0x604000059c50>, [self superclass]:NSObject
2017-11-14 13:06:51.927904+0800 KVC&KVO[851:92433] name setter function pointer:0x10c2429d0
2017-11-14 13:06:51.927987+0800 KVC&KVO[851:92433] printInfo function pointer:0x10c242ba0
2017-11-14 13:06:51.928224+0800 KVC&KVO[851:92433] After add observer————————————————————————–
2017-11-14 13:06:51.928316+0800 KVC&KVO[851:92433] isa:NSKVONotifying_PQCPeople,supperclass:PQCPeople
2017-11-14 13:06:51.928489+0800 KVC&KVO[851:92433] self:<PQCPeople: 0x604000059c50>, [self superclass]:NSObject
2017-11-14 13:06:51.928628+0800 KVC&KVO[851:92433] name setter function pointer:0x10c58f666
2017-11-14 13:06:51.928798+0800 KVC&KVO[851:92433] printInfo function pointer:0x10c242ba0
2017-11-14 13:06:51.928954+0800 KVC&KVO[851:92433] After remove observer————————————————————————–
2017-11-14 13:06:51.929077+0800 KVC&KVO[851:92433] isa:PQCPeople,supperclass:NSObject
2017-11-14 13:06:51.929268+0800 KVC&KVO[851:92433] self:<PQCPeople: 0x604000059c50>, [self superclass]:NSObject
2017-11-14 13:06:51.929431+0800 KVC&KVO[851:92433] name setter function pointer:0x10c2429d0
2017-11-14 13:06:51.929586+0800 KVC&KVO[851:92433] printInfo function pointer:0x10c242ba0

通過分析赡磅,我們會(huì)發(fā)現(xiàn)在添加KVO之后魄缚,isa 已經(jīng)替換成了NSKVONotifying_PQCPeople,而根據(jù) class_getSuperclass得到的結(jié)果竟然是 PQCPerson, 然后 namestr 是我們KVO需要觀察的屬性,它的 setter函數(shù)指針也變了焚廊。
我們上面也說道冶匹, OC 的消息機(jī)制是通過 isa 去查找實(shí)現(xiàn)的,那么我們可以根據(jù)以上的分析咆瘟,可以大致得出嚼隘,KVO的實(shí)現(xiàn)應(yīng)該是:

  • 添加 Observe
    通過 runtime 偷偷實(shí)現(xiàn)了一個(gè)子類,并且以 NSKVONotifying_+類名 來命名袒餐;
    將之前那個(gè)對象的isa指針指向了這個(gè)子類飞蛹;
    重寫了觀察的對象setter方法,并且在重寫的中添加了willChangeValueForKey:以及didChangeValueForKey:

  • 移除 Observe
    將 isa 的指向指回原來的類對象中灸眼。

因此卧檐,我們的 KVO 就是通過如圖這種過程,進(jìn)行鍵值觀察焰宣。


isa_swizzling.jpg

文末

通過上面的分析霉囚,我們也大概了解了 KVC 以及 KVO 的實(shí)現(xiàn)過程以及實(shí)現(xiàn)原理,對這個(gè)感興趣的同學(xué)匕积,我們可以試著去自己實(shí)現(xiàn)一下 KVC 以及 KVO盈罐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市闪唆,隨后出現(xiàn)的幾起案子盅粪,更是在濱河造成了極大的恐慌,老刑警劉巖悄蕾,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件票顾,死亡現(xiàn)場離奇詭異,居然都是意外死亡笼吟,警方通過查閱死者的電腦和手機(jī)库物,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門霸旗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贷帮,“玉大人,你說我怎么就攤上這事诱告∧焓啵” “怎么了民晒?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長锄禽。 經(jīng)常有香客問我潜必,道長,這世上最難降的妖魔是什么沃但? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任磁滚,我火速辦了婚禮,結(jié)果婚禮上宵晚,老公的妹妹穿的比我還像新娘垂攘。我一直安慰自己,他們只是感情好淤刃,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布晒他。 她就那樣靜靜地躺著,像睡著了一般逸贾。 火紅的嫁衣襯著肌膚如雪陨仅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天铝侵,我揣著相機(jī)與錄音灼伤,去河邊找鬼。 笑死咪鲜,一個(gè)胖子當(dāng)著我的面吹牛饺蔑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嗜诀,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼猾警,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了隆敢?” 一聲冷哼從身側(cè)響起发皿,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拂蝎,沒想到半個(gè)月后穴墅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡温自,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年玄货,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悼泌。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡松捉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出馆里,到底是詐尸還是另有隱情隘世,我是刑警寧澤可柿,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站丙者,受9級特大地震影響复斥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜械媒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一目锭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纷捞,春花似錦侣集、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缀辩,卻和暖如春臭埋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背臀玄。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工瓢阴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人健无。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓荣恐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親累贤。 傳聞我的和親對象是個(gè)殘疾皇子叠穆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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