面向切面編程之 Aspects 源碼解析及應(yīng)用

轉(zhuǎn)摘自面向切面編程

1. 背景

最近在做項目的打點統(tǒng)計的時候葵袭,發(fā)現(xiàn)業(yè)務(wù)邏輯和打點邏輯經(jīng)常耦合在一起涵妥,這樣一方面影響了正常的業(yè)務(wù)邏輯,同時也很容易搞亂打點邏輯眶熬,而且要查看打點情況的時候也很分散妹笆,因此想著如何將兩者解耦,并將打點邏輯集中起來娜氏。其實在 web 編程時候拳缠,這種場景很早就有了很成熟的方案,也就是所謂的 aop 編程(面向切面編程)贸弥,其原理也就是在不更改正常的業(yè)務(wù)處理流程的前提下窟坐,通過生成一個動態(tài)代理類,從而實現(xiàn)對目標(biāo)對象嵌入附加的操作。

在 iOS 中哲鸳,要想實現(xiàn)相似的效果也很簡單臣疑,利用 OC 的動態(tài)性,通過 swizzling method 改變目標(biāo)函數(shù)的 selector 所指向的實現(xiàn)徙菠,然后在新的實現(xiàn)中實現(xiàn)附加的操作讯沈,完成之后再回到原來的處理邏輯。想明白這些之后婿奔,我就打算動手實現(xiàn)缺狠,當(dāng)然并沒有重復(fù)造輪子,我在 github 發(fā)現(xiàn)了一個基于 swizzling method 的開源框架?Aspects?萍摊。這個庫的代碼量比較小挤茄,總共就一個類文件,使用起來也比較方便冰木,比如你想統(tǒng)計某個 controller 的 viewwillappear 的調(diào)用次數(shù)穷劈,你只需要引入 Aspect.h 頭文件,然后在合適的地方初始化如下代碼即可踊沸。

- (void)addKvLogAspect {

[selfwr_Aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^{

? ? ? ?KVLog_ReviewTimeline(ReviewTimeline_Open_Tab);

}error:NULL];

}

這篇文章主要是介紹 Aspects 源碼以及其思路歇终,以及我在實際應(yīng)用中遇到的一些問題。對 swizzling method 不了解的同學(xué)可以先去網(wǎng)上了解一下逼龟,下面的內(nèi)容是基于大家對 swizzling method 有一定的了解的基礎(chǔ)上的练湿。

2. 基本原理

我們知道 OC 是動態(tài)語言,我們執(zhí)行一個函數(shù)的時候审轮,其實是在發(fā)一條消息:[receiver message],這個過程就是根據(jù) message 生成 selector辽俗,然后根據(jù) selector 尋找指向函數(shù)具體實現(xiàn)的指針 IMP疾渣,然后找到真正的函數(shù)執(zhí)行邏輯。這種處理流程給我們提供了動態(tài)性的可能崖飘,試想一下榴捡,如果在運行時,動態(tài)的改變了 selector 和 IMP 的對應(yīng)關(guān)系朱浴,那么就能使得原來的[receiver message]進入到新的函數(shù)實現(xiàn)了吊圾。

那么具體怎么實現(xiàn)這樣的動態(tài)替換了?

直觀的一種方案是提供一個統(tǒng)一入口,如 commonImp ,將所有需要 hook 的函數(shù)都指向這個函數(shù)翰蠢,然后在這里项乒,提取相關(guān)信息進行轉(zhuǎn)發(fā),JSPatch 實現(xiàn)原理詳解對此方案的可行性有進行分析梁沧,對于64位機器可能會有點問題檀何。另外一個方法就是利用 oc 自己的消息轉(zhuǎn)發(fā)機制進行轉(zhuǎn)發(fā),Aspects 的大體思路,基本上是順著這個來的频鉴。為了更好的解釋這個過程栓辜,我們先來看一下消息具體是怎么找到對應(yīng)的 imp 的,見下圖(此圖并非原創(chuàng))垛孔。?

從上面我們可以發(fā)現(xiàn)藕甩,在發(fā)消息的時候,如果 selector 有對應(yīng)的 IMP ,則直接執(zhí)行周荐,如果沒有狭莱,oc 給我們提供了幾個可供補救的機會,依次有?resolveInstanceMethod?羡藐、forwardingTargetForSelector贩毕、forwardInvocation。Aspects 之所以選擇在?forwardInvocation?這里處理是因為仆嗦,這幾個階段特性都不太一樣:resolvedInstanceMethod?適合給類/對象動態(tài)添加一個相應(yīng)的實現(xiàn)辉阶,forwardingTargetForSelector?適合將消息轉(zhuǎn)發(fā)給其他對象處理,相對而言,forwardInvocation?是里面最靈活瘩扼,最能符合需求的况增。因此 Aspects 的方案就是,對于待 hook 的 selector李茫,將其指向?objc_msgForward?/?_objc_msgForward_stret?,同時生成一個新的?aliasSelector?指向原來的 IMP绿鸣,并且 hook 住?forwardInvocation?函數(shù),使他指向自己的實現(xiàn)栽燕。按照上面的思路罕袋,當(dāng)被 hook 的 selector 被執(zhí)行的時候,首先根據(jù) selector 找到了?objc_msgForward?/?_objc_msgForward_stret?,而這個會觸發(fā)消息轉(zhuǎn)發(fā)碍岔,從而進入?forwardInvocation浴讯。同時由于?forwardInvocation?的指向也被修改了,因此會轉(zhuǎn)入新的?forwardInvocation?函數(shù)蔼啦,在里面執(zhí)行需要嵌入的附加代碼榆纽,完成之后,再轉(zhuǎn)回原來的 IMP捏肢。

3. 源碼分析

3.1 數(shù)據(jù)結(jié)構(gòu)

介紹完大致思路之后奈籽,下面將從代碼層來來具體分析。從頭文件中可以看到使用aspects有兩種使用方式:1)類方法 2)實例方法

+ (id)aspect_hookSelector:(SEL)selector? ?withOptions:(AspectOptions)options ?usingBlock:(id)block error:(NSError**)error;

/// Adds a block of code before/instead/after the current `selector` for a specific instance.

- (id)aspect_hookSelector:(SEL)selector? ?withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError**)error;

兩者的主要原理基本差不多鸵赫,這里不做一一介紹衣屏,只是以實例方法為例進行說明。在介紹之前奉瘤,先介紹里面幾個重要的數(shù)據(jù)結(jié)構(gòu):

AspectOptions

typedefNS_OPTIONS(NSUInteger, AspectOptions) {

AspectPositionAfter? =0,/// Called after the original implementation (default)

AspectPositionInstead =1,/// Will replace the original implementation.

AspectPositionBefore? =2,/// Called before the original implementation.

AspectOptionAutomaticRemoval =1<<3/// Will remove the hook after the first execution.

};

這里表示了 block 執(zhí)行的時機勾拉,也就是額外操作的執(zhí)行時機煮甥,在我的應(yīng)用場景中就是打點邏輯的執(zhí)行時機,它可以在原始函數(shù)執(zhí)行之前藕赞,也可以是執(zhí)行之后成肘,甚至可以完全替換掉原來的邏輯。

AspectsContainer

一個對象或者類的所有的 Aspects 整體情況

// Tracks all aspects for an object/class.

@interfaceAspectsContainer:NSObject

- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;

- (BOOL)removeAspect:(id)aspect;

- (BOOL)hasAspects;

@property(atomic,copy)NSArray*beforeAspects;

@property(atomic,copy)NSArray*insteadAspects;

@property(atomic,copy)NSArray*afterAspects;

@end

AspectIdentifier

一個 Aspect 的具體內(nèi)容

@interfaceAspectIdentifier:NSObject

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError**)error;

- (BOOL)invokeWithInfo:(id)info;

@property(nonatomic,assign) SEL selector;

@property(nonatomic,strong)idblock;

@property(nonatomic,strong)NSMethodSignature*blockSignature;

@property(nonatomic,weak)idobject;

@property(nonatomic,assign) AspectOptions options;

@end

這里主要包含了單個的 aspect 的具體信息斧蜕,包括執(zhí)行時機双霍,要執(zhí)行 block 所需要用到的具體信息:包括方法簽名、參數(shù)等等

AspectInfo

一個 Aspect 執(zhí)行環(huán)境批销,主要是 NSInvocation 信息洒闸。

@interfaceAspectInfo:NSObject

- (id)initWithInstance:(__unsafe_unretainedid)instance invocation:(NSInvocation*)invocation;

@property(nonatomic,unsafe_unretained,readonly)idinstance;

@property(nonatomic,strong,readonly)NSArray*arguments;

@property(nonatomic,strong,readonly)NSInvocation*originalInvocation;

@end

3.2 代碼流程

有了上面的了解,我們就能更好的分析整個 apsects 的執(zhí)行流程均芽。添加一個 aspect 的關(guān)鍵流程如下圖所示:

從代碼來看丘逸,要想使用 aspects ,首先要添加一個 aspect 掀宋,可以通過上面介紹的類/實例方法深纲。關(guān)鍵代碼實現(xiàn)如下:

staticidaspect_add(idself, SEL selector, AspectOptions options,idblock,NSError**error) {

? ? ...

__block AspectIdentifier *identifier =nil;

? ? aspect_performLocked(^{

if(aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {//1判斷能否hook

...//2 記錄數(shù)據(jù)結(jié)構(gòu)

aspect_prepareClassAndHookSelector(self, selector, error);//3 swizzling

? ? ? ? }

? ? });

returnidentifier;

}

這個過程基本和上面的流程圖一致,這里重點介紹幾個關(guān)鍵部分劲妙。

3.2.1 判斷能否被 hook

對于對象實例而言湃鹊,這里主要是根據(jù)黑名單,比如 retain forwardInvocation 等這些方法在外部是不能被 hook ,(對于類對象還要確保同一個類繼承關(guān)系層級中镣奋,只能被 hook 一次币呵,因此這里需要判斷子類,父類有沒有被 hook侨颈,之所以做這樣的實現(xiàn)余赢,主要是為了避免出現(xiàn)死循環(huán)的出現(xiàn),這里有相關(guān)的討論)哈垢。如果能夠 hook没佑,則繼續(xù)下面的步驟。

3.2.2 swizzling method

這是真正的核心邏輯温赔,swizzling method 主要有兩部分,一個是對對象的 forwardInvocation 進行 swizzling,另一個是對傳入的 selector 進行 swizzling.

staticvoidaspect_prepareClassAndHookSelector(NSObject*self, SEL selector,NSError**error) {

Class klass = aspect_hookClass(self, error);//1? swizzling forwardInvocation

? ? Method targetMethod = class_getInstanceMethod(klass, selector);

? ? IMP targetMethodIMP = method_getImplementation(targetMethod);

if(!aspect_isMsgForwardIMP(targetMethodIMP)) {//2? swizzling method

...//

? ? }

}

3.2.2.1 swizzling forwardInvocation:

aspect_hookClass 函數(shù)主要 swizzling 類/對象的 forwardInvocation 函數(shù)鬼癣,aspects 的真正的處理邏輯都是在 forwradInvocation 函數(shù)里面進行的陶贼。對于對象實例而言,源代碼中并沒有直接 swizzling 對象的 forwardInvocation 方法待秃,而是動態(tài)生成一個當(dāng)前對象的子類拜秧,并將當(dāng)前對象與子類關(guān)聯(lián),然后替換子類的 forwardInvocation 方法(這里具體方法就是調(diào)用了 object_setClass(self, subclass) ,將當(dāng)前對象 isa 指針指向了 subclass ,同時修改了 subclass 以及其 subclass metaclass 的 class 方法,使他返回當(dāng)前對象的 class。,這個地方特別繞章郁,它的原理有點類似 kvo 的實現(xiàn)枉氮,它想要實現(xiàn)的效果就是志衍,將當(dāng)前對象變成一個 subclass 的實例,同時對于外部使用者而言聊替,又能把它繼續(xù)當(dāng)成原對象在使用楼肪,而且所有的 swizzling 操作都發(fā)生在子類,這樣做的好處是你不需要去更改對象本身的類惹悄,也就是春叫,當(dāng)你在 remove aspects 的時候,如果發(fā)現(xiàn)當(dāng)前對象的 aspect 都被移除了泣港,那么暂殖,你可以將 isa 指針重新指回對象本身的類,從而消除了該對象的 swizzling ,同時也不會影響到其他該類的不同對象)当纱。對于每一個對象而言呛每,這樣的動態(tài)對象只會生成一次,這里 aspect_swizzlingForwardInvocation 將使得 forwardInvocation 方法指向 aspects 自己的實現(xiàn)邏輯 ,具體代碼如下:

staticClass aspect_hookClass(NSObject*self,NSError**error) {

? ? ...

//生成動態(tài)子類坡氯,并swizzling forwardInvocation方法

subclass = objc_allocateClassPair(baseClass, subclassName,0);

aspect_swizzleForwardInvocation(subclass);//swizzling forwardinvation方法

? ? objc_registerClassPair(subclass);

? ? ? ...

object_setClass(self, subclass);//將當(dāng)前self設(shè)置為子類晨横,這里其實只是更改了self的isa指針而已

returnsubclass;

}

...

staticvoidaspect_swizzleForwardInvocation(Class klass) {

? ? ...

IMP originalImplementation = class_replaceMethod(klass,@selector(forwardInvocation:),? ? (IMP)__ASPECTS_ARE_BEING_CALLED__,"v@:@");

if(originalImplementation) {

class_addMethod(klass,NSSelectorFromString(AspectsForwardInvocationSelectorName),? ? ? ? originalImplementation,"v@:@")

? ? ? }

...

}

由于子類本身并沒有實現(xiàn) forwardInvocation ,隱藏返回的 originalImplementation 將為空值廉沮,所以也不會生成 NSSelectorFromString(AspectsForwardInvocationSelectorName) 颓遏。

3.2.2.2 swizzling selector

當(dāng) forwradInvocation 被 hook 之后,接下來滞时,將對傳入的 selector 進行 hook 叁幢,這里的做法是,將 selector 指向了轉(zhuǎn)發(fā) IMP 坪稽,同時生成一個 aliasSelector 曼玩,指向了原來的 IMP ,同時為了放在重復(fù) hook ,做了一個判斷,如果發(fā)現(xiàn) selector 已經(jīng)指向了轉(zhuǎn)發(fā) IMP ,那就就不需要進行交換了窒百,代碼如下

staticvoidaspect_prepareClassAndHookSelector(NSObject*self, SEL selector,NSError**error) {

? ? ...

? ? Method targetMethod = class_getInstanceMethod(klass, selector);

? ? IMP targetMethodIMP = method_getImplementation(targetMethod);

if(!aspect_isMsgForwardIMP(targetMethodIMP)) {

? ? ...

SEL aliasSelector = aspect_aliasForSelector(selector);//generator aliasSelector

if(![klass instancesRespondToSelector:aliasSelector]) {

__unusedBOOLaddedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);

? ? }

class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);// point to? _objc_msgForward

? ...

? ? }

}

3.2.3 handle ForwardInvocation

基于上面的代碼分析知道黍判,轉(zhuǎn)發(fā)最終的邏輯代碼最終轉(zhuǎn)入?__ASPECTS_ARE_BEING_CALLED__函數(shù)的處理中。這里篙梢,需要處理的部分包括額外處理代碼(如打點代碼)以及最終重新轉(zhuǎn)會原來的 selector 所指向的函數(shù)顷帖,其實現(xiàn)代碼如下:

staticvoid__ASPECTS_ARE_BEING_CALLED__(__unsafe_unretainedNSObject*self, SEL selector,NSInvocation*invocation) {

...

// Before hooks.? 原來邏輯之前執(zhí)行

? ? aspect_invoke(classContainer.beforeAspects, info);

? ? aspect_invoke(objectContainer.beforeAspects, info);

// Instead hooks.

BOOLrespondsToAlias =YES;

if(objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {//是否需要替換掉原來的路基

? ? ? ? aspect_invoke(classContainer.insteadAspects, info);

? ? ? ? aspect_invoke(objectContainer.insteadAspects, info);

}else{

? ? ? ? Class klass = object_getClass(invocation.target);

do{

if((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {

[invocation invoke];//根據(jù)aliasSelector找到原來的邏輯并執(zhí)行

break;

? ? ? ? ? ? ? ? }

}while(!respondsToAlias && (klass = class_getSuperclass(klass)));

? ? }

// After hooks.? 原來邏輯之后執(zhí)行

? ? aspect_invoke(classContainer.afterAspects, info);

? ? aspect_invoke(objectContainer.afterAspects, info);

// If no hooks are installed, call original implementation (usually to throw an exception)

if(!respondsToAlias) {//找不到aliasSelector的IMP實現(xiàn),沒有找到原來的邏輯渤滞,進行消息轉(zhuǎn)發(fā)

? ? ? ? ? invocation.selector = originalSelector;

SEL originalForwardInvocationSEL =NSSelectorFromString(AspectsForwardInvocationSelectorName);

if([selfrespondsToSelector:originalForwardInvocationSEL]) {

((void( *)(id, SEL,NSInvocation*))objc_msgSend)(self, originalForwardInvocationSEL, invocation);

}else{

[selfdoesNotRecognizeSelector:invocation.selector];

? ? ? ? }

? ? }? ? ? ? ? ? ? ? ? ?

...

}

依次處理 before/instead/after hook 以及真正函數(shù)實現(xiàn)贬墩。如果沒有找到原始的函數(shù)實現(xiàn),還需要進行轉(zhuǎn)發(fā)操作妄呕。?

4. 遇到的問題

以上就是 Apsects 的實現(xiàn)了陶舞,接下來會介紹在實際應(yīng)用過程中遇到的一些問題以及我的解決方案。

4.1 JSPatch 兼容問題

原因

我們的項目中引入了 JSPatch 作為我們的 hot fix方案绪励。 JSPatch 也會 hook 住對象的 forwradInvocation 方法肿孵,并且 swizzling 相應(yīng)的 method 唠粥,使其指向轉(zhuǎn)發(fā) IMP ,由于 aspects 也是基于這兩者實現(xiàn)的,那么會不會導(dǎo)致問題呢(其實類似的問題也會發(fā)生在對象提前被 kvo 了停做,會不會有影響)晤愧?

回過頭去看3.2.1 我們先是 hook了 類的?forwardInvocation?使其指向了?__ASPECTS_ARE_BEING_CALLED__,然后在 swizzling method 那里雅宾,aspect 有做一個判斷养涮,如果傳入的 selector 指向了轉(zhuǎn)發(fā) IMP ,那么我們什么也不做。因此可想而知眉抬,如果傳入的 selector 先被 JSPatch hook ,那么贯吓,這里我們將不會再處理,也就不會生成 aliasSelector 。

這會導(dǎo)致什么問題了蜀变?設(shè)想一下悄谐,當(dāng) selector 被觸發(fā)的時候,由于 selector 指向了轉(zhuǎn)發(fā) IMP 库北,因此會進入消息轉(zhuǎn)發(fā)過程爬舰,同時由于?forwardInvocation?被 aspects 所 hook ,最終會進入到 aspects 的處理邏輯?__ASPECTS_ARE_BEING_CALLED__?中來。讓我們回過頭去看看3.2.2中的分析寒瓦,由于找不到 aliasSelector 的 IMP 實現(xiàn)情屹,因此會在此進行消息轉(zhuǎn)發(fā)。而在 3.2.2.1 的分析中我們知道杂腰,子類并沒有實現(xiàn)?NSSelectorFromString(AspectsForwardInvocationSelectorName)?垃你,所以這里的流程就會進入?doesNotRecognizeSelector,從而拋出異常喂很。

解決方案

出現(xiàn)上訴問題的原因在于惜颇,當(dāng) aliasSelector 沒有被找到的時候,我們沒能將消息正常的轉(zhuǎn)發(fā)少辣,也就是沒有實現(xiàn)一個?NSSelectorFromString(AspectsForwardInvocationSelectorName)凌摄, 使得消息有機會重新轉(zhuǎn)發(fā)回去的方法。因此解決方案也就呼之欲出了漓帅,我的做法是在對子類的?forwardInvocation?方法進行交換而不僅僅是替換锨亏,實現(xiàn)邏輯如下,強制生成一個?NSSelectorFromString(AspectsForwardInvocationSelectorName)?指向原對象的?forwardInvocation?的實現(xiàn)忙干。


staticClass aspect_hookClass(NSObject*self,NSError**error) {

? ? ...

subclass = objc_allocateClassPair(baseClass, subclassName,0);

? ...

IMP originalImplementation = class_replaceMethod(subclass,@selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__,"v@:@");

if(originalImplementation) {

class_addMethod(subclass,NSSelectorFromString(AspectsForwardInvocationSelectorName),? originalImplementation,"v@:@");

}else{

Method baseTargetMethod = class_getInstanceMethod(baseClass,@selector(forwardInvocation:));

? ? ? ? IMP baseTargetMethodIMP = method_getImplementation(baseTargetMethod);

if(baseTargetMethodIMP) {

class_addMethod(subclass,NSSelectorFromString(AspectsForwardInvocationSelectorName), baseTargetMethodIMP,"v@:@");

? ? ? ? }

? }

...

}

注意如果?originalImplementation?為空屯伞,那么生成的?NSSelectorFromString(AspectsForwardInvocationSelectorName)?將指向 baseClass 也就是真正的這個對象的 forwradInvocation ,這個其實也就是 JSPatch hook 的方法。同時為了保證 block 的執(zhí)行順序(也就是前面介紹的 before hooks / instead hooks / after hooks )豪直,這里需要將這段代碼提前到 after hooks 執(zhí)行之前進行。這樣就解決了 forwardInvocation 在外面已經(jīng)被 hook 之后的沖突問題珠移。

4.2 remove操作

4.2.1 單個aspect remove

單個 aspect 的 remove 貌似有個問題弓乙,先來看看源碼末融。

if(aspect_isMsgForwardIMP(targetMethodIMP)) {

? ? ? SEL aliasSelector = aspect_aliasForSelector(selector);

? ? ? Method originalMethod = class_getInstanceMethod(klass, aliasSelector);

? ? ? IMP originalIMP = method_getImplementation(originalMethod);

if(originalIMP) {

? ? ? ? ? ? class_replaceMethod(klass, selector, originalIMP, typeEncoding);

? ? ? }

}

當(dāng)你對某個 aspect 執(zhí)行 remove 操作的時候,它會直接 replace 這個 selector 的 IMP暇韧,這個操作是對整個類的所有實例都生效的勾习,這會導(dǎo)致什么問題呢?

以類 A 為例懈玻,你先進入了 A 的一個實例 A1 巧婶,hook 住了方法 selector1 ,然后涂乌,并沒有銷毀這個實例的時候艺栈,通過其他路徑又進入類 A 的另一個實例 A2 ,當(dāng)然也 hook 了 selector1 ,然后這個時候湾盒,如果你 A2 中執(zhí)行了這個 aspect 的 remove 操作湿右,按照上面的邏輯,類 A 的 selector1 將會恢復(fù)正常罚勾,可像而知毅人,當(dāng)你退回 A1 的時候, A1 的 aspect 將會失效尖殃。這里其實我的解決思路很簡單丈莺,因為在執(zhí)行 remove 操作的時候,其實和這個對象相關(guān)的數(shù)據(jù)結(jié)構(gòu)都已經(jīng)被清除了送丰,即使不去恢復(fù) selector1 的執(zhí)行缔俄,在進入?__ASPECTS_ARE_BEING_CALLED__?由于這個沒有響應(yīng)的 aspects ,其實會直接跳到原來的處理邏輯,并不會有其他附加影響蚪战。

4.2.2 整個對象aspect remove

還有一個問題就是牵现,aspects 的 remove 操作只能支持單個的 remove 操作,不支持一次性刪除一個對象的所有 aspects 。這里邀桑,也做了一個擴展瞎疼,對原來的 aspects 進行擴展,實現(xiàn)了一次性 remove 一個對象所有 aspects 的方法壁畸。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贼急,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子捏萍,更是在濱河造成了極大的恐慌太抓,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件令杈,死亡現(xiàn)場離奇詭異走敌,居然都是意外死亡,警方通過查閱死者的電腦和手機逗噩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門掉丽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跌榔,“玉大人,你說我怎么就攤上這事捶障∩耄” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵项炼,是天一觀的道長担平。 經(jīng)常有香客問我,道長锭部,這世上最難降的妖魔是什么暂论? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮空免,結(jié)果婚禮上空另,老公的妹妹穿的比我還像新娘。我一直安慰自己蹋砚,他們只是感情好扼菠,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坝咐,像睡著了一般循榆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上墨坚,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天秧饮,我揣著相機與錄音,去河邊找鬼泽篮。 笑死盗尸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的帽撑。 我是一名探鬼主播泼各,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼亏拉!你這毒婦竟也來了扣蜻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤及塘,失蹤者是張志新(化名)和其女友劉穎莽使,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笙僚,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡芳肌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亿笤。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡檬嘀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出责嚷,到底是詐尸還是另有隱情,我是刑警寧澤掂铐,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布罕拂,位于F島的核電站,受9級特大地震影響全陨,放射性物質(zhì)發(fā)生泄漏爆班。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一辱姨、第九天 我趴在偏房一處隱蔽的房頂上張望柿菩。 院中可真熱鬧,春花似錦雨涛、人聲如沸枢舶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凉泄。三九已至,卻和暖如春蚯根,著一層夾襖步出監(jiān)牢的瞬間后众,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工颅拦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蒂誉,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓距帅,卻偏偏與公主長得像右锨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子锥债,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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