iOS-底層原理12:消息流程分析之 動態(tài)方法決議 & 消息轉(zhuǎn)發(fā)

在上一篇文章iOS-底層原理11:消息流程分析之慢速查找 中夸溶,分析了消息慢速查找流程足绅,如果查找不到將進行動態(tài)方法決議,如果動態(tài)方法決議仍然沒有找到實現(xiàn),則進行消息轉(zhuǎn)發(fā)粥航。

案例

step1: 新建一個LBHPerson類缀程,定義一個實例方法instanceMethod1和一個類方法classMethod1摆昧,只聲明不實現(xiàn)

//.h 
@interface LBHPerson : NSObject

- (void)instanceMethod1;
+ (void)classMethod1;

@end

//.m

@implementation LBHPerson

@end

step2:main函數(shù)中調(diào)用LBHPerson類的實例方法instanceMethod1

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        LBHPerson *person = [LBHPerson alloc];
        
        [person instanceMethod1];
        
    }
    return 0;
}

step3: 運行結(jié)果

調(diào)用類方法

[LBHPerson classMethod1];

運行結(jié)果

unrecognized selector sent to instance 0xxxxx 找不到方法實現(xiàn)

這是一個開發(fā)中很常見的奔潰問題伪嫁,先學(xué)習(xí)這篇文章晶伦,然后用動態(tài)方法決議和消息轉(zhuǎn)發(fā)解決這個問題。

1. 動態(tài)方法決議

動態(tài)方法決議:慢速查找流程未找到方法,會給一次機會

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
  
    // 對象方法
    if (! cls->isMetaClass()) {
        resolveInstanceMethod(inst, sel, cls);
    } 
    // 類方法
    else {
        resolveClassMethod(inst, sel, cls);
//為什么要有這行代碼? -- 類方法在元類中是對象方法,所以還是需要查詢元類中對象方法的動態(tài)方法決議
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // 重新查詢一次
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

分為以下幾步:

part1: 判斷cls是否是元類

  • 如果是蝇庭,調(diào)用實例方法的動態(tài)方法決議resolveInstanceMethod
  • 如果是元類北发,調(diào)用類方法的動態(tài)方法決議resolveClassMethod逼蒙,如果在元類沒有找到或者為空陕截,則在元類實例方法的動態(tài)方法決議resolveInstanceMethod中查找, 是因為類方法存儲在元類中合呐,是元類的實例方法拆祈,所以還需要查找元類中實例方法的動態(tài)方法決議

part2: 如果動態(tài)方法決議中淤年,將其實現(xiàn)指向了其他方法余素,則繼續(xù)查找指定的imp洛搀,即繼續(xù)慢速查找lookUpImpOrForward流程

此時 behavior = 1, LOOKUP_CACHE = 4谎砾,lookUpImpOrForward函數(shù)中形參behavior變成了 1 | 4 = 5 挚币,這決定了進入lookUpImpOrForward后:

  • fastpath(behavior & LOOKUP_CACHE) = 5 & 4 = 4凄吏,條件成立任连,會優(yōu)先cache_getImp讀取一次緩存
  • slowpath(behavior & LOOKUP_RESOLVER) = 5 & 2 = 0拱她,條件成立唬复,不會進入resolveMethod_locked動態(tài)方法決議。
  • lookUpImpOrForward會循環(huán)遍歷cls繼承鏈的所有類的cache和methodList來尋找imp

流程圖:

1.1 實例方法決議

step1: 實例方法在快速查找 -> 慢速查找 都沒有找到的情況下壤巷,會走到 resolveInstanceMethod 方法,源碼如下:

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    // 1. 查找元類對象(cls->ISA())的類中是否有`resolveInstanceMethod`的imp悲没。
    // (根元類中默認實現(xiàn)了`resolveInstanceMethod`方法,所以永遠不會return)
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        return;
    }
    
    // 2. 發(fā)送resolve_sel消息
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // 3. 再搜索一次sel的imp
    //(如果在上面resolveInstanceMethod函數(shù)實現(xiàn)了sel,我們就拿到imp了褂痰,成功將sel和imp寫入cls的緩存中)
    IMP imp = lookUpImpOrNil(inst, sel, cls);
    
    // 做Log記錄
    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

分步解析:

part1: 查找resolve_sel

if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
}

問題lookUpImpOrNil到底做了什么?
解答:
查看lookUpImpOrNil源碼

static inline IMP
lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
{
   // behavior = 0, LOOKUP_CACHE = 4屋厘, LOOKUP_NIL = 8
   return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
}

可以看到在lookUpImpOrNil中又調(diào)用了lookUpImpOrForward慢速查找流程

lookUpImpOrForward函數(shù)中形參behavior變成了 0 | 4 | 8 = 12瞻凤, 這決定了進入lookUpImpOrForward后:

  • fastpath(behavior & LOOKUP_CACHE) = 12 & 4 = 4肝集,條件成立,會優(yōu)先cache_getImp讀取一次緩存
  • slowpath(behavior & LOOKUP_RESOLVER) = 12 & 2 = 0忧吟,條件成立劣像,不會進入resolveMethod_locked動態(tài)方法決議摧玫。
  • lookUpImpOrNil中的lookUpImpOrForward會循環(huán)遍歷cls繼承鏈的所有類的cache和methodList來尋找imp

判斷能否在慢速查找流程中找到resolveInstanceMethod方法實現(xiàn)。實際上根本不會進入if條件,因為在NSObject元類存在resolveInstanceMethod類方法。

問題:為什么不會進if條件榜配? NSObject元類中存在resolveInstanceMethod類方法能證明嗎?

解答

/// 遍歷方法
-(void) printMethodes: (Class)cls {
   // 記錄函數(shù)個數(shù)
   unsigned int count = 0;
   // 讀取函數(shù)列表
   Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i < count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = class_getMethodImplementation(cls, sel);
        NSLog(@"method: %@-%p", NSStringFromSelector(sel), imp);
    }
    free(methodList);
}

//調(diào)用
[self printMethodes:objc_getMetaClass("NSObject")];

運行結(jié)果

NSObject元類方法列表中可以找到resolveInstanceMethod類方法

part2: 發(fā)送resolve_sel消息

// 2. 消息發(fā)送
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);

part3: 通過慢速查找流程獲取用戶調(diào)用的方法sel(demo中為instanceMethod1)的方法實現(xiàn)imp辆苔,此處的獲取是為了日志使用

IMP imp = lookUpImpOrNil(inst, sel, cls);
奔潰修改

step1:LBHPerson中新增一個lbhInstanceMethod的實例方法,聲明并實現(xiàn)

//.h
@interface LBHPerson : NSObject

- (void)lbhInstanceMethod;

@end


//.m
@implementation LBHPerson

- (void)lbhInstanceMethod
{
    NSLog(@"%s",__func__);
}

@end

step2:LBHPerson類中重寫resolveInstanceMethod類方法

@implementation LBHPerson

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    NSLog(@"%@ 來了", NSStringFromSelector(sel));
    
    if (sel == @selector(instanceMethod1)) {
        
        //獲取lbhInstanceMethod方法的imp
        IMP imp = class_getMethodImplementation(self, @selector(lbhInstanceMethod));
        //獲取lbhInstanceMethod的實例方法
        Method lbhInstanceMethod  = class_getInstanceMethod(self, @selector(lbhInstanceMethod));
        //獲取lbhInstanceMethod的豐富簽名
        const char *type = method_getTypeEncoding(lbhInstanceMethod);
        //將sel的實現(xiàn)指向lbhInstanceMethod
        
        return class_addMethod(self, sel, imp, type);
    }
    
    return [super resolveInstanceMethod:sel];
    //    return NO;
}

@end

運行

崩潰解決了啄寡,實際上這么寫是比較雞肋的,都已經(jīng)知道某個方法沒有實現(xiàn),那直接實現(xiàn)就好了墨技,當(dāng)然可以將if條件去掉,所有未實現(xiàn)的方法都走這個實現(xiàn),那么有沒有更好的方法呢娃善?繼續(xù)往下學(xué)習(xí)外厂。

1.2 類方法決議

類方法在快速查找 -> 慢速查找 都沒有找到的情況下冕象,會走到resolveClassMethod 方法代承,源碼如下:

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

resolveClassMethod方法流程與resolveInstanceMethod方法流程類似汁蝶。

崩潰解決

LBHPerson中添加一個lbhClassMethod類方法的,重寫resolveClassMethod類方法

+ (void)lbhClassMethod
{
    NSLog(@"%s",__func__);
}

+ (BOOL)resolveClassMethod:(SEL)sel{

    NSLog(@"%@ 來了", NSStringFromSelector(sel));
    
    if (sel == @selector(classMethod1)) {

        IMP imp = class_getMethodImplementation(objc_getMetaClass("LBHPerson"), @selector(lbhClassMethod));
        Method lgClassMethod1  = class_getInstanceMethod(objc_getMetaClass("LBHPerson"), @selector(lbhClassMethod));
        const char *type = method_getTypeEncoding(lgClassMethod1);
        
        return class_addMethod(objc_getMetaClass("LBHPerson"), sel, imp, type);
        
    }

    return [super resolveClassMethod:sel];
}
1.3 優(yōu)化

上面解決方法都是在單獨的某個類中重寫動態(tài)決議方法论悴,這意味著每個類中都需要重寫這兩個方法掖棉,這樣太麻煩了,怎么做呢膀估? 相信大家都會幔亥。

  • 實例方法: --> 父類 --> 根類 --> nil
  • 類方法 :元類 --> 根元類 --> 根類 --> nil

如果在當(dāng)前元類中沒有找到方法實現(xiàn),會沿著它們的繼承鏈向上查找察纯,它們都會經(jīng)過根類即NSObject帕棉。

問題: 是否可以將上述的兩個方法統(tǒng)一整合在一起呢?
解答:是可以的饼记,可以通過NSObject分類的方式來實現(xiàn)統(tǒng)一處理香伴,而且由于類方法的查找,在其繼承鏈具则,查找的也是實例方法即纲,所以可以將實例方法類方法的統(tǒng)一放在resolveInstanceMethod方法中處理。

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    NSLog(@"%@ 來了", NSStringFromSelector(sel));
    
    if (sel == @selector(classMethod1)) {
        
        IMP imp = class_getMethodImplementation(objc_getMetaClass("LBHPerson"), @selector(lbhClassMethod));
        Method lgClassMethod1  = class_getInstanceMethod(objc_getMetaClass("LBHPerson"), @selector(lbhClassMethod));
        const char *type = method_getTypeEncoding(lgClassMethod1);

        return class_addMethod(objc_getMetaClass("LBHPerson"), sel, imp, type);
    
    }else if (sel == @selector(instanceMethod1)) {
        
        //獲取lbhInstanceMethod方法的imp
        IMP imp = class_getMethodImplementation(self, @selector(lbhInstanceMethod));
        //獲取lbhInstanceMethod的實例方法
        Method lbhInstanceMethod1  = class_getInstanceMethod(self, @selector(lbhInstanceMethod));
        //獲取lbhInstanceMethod的豐富簽名
        const char *type = method_getTypeEncoding(lbhInstanceMethod1);
        //將sel的實現(xiàn)指向lbhInstanceMethod

        return class_addMethod(self, sel, imp, type);
        
    }
    
//    return [super resolveInstanceMethod:sel];
    return NO;
}

這種方式的實現(xiàn)博肋,正好與源碼中針對類方法的處理邏輯是一致的低斋,即完美闡述為什么調(diào)用了類方法動態(tài)方法決議,還要調(diào)用對象方法動態(tài)方法決議匪凡,其根本原因是類方法是元類中的實例方法膊畴。

2. 消息轉(zhuǎn)發(fā)

我們了解到,如果快速+慢速沒有找到方法實現(xiàn)病游,動態(tài)方法決議也不行唇跨,就使用消息轉(zhuǎn)發(fā),但是,我們找遍了源碼也沒有發(fā)現(xiàn)消息轉(zhuǎn)發(fā)的相關(guān)源碼轻绞,可以通過以下方式來了解:

  • 通過instrumentObjcMessageSends方式打印發(fā)送消息的日志
  • 通過hopper/IDA反編譯

2.1 instrumentObjcMessageSends

通過lookUpImpOrForward --> log_and_fill_cache --> logMessageSend采记,在logMessageSend源碼下方找到instrumentObjcMessageSends的源碼實現(xiàn)。
在main中調(diào)用
instrumentObjcMessageSends打印方法調(diào)用的日志信息政勃,有以下兩點準備工作

1唧龄、打開 objcMsgLogEnabled 開關(guān),即調(diào)用instrumentObjcMessageSends方法時奸远,傳入YES

2既棺、在main中通過extern 聲明instrumentObjcMessageSends方法

//最好使用命令行  
extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        LBHPerson *person = [LBHPerson alloc];
        instrumentObjcMessageSends(YES);
        [person instanceMethod1];
//        [LBHPerson classMethod];
        instrumentObjcMessageSends(NO);
        
    }
    return 0;
}
  • 通過logMessageSend源碼,了解到消息發(fā)送打印信息存儲在/tmp 文件夾下懒叛,
  • 運行代碼丸冕,并前往/tmp文件夾,發(fā)現(xiàn)有msgSends開頭的日志文件薛窥,打開發(fā)現(xiàn)在崩潰前胖烛,執(zhí)行了以下方法

2.2 通過hopper/IDA反編譯

HopperIDA是一個可以幫助我們靜態(tài)分析可視性文件的工具,可以將可執(zhí)行文件反匯編成偽代碼诅迷、控制流程圖等佩番,下面以Hopper為例 (針對比較簡單的反匯編,demo版本即可)

step1: 在上面的例子中罢杉,查看下崩潰的堆棧信息

崩潰堆棧信息

發(fā)現(xiàn)___forwarding___來自CoreFoundation

step2: 通過image list趟畏,讀取整個鏡像文件,然后搜索CoreFoundation,查看其可執(zhí)行文件的路徑

step3: 通過文件路徑滩租,找到CoreFoundation可執(zhí)行文件

step4: 打開hopper赋秀,選擇Try the Demo,然后將上一步的可執(zhí)行文件拖入hopper進行反匯編律想,選擇x86(64 bits)

hopper選擇Demo版本
hopper反匯編

step5: 以下是反匯編后的界面猎莲,主要使用上面的三個功能,分別是 匯編蜘欲、流程圖益眉、偽代碼

hoppper主要使用的三個功能

step6: 通過左側(cè)的搜索框搜索__forwarding_prep_0___,然后選擇偽代碼

偽代碼-__forwarding_prep_0___

step7: 進入___forwarding___的偽代碼實現(xiàn)姥份,首先是查看是否實現(xiàn)forwardingTargetForSelector方法郭脂,如果沒有響應(yīng),跳轉(zhuǎn)至loc_6459b即快速轉(zhuǎn)發(fā)沒有響應(yīng)澈歉,進入慢速轉(zhuǎn)發(fā)流程

偽代碼-___forwarding___

step8: 跳轉(zhuǎn)至loc_64a67展鸡,在其下方判斷是否響應(yīng)methodSignatureForSelector方法

  • 如果沒有響應(yīng),跳轉(zhuǎn)至loc_64dd7埃难,則直接報錯
  • 如果獲取methodSignatureForSelector方法簽名為nil莹弊,也是直接報錯

step9: 如果methodSignatureForSelector返回值不為空涤久,則在forwardInvocation方法中對invocation進行處理

所以,通過上面兩種查找方式可以驗證忍弛,消息轉(zhuǎn)發(fā)的方法有3個

步驟 方法
快速轉(zhuǎn)發(fā) forwardingTargetForSelector
慢速轉(zhuǎn)發(fā) methodSignatureForSelector + forwardInvocation

綜上所述响迂,消息轉(zhuǎn)發(fā)整體的流程如下

消息轉(zhuǎn)發(fā)整體流程

3. 消息轉(zhuǎn)發(fā)之快速轉(zhuǎn)發(fā)

針對前面的崩潰問題,如果動態(tài)方法決議也沒有找到實現(xiàn)细疚,則需要在LBHPerson中重寫forwardingTargetForSelector方法蔗彤,將LBHPerson的實例方法的接收者指定為LBHStudent 的對象(LBHStudent類中有instanceMethod1的具體實現(xiàn)),如下所示

//LBHPerson
@interface LBHPerson : NSObject
- (void)instanceMethod1;
@end

@implementation LBHPerson
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));

//     runtime + aSelector + addMethod + imp
    //將消息的接收者指定為LBHStudent疯兼,在LBHStudent中查找instanceMethod1的實現(xiàn)
    return [LBHStudent alloc];
}
@end

//LBHStudent
@interface LBHStudent : LBHPerson
@end

@implementation LBHStudent
- (void)instanceMethod1
{
    NSLog(@"%s",__func__);
}
@end

運行結(jié)果

快速轉(zhuǎn)發(fā)-指定消息接收者

問題: 如果將LBHStudentinstanceMethod1方法注釋掉然遏,程序運行并不會崩潰

注釋掉LBHStudentinstanceMethod1方法,在forwardingTargetForSelector打上斷點吧彪,發(fā)現(xiàn)程序在不停的執(zhí)行這個方法待侵,具體原因后續(xù)再去查找,不停的執(zhí)行這個方法是很不好的姨裸。

實際上這么寫是很雞肋的秧倾,除非把所有方法都寫在一個類中,顯然這是不現(xiàn)實的啦扬,而且指定的類中如果沒有這個方法的實現(xiàn)中狂,forwardingTargetForSelector方法會一直被調(diào)用

forwardingTargetForSelector方法改一下

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"LBHPerson %s %@",__func__,NSStringFromSelector(aSelector));
//    return [NSObject alloc];
    return [super forwardingTargetForSelector:aSelector];
}

這么寫可以解決方法找不到而一直執(zhí)行forwardingTargetForSelector
,但是并不能解決崩潰扑毡,需要配合慢速轉(zhuǎn)發(fā)使用。

4. 消息轉(zhuǎn)發(fā)之慢速轉(zhuǎn)發(fā)

如果快速轉(zhuǎn)發(fā)中還是沒有找到盛险,則進入最后的一次挽救機會瞄摊,即在LBHPerson中重寫methodSignatureForSelector,如下所示

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
}

運行結(jié)果

當(dāng)然此時可以將快速轉(zhuǎn)發(fā)的代碼注釋掉苦掘,只保留慢速轉(zhuǎn)發(fā)

也可以處理invocation事務(wù)换帜,如下所示,修改invocation的target[LBHStudent alloc]鹤啡,調(diào)用 [anInvocation invoke] 觸發(fā) LBHStudent類的instanceMethod1實例方法

不過實際開發(fā)中這么寫比較雞肋惯驼,有種畫蛇添足的感覺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末递瑰,一起剝皮案震驚了整個濱河市祟牲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抖部,老刑警劉巖说贝,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異慎颗,居然都是意外死亡乡恕,警方通過查閱死者的電腦和手機言询,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來傲宜,“玉大人运杭,你說我怎么就攤上這事『洌” “怎么了县习?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谆趾。 經(jīng)常有香客問我躁愿,道長,這世上最難降的妖魔是什么沪蓬? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任彤钟,我火速辦了婚禮,結(jié)果婚禮上跷叉,老公的妹妹穿的比我還像新娘逸雹。我一直安慰自己,他們只是感情好云挟,可當(dāng)我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布梆砸。 她就那樣靜靜地躺著,像睡著了一般园欣。 火紅的嫁衣襯著肌膚如雪帖世。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天沸枯,我揣著相機與錄音日矫,去河邊找鬼。 笑死绑榴,一個胖子當(dāng)著我的面吹牛哪轿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翔怎,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼窃诉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赤套?” 一聲冷哼從身側(cè)響起飘痛,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎于毙,沒想到半個月后敦冬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡唯沮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年脖旱,在試婚紗的時候發(fā)現(xiàn)自己被綠了堪遂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡萌庆,死狀恐怖溶褪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情践险,我是刑警寧澤猿妈,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站巍虫,受9級特大地震影響彭则,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜占遥,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一俯抖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瓦胎,春花似錦芬萍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至负芋,卻和暖如春漫蛔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背示罗。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工惩猫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚜点。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像拌阴,于是被迫代替她去往敵國和親绍绘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,666評論 2 350

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