iOS - FBKVOController 實現(xiàn)原理

本文導(dǎo)讀:

1.系統(tǒng)KVO的問題
2.FBKVOController優(yōu)點
3.FBKVOController的架構(gòu)設(shè)計圖
4.FBKVOController源碼詳讀
5.FBKVOController總結(jié)

一.系統(tǒng)KVO的問題

  • 當觀察者被銷毀之前枫疆,需要手動移除觀察者馍惹,否則會出現(xiàn)程序異常(向已經(jīng)銷毀的對象發(fā)送消息)燎猛;
  • 可能會對同一個被監(jiān)聽的屬性多次添加監(jiān)聽曹抬,這樣我們會接收到多次監(jiān)聽的回調(diào)結(jié)果溉瓶;
  • 當觀察者對多個對象的不同屬性進行監(jiān)聽,處理監(jiān)聽結(jié)果時谤民,需要在監(jiān)聽回調(diào)的方法中堰酿,作出大量的if判斷;
  • 當對同一個被監(jiān)聽的屬性進行兩次removeObserver時张足,會導(dǎo)致程序crash触创。這種情況通常出現(xiàn)在父類中有一個KVO,在父類的dealloc中remove一次为牍,而在子類中再次remove哼绑。

二. FBKVOController優(yōu)點

  • 可以同時對一個對象的多個屬性進行監(jiān)聽,寫法簡潔碉咆;
  • 通知不會向已釋放的觀察者發(fā)送消息抖韩;
  • 增加了block和自定義操作對NSKeyValueObserving回調(diào)的處理支持;
  • 不需要在dealloc 方法中手動移除觀察者疫铜,而且移除觀察者不會拋出異常茂浮,當FBKVOController對象被釋放時, 觀察者被隱式移除壳咕;

三.FBKVOController架構(gòu)設(shè)計圖

FBKVOController_00.png

四.FBKVOController源碼詳解

FBKVOController源碼詳解分四部分:分別是對兩個私有類_FBKVOInfo席揽,_FBKVOSharedController和兩個公開類FBKVOController,NSObject+FBKVOController的源碼解讀:

(一)FBKVOController

首先我們創(chuàng)建一個FBKVOController的實例對象時谓厘,有以下三種方法驹尼,一個類方法和兩個對象方法,

//該方法是一個全能初始化的對象方法庞呕,其他初始化方法內(nèi)部均調(diào)用該方法
//參數(shù):observer是觀察者新翎,retainObserved:表示是否強引用被觀察的對象
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved 

//該初始化方法內(nèi)部調(diào)用上一個初始化方法,默認強引用被觀察的對象
- (instancetype)initWithObserver:(nullable id)observer;

//該初始化方法內(nèi)部調(diào)用上一個初始化方法住练,默認強引用被觀察的對象
+ (instancetype)controllerWithObserver:(nullable id)observer;
NS_DESIGNATED_INITIALIZER;

我們先來看全能初始化方法內(nèi)部的實現(xiàn)地啰,該方法對三個實例變量_observer(觀察者),_objectInfosMap(NSMapTable讲逛,被監(jiān)聽對象->被監(jiān)聽屬性集合之間的映射關(guān)系)亏吝,pthread_mutex_init(互斥鎖):

//全能初始化方法
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
  self = [super init];
  if (nil != self) {
      
    //觀察者
    _observer = observer;

//NSMapTable中的key可以為對象,而且可以對其中的key和value弱引用
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
    _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
      
//對于靜態(tài)分配的互斥量, 可以把它設(shè)置為PTHREAD_MUTEX_INITIALIZER
//對于動態(tài)分配的互斥量, 在申請內(nèi)存(malloc)之后, 通過pthread_mutex_init進行初始化, 并且在釋放內(nèi)存(free)前需要調(diào)用pthread_mutex_destroy
    pthread_mutex_init(&_lock, NULL);
  }
  return self;
}

這里請先思考以下問題:

  • 屬性observer為何使用weak盏混,它和哪個對象之間會導(dǎo)致循環(huán)引用問題蔚鸥,是如何導(dǎo)致循環(huán)引用問題的惜论?
  • 為何不使用字典來保存被監(jiān)聽對象和被監(jiān)聽屬性集合之間的關(guān)系?
  • NSDictionary的局限性有哪些止喷?NSMapTable相對字典馆类,有哪些優(yōu)點?
  • 互斥鎖是為了保證哪些數(shù)據(jù)的線程安全弹谁?

帶著這些問題我們來看FBKVOController內(nèi)部是如何實現(xiàn)監(jiān)聽的乾巧,這里我們只看帶Block回調(diào)的一個監(jiān)聽方法,其他幾個方法和這個方法內(nèi)部實現(xiàn)是相同的预愤。下面的方法內(nèi)部做了如下工作:
1.傳入的參數(shù)keyPath沟于,block為空時,程序閃退植康,同時報出誤提示旷太;
2.對傳入?yún)?shù)為空的判讀;
3.利用傳入的參數(shù)創(chuàng)建_FBKVOInfo對象销睁;
4.調(diào)用內(nèi)部私有方法實現(xiàn)注冊監(jiān)聽供璧;

//觀察者監(jiān)聽object中健值路徑(keyPath)所對應(yīng)屬性的變化
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
{
//NSAssert是一個預(yù)處理宏, 它可以讓開發(fā)者比較便捷的捕獲錯誤, 讓程序閃退, 同時報出錯誤提示
  NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);

//首先判斷被監(jiān)聽的對象是否為空,被監(jiān)聽的健值路徑是否為空榄攀,回調(diào)的block是否為空
  if (nil == object || 0 == keyPath.length || NULL == block) {
    return;
  }

  // 根據(jù)傳進來的參數(shù)創(chuàng)建_FBKVOInfo對象嗜傅,將這些參數(shù)封裝到_FBKVOInfo對象中
  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];

  // 監(jiān)聽對象object的屬性信息(_FBKVOInfo對象)
  [self _observe:object info:info];
}

該私有方法內(nèi)部并沒有實現(xiàn)真正的注冊監(jiān)聽金句,這里使用NSMapTable保存了被監(jiān)聽對象object-> _FBKVOInfo對象集合的關(guān)系檩赢,具體的監(jiān)聽是在_FBKVOSharedController類中實現(xiàn)的。觀察者可以監(jiān)聽多個對象违寞,而每個對象中可能有多個屬性被監(jiān)聽贞瞒,其關(guān)系如下圖:


FBKVOController_01.png

內(nèi)部實現(xiàn)思路:

  • 對當前線程訪問的數(shù)據(jù)_objectInfosMap進行加鎖;
  • 根據(jù)被監(jiān)聽對象object到_objectInfosMap取出被監(jiān)聽的屬性信息對象集合infos趁曼;
  • 判斷被監(jiān)聽的屬性對象info是否存在集合中军浆;
  • 如果已經(jīng)存在,則不需要再次添加監(jiān)聽挡闰,防止多次監(jiān)聽乒融;
  • 如果獲取的集合infos為空,則建存放_FBKVOInfo對象的集合infos摄悯,保存映射關(guān)系:object->infos赞季;
  • 將被監(jiān)聽的信息_FBKVOInfo對象存到集合infos中;
  • 解鎖奢驯,其他線程可以訪問該數(shù)據(jù)申钩;
  • 調(diào)用_FBKVOSharedController 的方法實現(xiàn)監(jiān)聽;
//該方法是內(nèi)部私有方法
- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
  //先加鎖瘪阁,訪問_objectInfosMap
  pthread_mutex_lock(&_lock);

    //到_objectInfosMap中根據(jù)key(被監(jiān)聽的對象)獲取被監(jiān)聽的屬性信息集合
  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

   //判斷infos集合中是否存在被監(jiān)聽屬性信息對象info
  _FBKVOInfo *existingInfo = [infos member:info];

    //被監(jiān)聽對象的屬性已經(jīng)存在撒遣,不需要再次監(jiān)聽邮偎,防止多次添加監(jiān)聽
  if (nil != existingInfo) {
  
  //解鎖,其他線程可以再次訪問_objectInfosMap中的數(shù)據(jù)
    pthread_mutex_unlock(&_lock);
    return;
  }

  //根據(jù)被監(jiān)聽對象在_objectInfosMap獲取的被監(jiān)聽屬性信息的集合為空
  if (nil == infos) {
    //懶加載創(chuàng)建存放_FBKVOInfo對象的set集合infos
    infos = [NSMutableSet set];

    //保存被監(jiān)聽對象和被監(jiān)聽屬性信息的映射關(guān)系object->infos
    [_objectInfosMap setObject:infos forKey:object];
  }

  // 將被監(jiān)聽的信息_FBKVOInfo對象存到集合infos中
  [infos addObject:info];

  //解鎖
  pthread_mutex_unlock(&_lock);

   //最終的監(jiān)聽方法是通過_FBKVOSharedController中的方法來實現(xiàn)
  //_FBKVOSharedController內(nèi)部實現(xiàn)系統(tǒng)KVO方法
  [[_FBKVOSharedController sharedController] observe:object info:info];
}
(二)_FBKVOInfo

_FBKVOInfo私有類的內(nèi)部很簡單义黎,沒有任何業(yè)務(wù)邏輯禾进,只是一個簡單的Model,主要是將以下的實例變量封裝到對象中轩缤,方便訪問:

{
@public
//weak命迈,防止循環(huán)引用
  __weak FBKVOController *_controller;
   //被監(jiān)聽屬性的健值路徑
  NSString *_keyPath;

//NSKeyValueObservingOptionNew:觀察修改前的值
// NSKeyValueObservingOptionOld:觀察修改后的值
//NSKeyValueObservingOptionInitial:觀察最初的值(在注冊觀察服務(wù)時會調(diào)用一次觸發(fā)方法)
//NSKeyValueObservingOptionPrior:分別在值修改前后觸發(fā)方法(一次修改有兩次觸發(fā))
  NSKeyValueObservingOptions _options;

//被監(jiān)聽屬性值變化時的回調(diào)方法
  SEL _action;

//上下文信息(void * 任何類型)
  void *_context;
//被監(jiān)聽屬性值變化時的回調(diào)block
  FBKVONotificationBlock _block;
//監(jiān)聽狀態(tài)
  _FBKVOInfoState _state;
}

_FBKVOInfo私有類提供了一個全能初始化方法,來初始化以上實例變量火的。其他幾個部分初始化方法內(nèi)部均調(diào)用該全能初始化方法壶愤。

//全能初始化方法
- (instancetype)initWithController:(FBKVOController *)controller
                           keyPath:(NSString *)keyPath
                           options:(NSKeyValueObservingOptions)options
                             block:(nullable FBKVONotificationBlock)block
                            action:(nullable SEL)action
                           context:(nullable void *)context
{
  self = [super init];
  if (nil != self) {
    _controller = controller;
    _block = [block copy];
    _keyPath = [keyPath copy];
    _options = options;
    _action = action;
    _context = context;
  }
  return self;
}

同時_FBKVOInfo私有類還重寫了isEqual:和hash方法,用來進行_FBKVOInfo對象的判等性馏鹤。當我們在自定義對象時征椒,需要重寫isEqual:和hash方法,作為自定義對象相等性的判斷湃累。

  • 優(yōu)化判斷對象相等性的效率:
    1.首先判斷hash值是否相等勃救,若相等則進行第2步;若不等治力,則直接判斷不等蒙秒;hash值是對象判等的必要非充分條件;(即沒它一定不行宵统,有它不一定行)
    2.在hash值相等的情況下晕讲,再進行對象判等, 作為判等的結(jié)果;
    關(guān)于對象相等性判斷马澈,請看大神Mattt Thompson的一篇博客 Equality瓢省。
//當重寫hash方法時,我們可以將關(guān)鍵屬性的hash值進行位或運算來作為hash值
- (NSUInteger)hash
{
  return [_keyPath hash];
}

/**
 對于基本類型, ==運算符比較的是值;
 對于對象類型, ==運算符比較的是對象的地址(即是否為同一對象)
 */
- (BOOL)isEqual:(id)object
{
    //判斷對象是否為空痊班,若為空勤婚,則不相等
  if (nil == object) {
    return NO;
  }

    //判斷對象的地址是否相等,若相等涤伐,則為同一個對象(即是否為同一個對象)
  if (self == object) {
    return YES;
  }
    
    //判斷是否是同一類型馒胆,這樣可以提高判等的效率, 還可以避免隱式類型轉(zhuǎn)換帶來的潛在風(fēng)險
  if (![object isKindOfClass:[self class]]) {
    return NO;
  }
    
    //對各個屬性分別使用默認判等方法進行判斷
    //返回所有屬性判等的與結(jié)果
  return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
}

//輸出對象的調(diào)試信息
//description: 使用NSLog從控制臺輸出對象的信息
 //debugDescription:通過斷點po打印輸出對象的信息
- (NSString *)debugDescription
{
  NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p keyPath:%@", NSStringFromClass([self class]), self, _keyPath];
  if (0 != _options) {
    [s appendFormat:@" options:%@", describe_options(_options)];
  }
  if (NULL != _action) {
    [s appendFormat:@" action:%@", NSStringFromSelector(_action)];
  }
  if (NULL != _context) {
    [s appendFormat:@" context:%p", _context];
  }
  if (NULL != _block) {
    [s appendFormat:@" block:%p", _block];
  }
  [s appendString:@">"];
  return s;
}
  • 請分析如果將實例變量__weak FBKVOController *_controller前的 __weak去掉,它和_FBKVOInfo對象之間的循環(huán)引用環(huán)是如何形成的凝果?
(三)_FBKVOSharedController

_FBKVOSharedController私有類內(nèi)部實現(xiàn)了系統(tǒng)KVO的方法祝迂,用來接收和轉(zhuǎn)發(fā)KVO的通知。接口中提供了監(jiān)聽和移除監(jiān)聽的方法豆村。其接口如下:

@interface _FBKVOSharedController : NSObject

// 單例初始化方法
+ (instancetype)sharedController;

// 監(jiān)聽object的屬性
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info;

//移除對object中屬性的監(jiān)聽
- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info;

// 移除對object中多個屬性的監(jiān)聽
- (void)unobserve:(id)object infos:(nullable NSSet *)infos;

@end

_FBKVOSharedController私有類內(nèi)部有兩個私有成員變量液兽,_infos是用來存放_FBKVOInfo對象,_infos可以對其中的成員變量弱引用,這也是為何使用NSHashTable四啰,而不使用NSSet來存放_FBKVOInfo對象的原因宁玫。_mutex是互斥鎖:

{
    //存放被監(jiān)聽屬性的信息對象
  NSHashTable<_FBKVOInfo *> *_infos;
    //互斥鎖
  pthread_mutex_t _mutex;
}

_FBKVOSharedController私有類的初始化方法,支持iOS 系統(tǒng)和Mac系統(tǒng)柑晒,初始化實例變量_infos欧瘪,指定了_infos對存放在其中的成員變量弱引用,及判等性方式:

//提供全局的單例初始化方法匙赞,該單例對象的生命周期與程序的生命周期相同
+ (instancetype)sharedController
{
  static _FBKVOSharedController *_controller = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _controller = [[_FBKVOSharedController alloc] init];
  });
  return _controller;
}
//初始化成員變量_infos和_mutex
- (instancetype)init
{
  self = [super init];
  if (nil != self) {
    //初始化實例變量
    NSHashTable *infos = [NSHashTable alloc];
      
   // iOS 系統(tǒng)下:hashTable中的對象是弱引用佛掖,對象的判等方式:位移指針的hash值和直接判等
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
    _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];

  //MAC系統(tǒng)下
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
    if ([NSHashTable respondsToSelector:@selector(weakObjectsHashTable)]) {
      _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
    } else {
      // silence deprecated warnings
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
      _infos = [infos initWithOptions:NSPointerFunctionsZeroingWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
#pragma clang diagnostic pop
    }

#endif
      //初始化互斥鎖
    pthread_mutex_init(&_mutex, NULL);
  }
  return self;
}

- (void)dealloc
{
    //對象被銷毀時,銷毀互斥鎖
  pthread_mutex_destroy(&_mutex);
}

_FBKVOSharedController在這個方法中涌庭,調(diào)用系統(tǒng)KVO方法芥被,將自己注冊為觀察者,思路如下:
1.首先將被監(jiān)聽的信息對象_FBKVOInfo保存到_infos中坐榆;
2.然后調(diào)用系統(tǒng)KVO方法將自己注冊為被監(jiān)聽對象object的觀察者拴魄;
3.最后修改監(jiān)聽的狀態(tài);當不再監(jiān)聽時席镀,安全移除觀察者匹中;

//添加監(jiān)聽
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{
    //被監(jiān)聽的屬性信息_FBKVOInfo對象為空時,直接返回
  if (nil == info) {
    return;
  }

    // 加鎖豪诲,防止多線程訪問時顶捷,出現(xiàn)數(shù)據(jù)競爭
  pthread_mutex_lock(&_mutex);

   // 將被監(jiān)聽的屬性信息info對象添加到_infos中,_infos對成員變量info是弱引用
  [_infos addObject:info];   
 
    //添加完成之后屎篱,解鎖服赎,其他線程可以訪問
  pthread_mutex_unlock(&_mutex);
  
  // 添加監(jiān)聽
  [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];

  //修改監(jiān)聽狀態(tài)
  if (info->_state == _FBKVOInfoStateInitial) {
      
    info->_state = _FBKVOInfoStateObserving;
      
  } else if (info->_state == _FBKVOInfoStateNotObserving) {
      
      //不再監(jiān)聽時安全移除觀察者
    // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
    // and the observer is unregistered within the callback block.
    // at this time the object has been registered as an observer (in Foundation KVO),
    // so we can safely unobserve it.
    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  }
}

實現(xiàn)系統(tǒng)KVO監(jiān)聽回調(diào)的方法

//被監(jiān)聽屬性更改時的回調(diào)
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary<NSString *, id> *)change
                       context:(nullable void *)context
{
  NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);

  _FBKVOInfo *info;
  {
    pthread_mutex_lock(&_mutex);
  //確定_infos是否包含給定的對象context,若存在返回該對象芳室,否則返回nil;
  //所使用的相等性比較取決于所選擇的選項
  //例如专肪,使用NSPointerFunctionsObjectPersonality選項將使用isEqual:方法來判斷相等刹勃。
    info = [_infos member:(__bridge id)context];
    pthread_mutex_unlock(&_mutex);
  }

//通過上下文參數(shù)context傳過來的被監(jiān)聽的_FBKVOInfo對象堪侯,已經(jīng)存在_infos中
  if (nil != info) {
  
 //_FBKVOSharedController對象強引用FBKVOController對象,防止被提前釋放
 //因為在_FBKVOInfo中荔仁,對FBKVOController對象是弱引用
    FBKVOController *controller = info->_controller;
    if (nil != controller) {

      //強引用觀察者伍宦,在FBKVOController中,F(xiàn)BKVOController對象弱引用觀察者observer乏梁,防止在使用時已經(jīng)被釋放
      id observer = controller.observer;
      if (nil != observer) {

        //使用自定義block回傳監(jiān)聽結(jié)果
        if (info->_block) {

          NSDictionary<NSString *, id> *changeWithKeyPath = change;

          //將keyPath添加到字典中以便在觀察多個keyPath時次洼,能夠清晰知道監(jiān)聽的是哪個keyPath
          if (keyPath) {
            NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
            [mChange addEntriesFromDictionary:change];
            changeWithKeyPath = [mChange copy];
          }
          info->_block(observer, object, changeWithKeyPath);

        } else if (info->_action) {
//使用自定義方法回傳監(jiān)聽結(jié)果
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
          [observer performSelector:info->_action withObject:change withObject:object];
#pragma clang diagnostic pop
        } else {
          //使用系統(tǒng)默認方法回傳監(jiān)聽結(jié)果
          [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
        }
      }
    }
  }
}

_FBKVOSharedController實現(xiàn)了移除觀察者的方法,思路如下:
1.首先從_infos中移除被監(jiān)聽的屬性信息對象info遇骑;
2.然后根據(jù)監(jiān)聽狀態(tài)卖毁,通過調(diào)用系統(tǒng)的方法,移除正在被監(jiān)聽的屬性信息對象info;
3.最后修改監(jiān)聽狀態(tài)亥啦;

- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info
{
  if (nil == info) {
    return;
  }

    //先從HashTable中移除被監(jiān)聽的屬性信息對象
  pthread_mutex_lock(&_mutex);
  [_infos removeObject:info];
  pthread_mutex_unlock(&_mutex);

  // 當正在監(jiān)聽時炭剪,則移除監(jiān)聽
  if (info->_state == _FBKVOInfoStateObserving) {
    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  }
    //修改被監(jiān)聽的狀態(tài)
  info->_state = _FBKVOInfoStateNotObserving;
}
(四)NSObject+FBKVOController

NSObject+FBKVOController 分類比較簡單,它主要通過runtime方法翔脱,以懶加載的形式給 NSObject 奴拦,創(chuàng)建并關(guān)聯(lián)一個 FBKVOController 的對象。

@interface NSObject (FBKVOController)
@property (nonatomic, strong) FBKVOController *KVOController;
@property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;
@end

五.FBKVOController總結(jié)

FBKVOController是線程安全的届吁,相對于系統(tǒng)的KVO而言错妖,使用起來更方便,安全疚沐,簡潔暂氯。
1.NSHashTable和NSMapTable的使用;
2.互斥鎖pthread_mutex_t的使用
3.FBKVOController和Observer之間循環(huán)引用的形成和解決亮蛔;
4.FBKVOController和_FBKVOInfo之間循環(huán)引用的形成和解決株旷;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市尔邓,隨后出現(xiàn)的幾起案子晾剖,更是在濱河造成了極大的恐慌,老刑警劉巖梯嗽,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齿尽,死亡現(xiàn)場離奇詭異,居然都是意外死亡灯节,警方通過查閱死者的電腦和手機循头,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炎疆,“玉大人卡骂,你說我怎么就攤上這事⌒稳耄” “怎么了全跨?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長亿遂。 經(jīng)常有香客問我浓若,道長,這世上最難降的妖魔是什么蛇数? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任挪钓,我火速辦了婚禮,結(jié)果婚禮上耳舅,老公的妹妹穿的比我還像新娘碌上。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布馏予。 她就那樣靜靜地躺著蔓纠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吗蚌。 梳的紋絲不亂的頭發(fā)上腿倚,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音蚯妇,去河邊找鬼敷燎。 笑死,一個胖子當著我的面吹牛箩言,可吹牛的內(nèi)容都是我干的硬贯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼陨收,長吁一口氣:“原來是場噩夢啊……” “哼饭豹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起务漩,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤拄衰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饵骨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翘悉,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年居触,在試婚紗的時候發(fā)現(xiàn)自己被綠了妖混。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡轮洋,死狀恐怖制市,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弊予,我是刑警寧澤祥楣,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站块促,受9級特大地震影響荣堰,放射性物質(zhì)發(fā)生泄漏床未。R本人自食惡果不足惜竭翠,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望薇搁。 院中可真熱鬧斋扰,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至问裕,卻和暖如春逮壁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粮宛。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工窥淆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巍杈。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓忧饭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親筷畦。 傳聞我的和親對象是個殘疾皇子词裤,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

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