詳講Runtime方法交換(class_addMethod ,class_replaceMethod和method_exchangeImplementations)

前言

最近在整理博客,發(fā)現(xiàn)自己之前寫的關(guān)于Runtime攔截替換方法的一篇文章《12- Runtime基礎(chǔ)使用場景-攔截替換方法(class_addMethod ,class_replaceMethod和method_exchangeImplementations)》,大家還是很關(guān)注的,文章大家看完依然疑問,但是由于當(dāng)時生產(chǎn)力不足和后續(xù)的種種原因,并沒有補發(fā)文章。最近工作不忙崖媚,正好結(jié)合自己工作中遇到的Runtime使用場景,補上自己這個坑恤浪。

之前留給大家的疑惑主要有兩點畅哑,如下:

  • 第一,下邊這三個方法的具體作用:

    • class_addMethod
    • class_replaceMethod
    • method_exchangeImplementations
  • 第二资锰,為什么方法交換需要用到class_addMethodclass_replaceMethod這兩個方法敢课?

要回答這兩個問題,我們有必要了解一下OC中的類對象結(jié)構(gòu)和消息機制绷杜,往下看直秆。

類結(jié)構(gòu)

OC中的類對象結(jié)構(gòu)和消息機制包含的內(nèi)容其實很多,避免篇幅過長鞭盟,這里我只簡單的說一下和本文相關(guān)的部分圾结。

首先,在OC中有實例對象齿诉、類對象筝野、元類對象晌姚,如下:

[Student class]; // 是類對象
Student *stu = [Student new]; // p是實例對象
object_getClass([Student class]) // 元類

類對象(即我們?nèi)粘=蟹Q為的),它是基于實例對象的一種抽象定義歇竟,比如說喵咪小花都屬于貓挥唠,那么貓就是一種抽象的概念,定義了貓的外形焕议、活動特定等等屬性宝磨。我們用代碼的方法可以這么定義:

貓 *小花 = [貓 new];

所以O(shè)C中,類對象也是一個定義了一個實例對象包含了哪些方法盅安、屬性唤锉、父類是誰等信息的抽象對象。OC的底層是c/c++實現(xiàn)别瞭,所以O(shè)C中的類結(jié)構(gòu)是采用c++中的結(jié)構(gòu)體來表示的窿祥。下邊是我寫的一個偽結(jié)構(gòu)體,從偽結(jié)構(gòu)體中我們可以知道蝙寨,類對象中有方法列表晒衩、父類這個信息。

struct objc_class {
    Class super_class        // 當(dāng)前類的父類
    struct objc_method_list * * methodLists         // 方法列表 
    //......  其它信息這里忽略
}

(類對象的真實結(jié)構(gòu)并不是這樣籽慢,這里我們也可以忽略它的真實結(jié)構(gòu)浸遗。即便你日后了解了類的真實結(jié)構(gòu),也不會影響到下邊的結(jié)論箱亿。)
super_class 就是當(dāng)前類的父類。
methodLists實際上是一個數(shù)組弃秆,保存著類有哪些方法届惋。這里可以提到元類了,實例對象是一種結(jié)構(gòu)菠赚,而類對象和元類對象是另外一種結(jié)構(gòu)脑豹。關(guān)于類對象和元類對象的區(qū)別,對于本文只要記住一個:對象方法是保存在類對象的methodLists中衡查,而類方法保存在元類的methodLists中瘩欺。

methodLists數(shù)組中保存著結(jié)構(gòu)體method_t,這個結(jié)構(gòu)體包含了我們平時寫的方法的信息拌牲。偽結(jié)構(gòu)體如下:

struct method_t {
    SEL method_name 
    IMP method_imp  
    char * types  
}  

SEL method_name sel就是方法的名字
IMP method_imp imp保存著一個指針俱饿,這個指針指向函數(shù)的具體實現(xiàn)地址。 所以塌忽,方法真正的實現(xiàn)是單獨保存在一個地方的拍埠,它的實現(xiàn)地址交給imp保存。當(dāng)我們執(zhí)行方法的時候土居,實際上是從method_t結(jié)構(gòu)體中找到imp枣购,然后調(diào)用嬉探。
這里有一點很重要,Runtime替換方法實際上就是替換imp棉圈。所以產(chǎn)生了替換了方法之后涩堤,明明你調(diào)用的是methodA,但是執(zhí)行的是methodB的效果分瘾。因為methodA對應(yīng)的method_t結(jié)構(gòu)體中的imp實際保存的是methodB方法的實現(xiàn)地址了胎围。
types 可以簡單的認(rèn)為到能代表這個方法的特定字符,用法我們暫時忽略芹敌。 有一點需要注意痊远,如果你修改了imp為新的imp外,同時修改types改成新方法的types氏捞,這樣才是真正把method_t結(jié)構(gòu)體改成了新的方法碧聪。

消息機制

一般在OC中調(diào)用方法,底層會轉(zhuǎn)成一個objc_msgSend的c++函數(shù)液茎。比如說:[stu instanceMthod]逞姿,底層實際上是

objc_msgSend([Student class], @Seletor(instanceMthod))

表示我們從Person這個類對象結(jié)構(gòu)體中查找instanceMthod這個方法,找到它并且調(diào)用捆等。查找調(diào)用這個方法的過程滞造,我們可以簡單認(rèn)為就是消息機制。 下邊我們簡單說一下消息機制的流程栋烤,還是用[stu instanceMthod]來作為例子:

假如這個方法在Student的父類Person中

  • 首先谒养,實例對象p在對應(yīng)的類對象Student的方法列表中methodLists查找instanceMthod方法,沒有找到明郭。(如果能找到买窟,那么就直接調(diào)用對應(yīng)method_t結(jié)構(gòu)中的imp執(zhí)行方法,結(jié)束查找)
  • 通過類對象Student的superClass找到父類對象Person薯定,在父類的的方法列表中methodLists查找instanceMthod方法始绍,找到了,調(diào)用方法话侄,結(jié)束查找亏推。(如果在父類對象Person、以及Person的父類對象NSObject沒有找到該方法年堆,那么會進入消息轉(zhuǎn)發(fā)的另外兩個階段吞杭,如果這兩個階段還是沒有找到要調(diào)用的方法,那么就會報經(jīng)典錯誤unrecognized selector sent to instance

當(dāng)然這個消息機制的過程是非常簡陋的嘀韧,實際上在進入methodLists查找之前篇亭,會先進入方法緩存cache中查找,有興趣你可以自己多了解一下锄贷。

三個方法的作用

class_addMethod

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) 

作用:就是動態(tài)在類對象cls中添加一個方法译蒂。參數(shù)SEL曼月、IMPtypes我們上邊都提到過柔昼。
注意:如果cls中已經(jīng)有了要添加的方法聲明和方法實現(xiàn)哑芹,那么添加失敗,返回NO捕透。如果沒有聲明和實現(xiàn)方法聪姿,或者只聲明沒有方法實現(xiàn),都可以添加成功乙嘀,返回YES末购。

class_replaceMethod

IMP class_replaceMethod(Class cls, SEL name, IMP newImp, const char *newTypes) 

作用:把類對象中的cls的方法的imp替換成newImp,同時還需要替換newTypes虎谢。

method_exchangeImplementations

void method_exchangeImplementations(Method m1, Method m2)

作用:Method這里可以認(rèn)為就是上邊說到的method_t結(jié)構(gòu)體盟榴,交換m1和m2,實際上就是交換兩個method_t結(jié)構(gòu)體中的IMP和types婴噩。
注意:如果imp為nil擎场,交換操作將失敗。

為什么會用到dispatch_once

dispatch_once我們?nèi)粘W铋L用的就是單例几莽。保證在程序運行過程中迅办,其代碼塊內(nèi)的代碼只執(zhí)行一次。Runtime交換方法之所以會用到dispatch_once章蚣,是為了防止load被手動調(diào)用站欺。 load方法的調(diào)用時機是在main函數(shù)被調(diào)用之前,且只被系統(tǒng)調(diào)用一次纤垂。正常情況下镊绪,我們無需再手動調(diào)用load方法,但是為了防止意外洒忧,所以加了dispatch_once,保證替換方
法的Runtime代碼只能執(zhí)行一次够颠,從而避免方法有替換回去熙侍。

method_exchangeImplementations

一般我們交換方法實現(xiàn)的場景比較明確,比如替換蘋果API中的類的某個方法或者第三方框架中的類的某個方法履磨。
例子如下:

@interface LLPerson : NSObject
- (void)personInstanceMethod;
@end
@implementation LLPerson
- (void)personInstanceMethod{
    NSLog(@"person對象方法, 調(diào)用者:%@ 方法:%s", self,__FUNCTION__);
}
@end


@interface LLPerson (huan)
@end
@implementation LLPerson (huan)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"------// 替換開始 //------");
        NSLog(@"%@", self);
        Method oriMethod = class_getInstanceMethod(self, @selector(personInstanceMethod));
        Method swiMethod = class_getInstanceMethod(self, @selector(new_personInstanceMethod));
        method_exchangeImplementations ( oriMethod, swiMethod) ;
        NSLog(@"------// 替換完畢 //------");
    });
}

- (void)new_personInstanceMethod {
    NSLog(@"Person中的新方法, 調(diào)用者:%@蛉抓, 方法:%s",self,__FUNCTION__);
    [self new_personInstanceMethod];
}
@end

調(diào)用代碼如下:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"------------");
    LLStudent *stu = [LLStudent new];
    [stu personInstanceMethod];
}
@end

結(jié)果如下:

2020-01-07 22:19:27.096634+0800 RunTime1[1972:190371] ------// 替換開始 //------
2020-01-07 22:19:27.097367+0800 RunTime1[1972:190371] LLPerson
2020-01-07 22:19:27.097739+0800 RunTime1[1972:190371] ------// 替換完畢 //------
2020-01-07 22:19:27.203340+0800 RunTime1[1972:190371] ------------
2020-01-07 22:19:27.203516+0800 RunTime1[1972:190371] Person中的新方法, 調(diào)用者:<LLStudent: 0x600003520330>, 方法:-[LLPerson(huan) new_personInstanceMethod]
2020-01-07 22:19:27.203679+0800 RunTime1[1972:190371] person對象方法, 調(diào)用者:<LLStudent: 0x600003520330> 方法:-[LLPerson personInstanceMethod]

這種情況非常簡單剃诅,我們明確的知道了LLPerson中的方法聲明和方法實現(xiàn)巷送,只需要在分類中直接交換就可以了。不需要其它的額外代碼矛辕。 下邊介紹一種特殊的情況笑跛,請往下看付魔。

為什么會用到class_addMethod、class_replaceMethod

下邊要講的這種情況是在我開發(fā)過程中遇到的飞蹂,如果只是用method_exchangeImplementations進行方法交換之后几苍,運行會出現(xiàn)crash。
場景:Person聲明了某個方法并且實現(xiàn)了方法陈哑,然后Student繼承Person妻坝,沒有重寫父類的這個方法,依然不影響直接調(diào)用和使用Person中的這個方法惊窖。 如果此時我們在Student的分類中交換父類的這個方法刽宪,會發(fā)生了什么?
代碼如下:

@interface LLPerson : NSObject
- (void)personInstanceMethod;
@end
@implementation LLPerson
- (void)personInstanceMethod{
    NSLog(@"person對象方法, 調(diào)用者:%@ 方法:%s", self,__FUNCTION__);
}
@end


@interface LLStudent : LLPerson
@end
@implementation LLStudent
@end

@interface LLStudent (huan)
@end
@implementation LLStudent (huan)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    NSLog(@"------// 替換開始 //------");
    NSLog(@"%@", self);
        Method oriMethod = class_getInstanceMethod(self, @selector(personInstanceMethod));
        Method swiMethod = class_getInstanceMethod(self, @selector(studentInstanceMethod));
        method_exchangeImplementations ( oriMethod, swiMethod) ;
        NSLog(@"------// 替換完畢 //------");
    });
}

- (void)studentInstanceMethod {
      NSLog(@"Student中的新方法, 調(diào)用者:%@界酒, 方法:%s",self,__FUNCTION__);
      [self studentInstanceMethod];
}
@end

調(diào)用代碼:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"------------");
    LLStudent *stu = [LLStudent new];
    [stu personInstanceMethod];
    
    NSLog(@"------------");
    LLPerson *person = [LLPerson new];
    [person personInstanceMethod]; 
}
@end

運行結(jié)果:

2020-01-07 22:41:10.744351+0800 RunTime1[2421:244815] ------// 替換開始 //------
2020-01-07 22:41:10.745134+0800 RunTime1[2421:244815] LLStudent
2020-01-07 22:41:10.745427+0800 RunTime1[2421:244815] ------// 替換完畢 //------
2020-01-07 22:41:10.852287+0800 RunTime1[2421:244815] ------------
2020-01-07 22:41:10.852488+0800 RunTime1[2421:244815] Student中的新方法, 調(diào)用者:<LLStudent: 0x600003964160>圣拄, 方法:-[LLStudent(huan) studentInstanceMethod]
2020-01-07 22:41:10.852652+0800 RunTime1[2421:244815] person對象方法, 調(diào)用者:<LLStudent: 0x600003964160> 方法:-[LLPerson personInstanceMethod]
2020-01-07 22:41:10.852797+0800 RunTime1[2421:244815] ------------
2020-01-07 22:41:10.852948+0800 RunTime1[2421:244815] Student中的新方法, 調(diào)用者:<LLPerson: 0x600003958050>, 方法:-[LLStudent(huan) studentInstanceMethod]
2020-01-07 22:41:10.853127+0800 RunTime1[2421:244815] -[LLPerson studentInstanceMethod]: unrecognized selector sent to instance 0x600003958050
// 崩潰

具體的崩潰位置是LLStudent+huan分類中的[self studentInstanceMethod];這一行盾计∈鄣#看到這里,可能大家會有點蒙署辉,會有下邊這幾個問題:

  • 不是已經(jīng)交換了父類的方法嗎族铆,為什么執(zhí)行[person personInstanceMethod]會crash呢?
  • 為什么[stu personInstanceMethod]執(zhí)行之后沒有crash哭尝?
  • 為什么在父類的分類中交換方法就沒有問題呢哥攘?
  • 怎么處理這個問題呢?

分析如下:

問題一:為什么執(zhí)行[person personInstanceMethod]會crash呢材鹦?

注意逝淹,上邊的代碼是在子類Student的分類中把父類的方法和子類的方法進行了交換。交換之后如下:


圖一

當(dāng)執(zhí)行[person personInstanceMethod]時桶唐,實際上是執(zhí)行子類中的studentInstanceMethod方法栅葡,首先調(diào)用NSLog,輸出結(jié)果尤泽。然后調(diào)用studentInstanceMethod方法中的 [self studentInstanceMethod]欣簇。這里要特別注意,NSLog打印出的當(dāng)前self是<LLPerson: 0x600003958050>坯约,所以 [self studentInstanceMethod]實際上就是[person studentInstanceMethod]熊咽,底層實現(xiàn)代碼為

objc_msgSend([LLPerson class], @Seletor(studentInstanceMethod))

用我們上邊提到的消息機制來還原查找方法studentInstanceMethod的過程,類對象LLPerson中的方法列表methodLists中只有一個方法personInstanceMethod闹丐,且這個方法的IMP指向了studentInstanceMethod的實現(xiàn)地址横殴。但是methodLists中根本沒有studentInstanceMethod這個方法,所以經(jīng)過消息機制的三個階段也找不到該方法卿拴,最終報錯-[LLPerson studentInstanceMethod]: unrecognized selector sent to instance 0x600003958050衫仑。

問題二:為什么[stu personInstanceMethod]執(zhí)行之后沒有crash梨与?

[stu personInstanceMethod]底層實現(xiàn)代碼為
底層代碼即

objc_msgSend([Student class], @Seletor(personInstanceMethod))

用消息機制來還原查找方法personInstanceMethod的過程,首先在

  • 首先惑畴,在實例對象stu對應(yīng)的類對象Student的方法列表methodLists中查找personInstanceMethod方法蛋欣,沒有找到。
  • 然后通過類對象Student的superClass找到父類對象Person如贷,在父類的的方法列表methodLists中查找personInstanceMethod方法陷虎,找到了,調(diào)用方法的IMP杠袱,此時是studentInstanceMethod尚猿。首先執(zhí)行NSLog,打印結(jié)果楣富。注意打印結(jié)果中的self是<LLStudent: 0x600003964160>凿掂,所以接下來調(diào)用[self studentInstanceMethod]實際上就是[stu studentInstanceMethod],所以底層實現(xiàn)代碼是
objc_msgSend([Student class], @Seletor(studentInstanceMethod))

按照消息機制的查找過程纹蝴,我們在類對象Student的方法列表methodLists中找到studentInstanceMethod庄萎,然后調(diào)用該方法的IMP,此時是personInstanceMethod塘安。
所以我們可以看到糠涛,[stu personInstanceMethod]這行代碼的運行結(jié)果是:

2020-01-07 22:41:10.852488+0800 RunTime1[2421:244815] Student中的新方法, 調(diào)用者:<LLStudent: 0x600003964160>, 方法:-[LLStudent(huan) studentInstanceMethod]
2020-01-07 22:41:10.852652+0800 RunTime1[2421:244815] person對象方法, 調(diào)用者:<LLStudent: 0x600003964160> 方法:-[LLPerson personInstanceMethod]
問題三:為什么在父類的分類中交換方法就沒有問題呢兼犯?

簡而言之忍捡,就是類對象Person在進行方法交換之前,它的方法列表methodLists中已經(jīng)包含了交換前的方法和交換后的方法切黔,不會存在交換之后砸脊,方法找不到的問題。

問題四:怎么處理這個問題呢纬霞?

首先再次明確我們的目的是為了在Student中將使用的父類方法進行方法交換凌埂。成功的標(biāo)志和直接在父類中的分類中進行方法交換的結(jié)果一樣,如果stu執(zhí)行studentInstanceMethod和personInstanceMethod能夠調(diào)用到對方的實現(xiàn)诗芜,就達到了目的侨舆。 一定要明確這一點。

通過上邊的分析绢陌,我們知道直接使用method_exchangeImplementations的方法實現(xiàn)不了我們想要的目的。解決的方式熔恢,如問題三中提到的那樣脐湾,先讓stu擁有交換前和交換后的方法,然后再進行交換叙淌。

好了秤掌,先看下代碼和運行結(jié)果愁铺,我們再做具體的分析。

@implementation LLStudent (huan)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"------// 替換開始 //------");
        NSLog(@"%@", self);
        Method oriMethod = class_getInstanceMethod(self, @selector(personInstanceMethod));
        Method swiMethod = class_getInstanceMethod(self, @selector(studentInstanceMethod));
        BOOL didAddMethod = class_addMethod(self,
                                            @selector(personInstanceMethod),
                                            method_getImplementation(swiMethod),
                                            method_getTypeEncoding(swiMethod)
                                            );
        if (didAddMethod) {
            class_replaceMethod(self,
                                @selector(studentInstanceMethod),
                                method_getImplementation(oriMethod),
                                method_getTypeEncoding(oriMethod));
        }else{
            method_exchangeImplementations (oriMethod, swiMethod);
        }
        NSLog(@"------// 替換完畢 //------");
    });
}

- (void)studentInstanceMethod {
      NSLog(@"Student中的新方法, 調(diào)用者:%@闻鉴, 方法:%s",self,__FUNCTION__);
      [self studentInstanceMethod];
}
@end
2020-01-08 00:14:35.333082+0800 RunTime1[3674:509474] ------// 替換開始 //------
2020-01-08 00:14:35.333878+0800 RunTime1[3674:509474] LLStudent
2020-01-08 00:14:35.334058+0800 RunTime1[3674:509474] ------// 替換完畢 //------
2020-01-08 00:14:35.441442+0800 RunTime1[3674:509474] ------------
2020-01-08 00:14:35.441627+0800 RunTime1[3674:509474] Student中的新方法, 調(diào)用者:<LLStudent: 0x6000036185e0>茵乱, 方法:-[LLStudent(huan) studentInstanceMethod]
2020-01-08 00:14:35.441779+0800 RunTime1[3674:509474] person對象方法, 調(diào)用者:<LLStudent: 0x6000036185e0> 方法:-[LLPerson personInstanceMethod]
2020-01-08 00:14:35.441893+0800 RunTime1[3674:509474] ------------
2020-01-08 00:14:35.442032+0800 RunTime1[3674:509474] person對象方法, 調(diào)用者:<LLPerson: 0x600003614160> 方法:-[LLPerson personInstanceMethod]

通過打印結(jié)果,可以看到已經(jīng)實現(xiàn)了我們的目的孟岛,并且父類依然可以調(diào)用到瓶竭,沒有崩潰。我們具體分析下:
首先渠羞,執(zhí)行class_addMethod方法

    BOOL didAddMethod = class_addMethod(self,
                                            @selector(personInstanceMethod),
                                            method_getImplementation(swiMethod),
                                            method_getTypeEncoding(swiMethod)
                                            );

結(jié)果didAddMethod = YES斤贰, 我們動態(tài)給類對象Student新添加了personInstanceMethod方法,并且這個方法的IMP是studentInstanceMethod次询。 此時類對象Student方法列表中就包含了交換前和交換后的方法荧恍,而類對象Person的方法列表我們并沒有進行操作,所以不變屯吊,看圖二送巡。


圖二

然后進入if判斷中,執(zhí)行下邊代碼:

class_replaceMethod(self,
                                @selector(studentInstanceMethod),
                                method_getImplementation(oriMethod),
                                method_getTypeEncoding(oriMethod));

類對象Student中的方法studentInstanceMethod的imp和types替換為oriMethod(即personInstanceMethod)的盒卸。此時骗爆,類對象Student中的兩個方法及它們的imp實際如下:

圖三

是不是很熟悉?沒錯世落,和圖一中交換后的類對象Person的方法列表中一樣淮腾。這個時候執(zhí)行[stu personInstanceMethod]就不會crash且實現(xiàn)方法交換的效果了。
小結(jié)一下:如果想要實現(xiàn)方法交換屉佳,那么交換前后的方法必須都在當(dāng)前類對象中有實現(xiàn)才可以谷朝。 所以,AFNetworking和其它一些第三方框架要用到class_addMethod武花、class_replaceMethod兩個方法圆凰,是為了兼顧上邊這種特殊的情況,造成crash体箕。

結(jié)尾

終于把之前的坑補上了专钉。其實在Runtime交換方法的使用過程中還有其它的情況存在,比如說組內(nèi)多個人都對同一個方法進行了交換操作等等累铅,所以我們最好是把這種操作交給一個人或者一個組來統(tǒng)一維護跃须,避免這種情況。

交流

希望能和大家交流技術(shù)

我的博客地址: http://www.lilongcnc.cc/


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末娃兽,一起剝皮案震驚了整個濱河市菇民,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖第练,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阔馋,死亡現(xiàn)場離奇詭異,居然都是意外死亡娇掏,警方通過查閱死者的電腦和手機呕寝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來婴梧,“玉大人下梢,你說我怎么就攤上這事志秃≌颍” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵浮还,是天一觀的道長竟坛。 經(jīng)常有香客問我,道長钧舌,這世上最難降的妖魔是什么担汤? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮洼冻,結(jié)果婚禮上崭歧,老公的妹妹穿的比我還像新娘。我一直安慰自己撞牢,他們只是感情好率碾,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著屋彪,像睡著了一般所宰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上畜挥,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天仔粥,我揣著相機與錄音,去河邊找鬼蟹但。 笑死躯泰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的华糖。 我是一名探鬼主播麦向,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼客叉!你這毒婦竟也來了磕蛇?” 一聲冷哼從身側(cè)響起景描,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秀撇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體向族,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡呵燕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了件相。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片再扭。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖夜矗,靈堂內(nèi)的尸體忽然破棺而出泛范,到底是詐尸還是另有隱情,我是刑警寧澤紊撕,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布罢荡,位于F島的核電站,受9級特大地震影響对扶,放射性物質(zhì)發(fā)生泄漏区赵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一浪南、第九天 我趴在偏房一處隱蔽的房頂上張望笼才。 院中可真熱鬧,春花似錦络凿、人聲如沸骡送。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摔踱。三九已至,卻和暖如春到千,著一層夾襖步出監(jiān)牢的瞬間昌渤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工憔四, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留膀息,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓了赵,卻偏偏與公主長得像潜支,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子柿汛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

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