Runtime窺探 (六)| AOP與Aspects核心源碼

前言

如何把這個世界變得美好湃鹊?把你自己變得更美好

秋天來了

我們這篇博客繼續(xù)來介紹Runtime在開發(fā)中的實際應(yīng)用儒喊,通過開源庫Aspects來對runtime有更好的認識和理解。

一币呵、Aspects庫

這個庫是iOS基于AOP編程思想的開源庫怀愧,用于跟蹤修改一個指定的類的某個方法執(zhí)行前/替換/后,同時可以自定義添加一段代碼塊.對這個類的所有對象都會起作用。

所有的調(diào)用都會是線程安全的.Aspects 使用了Runtime的消息轉(zhuǎn)發(fā)機制以及method swizzling,會有一定的性能消耗.所有對于過于頻繁的調(diào)用,不建議使用 Aspects.Aspects更適用于視圖/控制器相關(guān)的等余赢。并不適用于每秒調(diào)用不超過1000次的代碼.

二芯义、AOP

基本概念:

  • AOP(Aspect Oriented Programming)是面向切面編程。通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術(shù)的編程思想妻柒。

  • 利用AOP可以對業(yè)務(wù)邏輯的各個部分進行隔離扛拨,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性举塔,同時提高了開發(fā)的效率绑警。

主要功能:

  • 日志記錄求泰,性能統(tǒng)計,安全控制计盒,事務(wù)處理渴频,異常處理,事務(wù)的處理等等北启。

主要意圖:

  • 將日志記錄卜朗,性能統(tǒng)計,安全控制暖庄,事務(wù)處理聊替,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來,通過對這些行為的分離培廓,我們希望可以將它們獨立到非指導(dǎo)業(yè)務(wù)邏輯的方法中,進而改變這些行為的時候不影響業(yè)務(wù)邏輯的代碼春叫。

說明:

  • 我們在開發(fā)一個應(yīng)用程序的時候肩钠,把系統(tǒng)分為很多模塊(如:首頁、分類暂殖、購物車价匠、我的等模塊)。我們從立體上看就是一個并列的樹形結(jié)構(gòu)呛每,屬于縱向切入系統(tǒng)踩窖。也就是OOP的的編程思想(面向?qū)ο缶幊趟枷耄鳤OP就屬于橫向切入系統(tǒng)晨横,把整個系統(tǒng)的重復(fù)操的的部分提取出來(Log打印洋腮、日志記錄、應(yīng)用系統(tǒng)的異常捕捉及處理等等)手形,由此可見啥供,AOP是OOP的一個有效補充。

  • 假設(shè)把應(yīng)用程序想成一個立體結(jié)構(gòu)的話库糠,OOP的利刃是縱向切入系統(tǒng)伙狐,把系統(tǒng)劃分為很多個模塊(如:用戶模塊,文章模塊等等)瞬欧,而AOP的利刃是橫向切入系統(tǒng)贷屎,提取各個模塊可能都要重復(fù)操作的部分(如:權(quán)限檢查,日志記錄等等)艘虎。

注意:

  • AOP不是一種具體代碼實現(xiàn)的技術(shù)唉侄,實際上是編程思想。凡是符合AOP思想的技術(shù)編程顷帖,都可以看成是AOP的實現(xiàn)美旧。

三渤滞、解析Aspects庫

我們經(jīng)常用的Method Swizzling就是一種AOP思想實現(xiàn),Aspects是比較很棒的基于AOP編程思想的開源庫榴嗅,
由于Aspects的代碼較多妄呕,我們只是來閱讀Aspects的核心實現(xiàn)思路和流程。

1.Aspects的基本模塊

  • AspectInfo

    • Aspect信息類:用來保存信息的嗽测,存放被hook的實例绪励、方法、參數(shù)等
  • AspectIdentifier

    • Aspect標(biāo)識類:用來追蹤一個唯一的aspect唠粥,AspectIdentifier對應(yīng)的實例疏魏,里面會包含了單個的 Aspect 的具體信息,包括執(zhí)行時機晤愧,要執(zhí)行 block 所需要用到的具體信息:包括方法簽名大莫、參數(shù)等等。初始化AspectIdentifier的過程實質(zhì)是把我們傳入的block打包成AspectIdentifier官份。
  • AspectsContainer

    • 是一個用來存儲所有的aspect的容器只厘,可能存儲實例方法/類方法。所以會有兩種容器
  • AspectTracker

    • AspectTracker來跟蹤要被hook的類

上面這些模塊都是用來輔助核心思想實現(xiàn)的舅巷,使開源庫模塊清晰羔味、較高容錯率钠右、職責(zé)明確等等赋元,這些模塊還是比較好理解的飒房,就不一一閱讀了。其實很多優(yōu)秀開源庫都會有類似的模塊(比如信息情屹、容器坪仇、唯一標(biāo)識等等)。下面我們主要了解Aspects的核心思想以及流程垃你。

2.小插曲

為什么大多數(shù)開源庫都會有這些模塊椅文?舉個例子:

和女朋友一起去溜一堆狗......

  • 我們怎么來控制一堆狗不亂跑呢惜颇?那就用繩子把狗拴起來,把繩子握在手里或者你想綁在腿上凌摄,這時候手或腿就是來控制所有狗的工具羡蛾,也就是我們所說的容器(用來保存所有的個體信息)。

  • 我們怎么來看這個狗的名字锨亏、品種痴怨、年齡忙干?我們可以制作一個標(biāo)簽掛在狗脖子上浪藻,上面寫著狗的基本信息捐迫。也就是我們所說的信息模塊(用來存儲個體基本信息)爱葵。

  • 現(xiàn)在女朋友要溜xx這條狗施戴,怎么給她萌丈?就需要一個唯一標(biāo)識狗個體的工具,那我們可以在繩子上有個編號便簽辆雾,也就是我們所說的唯一標(biāo)識模塊(標(biāo)識某個個體,包含基本信息以及其他輔助信息)乾颁。這樣我們可以通過編號就可以找到對應(yīng)的狗涂乌,而且不會找錯英岭,這樣就可以愉快的來遛狗湿右。

  • 上面這些工作都是來輔助我們遛狗(核心模塊)

3.Aspects對外接口以及基本說明

通過源代碼Aspects中可以看到下面兩個對外公開接口用于hook selector

@interface NSObject (Aspects)

/***********************
第一個參數(shù)selector:是要給它增加切面的原方法
第二個參數(shù)是AspectOptions:是代表這個切片增加在原方法的before / instead / after
第三個入?yún)lock:這個block復(fù)制了正在被hook的方法的簽名signature類型
第一個參數(shù)selector將返回一個遵循<AspectInfo>的id對象,這個對象繼承了方法的所有參數(shù)毅人,
這些參數(shù)都會被填充到匹配的block的簽名里
你也可以使用一個空block,或者一個簡單的id<AspectInfo>
不支持hook靜態(tài)static方法的
返回一個可以用來撤銷aspect的token
***********************/

//hook類方法丈莺,hook一個類的所有實例對應(yīng)的一個方法
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;
//hook實例方法,hook類的一個具體實例對應(yīng)的一個方法
//為一個具體實例的seletor的執(zhí)行 之前/或者被替換/之后 添加一個block代碼
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

@end

方法說明:

  • 第一個方法為類方法:也就是說接受者是一個要被hook的類缔俄,也就是說hook一個類的所有實例對應(yīng)的一個方法。會對類進行消息轉(zhuǎn)發(fā)和method swizzling俐载。會對類中methodLists的兩個方法進行修改:

    • 1.forwardInvocation:
      forwardInvocation:的IMP(方法實現(xiàn))被替換為:__ASPECTS_ARE_BEING_CALLED__,這個函數(shù)內(nèi)部具體執(zhí)行被hook的selector和切入操作的實現(xiàn)遏佣。forwardInvocation: 的本來的IMP被保存在__aspects_forwardInvocation:中。在調(diào)用aspect_hookClass()函數(shù)會進行forwardInvocation:的替換

    • 2.要被hook的selector:要被hook的selector的IMP被替換為:_objc_msgForward/_objc_msgForward_stret状婶,這個函數(shù)用于直接觸發(fā)消息轉(zhuǎn)發(fā)機制馅巷,而不會在methodLists查找函數(shù)來執(zhí)行草姻。被hook的selector的原始IMP被保存在方法aspects_selector中钓猬。在調(diào)用aspect_prepareClassAndHookSelector()函數(shù)會進行selector的替換

    • 3.當(dāng)我們調(diào)用[objc message];時就直接觸發(fā)消息轉(zhuǎn)發(fā)機制調(diào)用forwardInvocation:方法碴倾,而實現(xiàn)就是__ASPECTS_ARE_BEING_CALLED__這個函數(shù)(真正執(zhí)行的函數(shù)實體),從而實現(xiàn)selector的hook跌榔。

  • 第二個方法為實例方法:也就是說接受者是一個要被hook的類的實例對象,也就是說hook類的一個具體實例對應(yīng)的一個方法僧须。這里跟上面區(qū)別主要是新建子類用來操作hook,具體步驟如下:
    • 1.新建一個要被hook的類的子類:xxx__Aspects_
    • 2.把要hook的實例的isa指向上面新生成的子類xxx__Aspects_担平,也就是說當(dāng)前這個實例變成了子類xxx__Aspects_的實例對象。
    • 3.對上面新建子類xxx__Aspects_進行消息轉(zhuǎn)發(fā)和method swizzling暂论。具體思路就是和上面的類方法流程一樣面褐。

上面是Aspects的核心思想以及流程的簡單說明,下面我們對這些核心代碼進行梳理介紹取胎。

注意:

  • Aspects并不能hook類的類方法
  • Aspects不能hook靜態(tài)方法
  • Aspects不能hook類中不存在或者未實現(xiàn)的方法

4.Aspects核心代碼解析

對外接口源碼

1.上圖說明:

  • 不管調(diào)用hook類的的實例方法還是類方法展哭,在函數(shù)內(nèi)都會調(diào)起私有c函數(shù)static id aspect_add()來進行統(tǒng)一處理
  • 使用自旋鎖來保證線程安全執(zhí)行
  • 然后會進行前期準(zhǔn)備工作處理(如黑名單排除、生成標(biāo)識實例以及添加匪傍、block塊的函數(shù)簽名和轉(zhuǎn)換處理等等),這些通過源碼還是比較好理解的
  • 把前期準(zhǔn)備工作做完后役衡,就會調(diào)用函數(shù)aspect_prepareClassAndHookSelector(self, selector, error);來進行核心模塊實現(xiàn)

下面我們對函數(shù)aspect_prepareClassAndHookSelector(self, selector, error);來進行查看源碼

2.核心函數(shù)aspect_prepareClassAndHookSelector()

函數(shù)aspect_prepareClassAndHookSelector()的具體實現(xiàn)如下:

aspect_prepareClassAndHookSelector
  • 函數(shù)主要分類兩部分處理:
    • 一個是hook Class的處理薪棒,這一部分主要實現(xiàn)上面提到的forwardInvocation:函數(shù)的替換具體過程
    • 一個是hook selector的處理手蝎,這一部分主要實現(xiàn)上面提到的要把被hook的selector的函數(shù)實現(xiàn)替換成_objc_msgForward/_objc_msgForward_stret盗尸,直接觸發(fā)消息轉(zhuǎn)發(fā)機制調(diào)用forwardInvocation:

3.hook Class過程

我們先來看一下核心函數(shù)aspect_prepareClassAndHookSelector()中的第一部分hook Class過程,這個會調(diào)用aspect_hookClass函數(shù)

aspect_hookClass
  • 上面根據(jù)self獲取類對象/元類的Class

  • 先對當(dāng)前類進行篩選泼各,如果當(dāng)前Class是以xxx_Aspects_后綴結(jié)尾的名稱,說明這個Class已經(jīng)被hook過了,不需要再進行下面重復(fù)處理及塘,直接返回當(dāng)前Class锐极,去執(zhí)行hook selector的過程(后面會說)

  • 然后再看baseClass是不是元類笙僚,object_getClass(self)獲取self的isa指針灵再。如果當(dāng)前是類對象,則class_isMetaClass(baseClass)是元類翎迁,說明當(dāng)前hook的是某一個類的所有實例的對應(yīng)方法。直接調(diào)用函數(shù)aspect_swizzleClassInPlace()(后面介紹)來method swizzling函數(shù)forwardInvocation:

  • 判斷當(dāng)前實例對象的isa指向statedClass和baseClass汪榔,按理說當(dāng)self為實例變量時,object_getClass(self)與[self class]輸出結(jié)果一直痴腌,均獲得isa指針,即指向類對象的指針士聪。但是這里判斷相不相等?我們上一篇博客說過KVO過的對象的isa會指向一個中間類NSKVONotifying_XXX剥悟,所以說不相等時,說明這個實例對象是被KVO觀察的對象懦胞。直接調(diào)用函數(shù)aspect_swizzleClassInPlace()來method swizzling函數(shù)forwardInvocation:

  • 上面情況都排除了凉泄,說明hook的是某一個類的實例的對應(yīng)方法,下面就是hook類方法和實例方法的區(qū)別了

    • 1.新建當(dāng)前self的所屬類的子類:xxx__Aspects_
    • 2.調(diào)用aspect_swizzleForwardInvocation()替換函數(shù)forwardInvocation:的實現(xiàn)
    • 3.調(diào)用函數(shù)aspect_hookedGetClass()后众,把新建子類xxx__Aspects_的isa指向self的所屬類,把新建子類xxx__Aspects_的元類的isa指向self的所屬類
    • 4.上面完成后蒂誉,注冊新類說明新建子類創(chuàng)建完畢
    • 5.把當(dāng)前self的isa指向新建子類xxx__Aspects_,成功的把self hook成了其子類 xxx_Aspects_右锨,也就是self所屬類是xxx_Aspects_,而不再是原始類xxx了。

上面就是整個hook Class的過程讥电,流程圖如下:

hook Class

上面都會調(diào)用有一個函數(shù)aspect_swizzleClassInPlace轧抗,這個函數(shù)的作用就是我們來替換Class系統(tǒng)方法forwardInvocation:的實現(xiàn)恩敌,源代碼如下

//替換類的快速消息轉(zhuǎn)發(fā)方法横媚,并把類添加到交換類的集合中
static Class aspect_swizzleClassInPlace(Class klass) {
    NSCParameterAssert(klass);
    NSString *className = NSStringFromClass(klass);

    _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
        if (![swizzledClasses containsObject:className]) {
            //不包含,就調(diào)用aspect_swizzleForwardInvocation()方法灯蝴,并把className加入到Set集合里面。
            aspect_swizzleForwardInvocation(klass);
            [swizzledClasses addObject:className];
        }
    });
    return klass;
}

//類的forwardInvocation方法替換為__ASPECTS_ARE_BEING_CALLED__的實現(xiàn)绽乔,返回新函數(shù)imp
static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);

    //替換類中已有方法的實現(xiàn),返回原來函數(shù)imp
    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
    
    if (originalImplementation) {
        //originalImplementation不為空的話說明原方法有實現(xiàn)折砸,添加一個新方法保存原來類的ForwardInvocation方法實現(xiàn)
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
  • 從上面代碼中看出,把傳入的Class加入到集合中用于hook完成后若需移除時用到睦授,同時調(diào)用函數(shù)aspect_swizzleForwardInvocation()
  • 函數(shù)aspect_swizzleForwardInvocation()中可以看到:使用class_replaceMethod方法把Class的forwardInvocation:函數(shù)實現(xiàn)替換成了函數(shù)__ASPECTS_ARE_BEING_CALLED__,這個才是真正的函數(shù)執(zhí)行入口。同時把類的原始forwardInvocation:函數(shù)實現(xiàn)保存在了__aspects_forwardInvocation:,用于后面hook selector不成功時怖辆,調(diào)用原始的forwardInvocation:函數(shù)來執(zhí)行或者拋出異常等。

hook Class總結(jié):到這里就把hook Class的過程解析完成了竖螃,說到底過程就是處理要被hook的類,同時把類的消息轉(zhuǎn)發(fā)方法forwardInvocation:替換成__ASPECTS_ARE_BEING_CALLED__函數(shù)特咆。

4.hook selector過程

在核心函數(shù)aspect_prepareClassAndHookSelector()中的hook Class處理在上面已經(jīng)解析完成,現(xiàn)在我們來繼續(xù)往下解析hook selector腻格。這一部分源碼如下:

Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
    //當(dāng)前imp不是消息轉(zhuǎn)發(fā)方法
    //獲取當(dāng)前原始的selector對應(yīng)的IMP的方法編碼typeEncoding
    const char *typeEncoding = method_getTypeEncoding(targetMethod);
    
    //給原始方法添加一個前綴名"aspects__XX"
    SEL aliasSelector = aspect_aliasForSelector(selector);
    
    if (![klass instancesRespondToSelector:aliasSelector]) {
        //沒有找到新方法"aspects__XX"啥繁,就添加一個新方法
        __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
        NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
    }
    
    //我們使用消息轉(zhuǎn)發(fā)forwardInvocation來進行hook
    //把當(dāng)前的sel方法的替換成forwardInvocation方法菜职,selector被執(zhí)行的時候旗闽,直接會觸發(fā)消息轉(zhuǎn)發(fā)從而進入forwardInvocation
    class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
    AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
  • 1.獲取要被hook的方法Method以及函數(shù)實現(xiàn)IMP指針

  • 2.判斷當(dāng)前方法實現(xiàn)是不是消息轉(zhuǎn)發(fā)方法蜜另,如果是直接返回不作處理,hook不成功蚕钦。不是繼續(xù)往下執(zhí)行3

  • 3.獲取原始的selector對應(yīng)的IMP的方法編碼typeEncoding鹅很,以及給原始方法添加一個前綴名"aspects__XX"嘶居,獲取SEL促煮,這個sel保存了原始方法的實現(xiàn)。這樣才hook的過程中菠齿,如果不成功會調(diào)用這個sel,走原始代碼绳匀,不會影響正常函數(shù)。

  • 4.如果沒有找到這個方法就調(diào)用class_addMethod給這個Class添加一個新方法.

  • 5.調(diào)用class_replaceMethod函數(shù)來把selector的函數(shù)實現(xiàn)替換成forwardInvocation:,這樣調(diào)用selector時就回走forwardInvocation:函數(shù)疾棵,而這個函數(shù)在hook Class的過程中也被替換成__ASPECTS_ARE_BEING_CALLED__函數(shù),真正實現(xiàn)的函數(shù)入口就是這個是尔。

hook selector總結(jié):說到底這個過程就是處理要被hook的selector,把selector方法的實現(xiàn)替換成的消息轉(zhuǎn)發(fā)方法forwardInvocation:拟枚。

5.舉例說明核心流程:

  • 新建類A
  • 給類A添加一個對象方法method以及實現(xiàn)
  • 初始化一個A類實例對象a

我們準(zhǔn)備hook初始化階段調(diào)用下面代碼:

[a aspect_hookSelector:@selector(method) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo>aspectInfo,){
        NSLog(@"arguments = %@",aspectInfo.arguments);    
} error:NULL];
  • 1.因為hook的是實例方法,所以在hook Class的時候會新建子類:A__Aspects_(中間類)
  • 2.調(diào)用aspect_swizzleForwardInvocation()把A__Aspects_類的forwardInvocation:函數(shù)實現(xiàn)替換成__ASPECTS_ARE_BEING_CALLED__恩溅,
  • 3.把A__Aspects_的isa指向A,也就是把A__Aspects_類和A類一模一樣脚乡。
  • 4.把self,也就是a的所屬類變成A__Aspects_類每窖。
  • 5.給A__Aspects_類的添加一個和原始要被hook的方法一樣的函數(shù)弦悉,用于保存原始方法的實現(xiàn)
  • 6.替換A__Aspects_類的要被hook的方法,替換成forwardInvocation:函數(shù)

我們在hook執(zhí)行過程調(diào)用下面代碼:

[a method];
  • 因為在hook初始化階段時稽莉,把method替換成了forwardInvocation:函數(shù)。forwardInvocation:函數(shù)又被替換成了__ASPECTS_ARE_BEING_CALLED__函數(shù)。所以[a method]的函數(shù)實現(xiàn)就是__ASPECTS_ARE_BEING_CALLED__函數(shù)劈猪。
  • __ASPECTS_ARE_BEING_CALLED__函數(shù)就是我們hook切入的具體操作。如果我們hook沒有成功時战得,也會調(diào)用原始的selector方法或者拋出異常,不會影響正常函數(shù)的實現(xiàn)常侦。

5.Aspects總結(jié)

Aspects核心思想就是通過runtime的消息轉(zhuǎn)發(fā)機制和method swizzling生成中間類來替換函數(shù)實現(xiàn)。這種思想和上一篇KVO的底層實現(xiàn)很相似聋亡。可以仔細閱讀里面的代碼坡倔,學(xué)習(xí)相關(guān)的實現(xiàn)思想以及優(yōu)秀的代碼片段。

我沒有把所有的代碼都一一解析罪塔,如果想看代碼注釋的,博客最后會有注釋的項目地址垢袱。我對Aspects的庫的中文注釋以及理解說明,有興趣的可以下載看下请契。

有注釋的Aspects地址:https://git.coding.net/Dely/JYAOPDemo.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涌韩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臣樱,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雇毫,死亡現(xiàn)場離奇詭異踩蔚,居然都是意外死亡棚放,警方通過查閱死者的電腦和手機馅闽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門馍迄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人攀圈,你說我怎么就攤上這事∽咐矗” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵撕捍,是天一觀的道長。 經(jīng)常有香客問我泣洞,道長忧风,這世上最難降的妖魔是什么球凰? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮呕诉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘甩挫。我一直安慰自己,他們只是感情好伊者,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著亦渗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪法精。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天搂蜓,我揣著相機與錄音,去河邊找鬼帮碰。 笑死,一個胖子當(dāng)著我的面吹牛收毫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播此再,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼摘符!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起策吠,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎带族,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝙砌,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年择克,在試婚紗的時候發(fā)現(xiàn)自己被綠了前普。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肚邢。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡拭卿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出记劈,到底是詐尸還是另有隱情,我是刑警寧澤目木,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站刽射,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏誓禁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一摹恰、第九天 我趴在偏房一處隱蔽的房頂上張望怒见。 院中可真熱鬧,春花似錦遣耍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纪隙。三九已至,卻和暖如春绵咱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背麸拄。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拢切,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓淮椰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親主穗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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