Objective-C KVO 中 runtime 探究

最近學(xué)習(xí)Runtime释漆,順便總結(jié)一下在Objective-C中KVO使用到的Runtime機(jī)制。

系統(tǒng)的KVO使用

故事還得從OC的KVO說(shuō)起涎拉,一般的我們使用KVO類似的如下所示荡短,創(chuàng)建一個(gè)對(duì)象标沪,然后調(diào)用addObserver方法進(jìn)行某個(gè)屬性的監(jiān)聽(tīng),有意思的是苞轿,我們?cè)趧?chuàng)建對(duì)象處和調(diào)用了addObserver方法處打斷點(diǎn)茅诱,然后使用po命令打印對(duì)象的isa,發(fā)現(xiàn)了對(duì)象的isa指針在調(diào)用了addObserver方法之后變了呕屎,明顯滴让簿,調(diào)用了addObserver方法之后使用了runtime機(jī)制動(dòng)態(tài)的修改了對(duì)象的isa指針。

對(duì)象添加KVO監(jiān)聽(tīng)之后isa的變換

KVO中runtime的幾個(gè)概念

大家一定會(huì)很好奇秀睛,runtime是怎么實(shí)現(xiàn)了KVO尔当,那好下面就慢慢的揭開謎底。先了解幾個(gè)runtime的概念:

  • 動(dòng)態(tài)創(chuàng)建Class
    objc_allocateClassPair可以動(dòng)態(tài)創(chuàng)建Class,objc_registerClassPair進(jìn)行注冊(cè)動(dòng)態(tài)創(chuàng)建的Class
  • 修改對(duì)象的Class
    object_setClass可以修改對(duì)象的Class蹂安,也就是修改了isa指針指向的Class對(duì)象
  • 動(dòng)態(tài)添加方法
    class_addMethod可以給類添加方法
  • runtime方法調(diào)用
    objc_msgSendobjc_msgSendSuper是OC消息發(fā)送機(jī)制的底層實(shí)現(xiàn)

KVO的實(shí)現(xiàn)步驟解析

好了椭迎,掌握了這幾個(gè)runtime的概念,以及一開始看到了對(duì)象isa指針的變化田盈,我們大概可以猜測(cè)KVO的實(shí)現(xiàn)了畜号,大概需要以下幾個(gè)步驟:

  • 動(dòng)態(tài)了創(chuàng)建一個(gè)類(objc_allocateClassPair/objc_registerClassPair),該類是我們需要監(jiān)聽(tīng)的對(duì)象的類的子類允瞧,然后修改當(dāng)前對(duì)象的Class(object_setClass)简软。
  • 給類動(dòng)態(tài)的添加方法(object_setClass),比如KVO監(jiān)聽(tīng)name屬相述暂,一般的需要添加一個(gè)setName:方法痹升,然后添加方法的實(shí)現(xiàn),在方法實(shí)現(xiàn)中畦韭,調(diào)用父類也就是原始類的setName:方法進(jìn)行屬性的設(shè)置(objc_msgSend疼蛾,亦可以使用objc_msgSendSuper)。
  • 給observer的回調(diào)方法發(fā)送監(jiān)聽(tīng)消息(objc_msgSend),最后需要重置當(dāng)前對(duì)象的Class(object_setClass)艺配,確保再次監(jiān)聽(tīng)還會(huì)執(zhí)行同樣的流程.

自定義的KVO實(shí)現(xiàn)

系統(tǒng)的KVO addObserver方法是NSObject對(duì)象的一個(gè)類別中NSObject(NSKeyValueObserverRegistration)定義的方法察郁,所以我們也創(chuàng)建一個(gè)NSObject的類別NSObject (YTT_KVO),添加一個(gè)類似的監(jiān)聽(tīng)方法转唉。

創(chuàng)建KVO使用的類別

@interface NSObject (YTT_KVO)

- (void)ytt_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

@end

類別方法的實(shí)現(xiàn)

類別的監(jiān)聽(tīng)方法皮钠,做的事情主要是保存方法參數(shù),動(dòng)態(tài)創(chuàng)建之類以及設(shè)置對(duì)象的Class到子類赠法,動(dòng)態(tài)的添加子類的方法鳞芙。

- (void)ytt_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    
    // 保存keypath
    objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    // 獲取當(dāng)前類
    Class selfClass = self.class;
    
    // 動(dòng)態(tài)創(chuàng)建KVO類
    const char * className = NSStringFromClass(selfClass).UTF8String;
    char kvoClassName[1000];
    sprintf(kvoClassName, "%s%s", "YTT_KVO_", className);
    Class kvoClass = objc_allocateClassPair(selfClass, kvoClassName, 0);
    if (!kvoClass) {
        // Nil if the class could not be created (for example, the desired name is already in use).
        kvoClass = NSClassFromString([NSString stringWithUTF8String:kvoClassName]);
    }
    objc_registerClassPair(kvoClass);
    
    // 修改當(dāng)前類指向?yàn)閯?dòng)態(tài)創(chuàng)建的KVO子類
    object_setClass(self, kvoClass);
    
    // 動(dòng)態(tài)添加一個(gè)方法:setXxx()
    SEL sel = NSSelectorFromString([NSString stringWithFormat:@"set%@:", keyPath.capitalizedString]);
    class_addMethod(kvoClass, sel, (IMP)setValue, NULL);
}

動(dòng)態(tài)添加方法的實(shí)現(xiàn)

動(dòng)態(tài)添加方法做的事情主要是創(chuàng)建和設(shè)置回調(diào)參數(shù),包含新值和舊值,調(diào)父類的設(shè)置方法原朝,給原始類設(shè)置值驯嘱,給observer回調(diào)方法發(fā)送消息通知,告訴值改變了喳坠。

void setValue(id self, SEL _cmd, id value) {
    
    // 保存當(dāng)前的Class鞠评,重置Class使用
    Class selfClass = [self class];
    // 設(shè)置Class為原始Class
    object_setClass(self, [self superclass]);
    // 獲取keyPath
    NSString* keyPath = objc_getAssociatedObject(self, "keyPath");

    // KVO 回調(diào)參數(shù)
    NSMutableDictionary* change = [NSMutableDictionary dictionary];
    change[NSKeyValueChangeNewKey] = value;
    
    // 獲取舊的值
    SEL getSel = NSSelectorFromString([NSString stringWithFormat:@"%@", keyPath]);
    if ([self respondsToSelector:getSel]) {
        id ret = ((id(*)(id, SEL, id))objc_msgSend)(self, getSel, value);
        if (ret) {
            change[NSKeyValueChangeOldKey] = ret;
        }
    }
    
    // 給原始類設(shè)置數(shù)據(jù)
    SEL setSel = NSSelectorFromString([NSString stringWithFormat:@"set%@:", keyPath.capitalizedString]);
    if ([self respondsToSelector:setSel]) {
        ((void(*)(id, SEL, id))objc_msgSend)(self, setSel, value);
    }
    
    // 發(fā)送通知
    id observer = objc_getAssociatedObject(self, "observer");
    SEL observerSel = @selector(ytt_observeValueForKeyPath:ofObject:change:context:);
    if ([observer respondsToSelector:observerSel]) {
        ((void(*) (id, SEL, NSString*, id, id ,id))(void *)objc_msgSend)(observer, observerSel, keyPath, self, change, nil);
    }
    
    // 重置class指針,這樣再次調(diào)用對(duì)象方法會(huì)走到這里面
    object_setClass(self, selfClass);
}

KVO通知的接收處理

在監(jiān)聽(tīng)的對(duì)象中定義我們約定好的方法壕鹉,類似Objective-C中的KVO進(jìn)行處理

- (void)ytt_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    id new = change[NSKeyValueChangeNewKey];
    id old = change[NSKeyValueChangeOldKey];
    
    NSLog(@"%@-%@", old, new);
}

注意點(diǎn)

  • objc_msgSend 的使用注意

我使用的是xcode8剃幌,ENABLE_STRICT_OBJC_MSGSEND開關(guān)默認(rèn)是打開的,需要進(jìn)行方法的指針強(qiáng)轉(zhuǎn)為對(duì)應(yīng)的方法才能調(diào)用

((void(*) (id, SEL, NSString*, id, id ,id))(void *)objc_msgSend)(observer, observerSel, keyPath, self, change, nil);

// 這句代碼的意思是把objc_msgSend這個(gè)任意類型的指針【(void *)】轉(zhuǎn)換為一個(gè)返回值為void的函數(shù)指針【void(*)】晾浴,并且參數(shù)列表為【(id, SEL, NSString*, id, id ,id)】

// void (*) 聲明一個(gè)函數(shù)指針负乡,返回值是void類型
// (void *) 返回的是一個(gè)任意類型的指針

// 比如我們需要獲取person的name屬性,返回值是NSString*類型脊凰,那么objc_msgSend方法定義如下
id ret = ((NSString*(*)(id, SEL))objc_msgSend)(self.person, @selector(name));

也可以設(shè)置ENABLE_STRICT_OBJC_MSGSEND開關(guān)關(guān)閉抖棘,那么可以不額外的處理objc_msgSend強(qiáng)轉(zhuǎn)的問(wèn)題,如果是一個(gè)庫(kù)項(xiàng)目狸涌,那么不建議手動(dòng)關(guān)閉這個(gè)開關(guān)切省,這樣不管是ENABLE_STRICT_OBJC_MSGSEND開關(guān)打開還是關(guān)閉都不會(huì)影響。

  • objc_allocateClassPair 為空處理

objc_allocateClassPair 調(diào)用可能返回Nil帕胆,這種情況在當(dāng)前的場(chǎng)景第二次調(diào)用同一個(gè)類對(duì)象的ytt_addObserver方法可能發(fā)生朝捆,這種情況,使用NSClassFromString方法創(chuàng)建Class懒豹。

    Class kvoClass = objc_allocateClassPair(selfClass, kvoClassName, 0);
    if (!kvoClass) {
        // Nil if the class could not be created (for example, the desired name is already in use).
        kvoClass = NSClassFromString([NSString stringWithUTF8String:kvoClassName]);
    }
    objc_registerClassPair(kvoClass);
```最近學(xué)習(xí)Runtime芙盘,順便總結(jié)一下在Objective-C中KVO使用到的Runtime機(jī)制。

### 系統(tǒng)的KVO使用

故事還得從OC的KVO說(shuō)起脸秽,一般的我們使用KVO類似的如下所示何陆,創(chuàng)建一個(gè)對(duì)象,然后調(diào)用`addObserver`方法進(jìn)行某個(gè)屬性的監(jiān)聽(tīng)豹储,有意思的是,我們?cè)趧?chuàng)建對(duì)象處和調(diào)用了`addObserver`方法處打斷點(diǎn)淘这,然后使用po命令打印對(duì)象的isa剥扣,發(fā)現(xiàn)了對(duì)象的isa指針在調(diào)用了`addObserver`方法之后變了,明顯滴铝穷,調(diào)用了`addObserver`方法之后使用了runtime機(jī)制動(dòng)態(tài)的修改了對(duì)象的isa指針钠怯。  

![對(duì)象添加KVO監(jiān)聽(tīng)之后isa的變換](https://static.oschina.net/uploads/img/201704/27113947_RjbW.png "對(duì)象添加KVO監(jiān)聽(tīng)之后isa的變換")  

### KVO中runtime的幾個(gè)概念

大家一定會(huì)很好奇,runtime是怎么實(shí)現(xiàn)了KVO曙聂,那好下面就慢慢的揭開謎底晦炊。先了解幾個(gè)runtime的概念:
- 動(dòng)態(tài)創(chuàng)建Class  
`objc_allocateClassPair`可以動(dòng)態(tài)創(chuàng)建Class,`objc_registerClassPair`進(jìn)行注冊(cè)動(dòng)態(tài)創(chuàng)建的Class  
- 修改對(duì)象的Class  
`object_setClass`可以修改對(duì)象的Class,也就是修改了isa指針指向的Class對(duì)象  
- 動(dòng)態(tài)添加方法  
`class_addMethod`可以給類添加方法  
- runtime方法調(diào)用  
`objc_msgSend`和`objc_msgSendSuper`是OC消息發(fā)送機(jī)制的底層實(shí)現(xiàn)  

### KVO的實(shí)現(xiàn)步驟解析

好了,掌握了這幾個(gè)runtime的概念断国,以及一開始看到了對(duì)象isa指針的變化,我們大概可以猜測(cè)KVO的實(shí)現(xiàn)了,大概需要以下幾個(gè)步驟:  

- 動(dòng)態(tài)了創(chuàng)建一個(gè)類(objc_allocateClassPair/objc_registerClassPair)色迂,該類是我們需要監(jiān)聽(tīng)的對(duì)象的類的子類蚀狰,然后修改當(dāng)前對(duì)象的Class(object_setClass)。
- 給類動(dòng)態(tài)的添加方法(object_setClass)薄疚,比如KVO監(jiān)聽(tīng)name屬相碧信,一般的需要添加一個(gè)setName:方法,然后添加方法的實(shí)現(xiàn)街夭,在方法實(shí)現(xiàn)中砰碴,調(diào)用父類也就是原始類的setName:方法進(jìn)行屬性的設(shè)置(objc_msgSend,亦可以使用objc_msgSendSuper)板丽。
- 給observer的回調(diào)方法發(fā)送監(jiān)聽(tīng)消息(objc_msgSend),最后需要重置當(dāng)前對(duì)象的Class(object_setClass)呈枉,確保再次監(jiān)聽(tīng)還會(huì)執(zhí)行同樣的流程.

### 自定義的KVO實(shí)現(xiàn)
系統(tǒng)的KVO `addObserver`方法是NSObject對(duì)象的一個(gè)類別中`NSObject(NSKeyValueObserverRegistration)`定義的方法,所以我們也創(chuàng)建一個(gè)NSObject的類別`NSObject (YTT_KVO)`檐什,添加一個(gè)類似的監(jiān)聽(tīng)方法碴卧。

#### 創(chuàng)建KVO使用的類別

@interface NSObject (YTT_KVO)

  • (void)ytt_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

@end


#### 類別方法的實(shí)現(xiàn)
類別的監(jiān)聽(tīng)方法,做的事情主要是保存方法參數(shù)乃正,動(dòng)態(tài)創(chuàng)建之類以及設(shè)置對(duì)象的Class到子類住册,動(dòng)態(tài)的添加子類的方法。

  • (void)ytt_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {

    // 保存keypath
    objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    // 獲取當(dāng)前類
    Class selfClass = self.class;

    // 動(dòng)態(tài)創(chuàng)建KVO類
    const char * className = NSStringFromClass(selfClass).UTF8String;
    char kvoClassName[1000];
    sprintf(kvoClassName, "%s%s", "YTT_KVO_", className);
    Class kvoClass = objc_allocateClassPair(selfClass, kvoClassName, 0);
    if (!kvoClass) {
    // Nil if the class could not be created (for example, the desired name is already in use).
    kvoClass = NSClassFromString([NSString stringWithUTF8String:kvoClassName]);
    }
    objc_registerClassPair(kvoClass);

    // 修改當(dāng)前類指向?yàn)閯?dòng)態(tài)創(chuàng)建的KVO子類
    object_setClass(self, kvoClass);

    // 動(dòng)態(tài)添加一個(gè)方法:setXxx()
    SEL sel = NSSelectorFromString([NSString stringWithFormat:@"set%@:", keyPath.capitalizedString]);
    class_addMethod(kvoClass, sel, (IMP)setValue, NULL);
    }


#### 動(dòng)態(tài)添加方法的實(shí)現(xiàn)
動(dòng)態(tài)添加方法做的事情主要是創(chuàng)建和設(shè)置回調(diào)參數(shù)瓮具,包含新值和舊值荧飞,調(diào)父類的設(shè)置方法,給原始類設(shè)置值名党,給observer回調(diào)方法發(fā)送消息通知叹阔,告訴值改變了。  

void setValue(id self, SEL _cmd, id value) {

// 保存當(dāng)前的Class传睹,重置Class使用
Class selfClass = [self class];
// 設(shè)置Class為原始Class
object_setClass(self, [self superclass]);
// 獲取keyPath
NSString* keyPath = objc_getAssociatedObject(self, "keyPath");

// KVO 回調(diào)參數(shù)
NSMutableDictionary* change = [NSMutableDictionary dictionary];
change[NSKeyValueChangeNewKey] = value;

// 獲取舊的值
SEL getSel = NSSelectorFromString([NSString stringWithFormat:@"%@", keyPath]);
if ([self respondsToSelector:getSel]) {
    id ret = ((id(*)(id, SEL, id))objc_msgSend)(self, getSel, value);
    if (ret) {
        change[NSKeyValueChangeOldKey] = ret;
    }
}

// 給原始類設(shè)置數(shù)據(jù)
SEL setSel = NSSelectorFromString([NSString stringWithFormat:@"set%@:", keyPath.capitalizedString]);
if ([self respondsToSelector:setSel]) {
    ((void(*)(id, SEL, id))objc_msgSend)(self, setSel, value);
}

// 發(fā)送通知
id observer = objc_getAssociatedObject(self, "observer");
SEL observerSel = @selector(ytt_observeValueForKeyPath:ofObject:change:context:);
if ([observer respondsToSelector:observerSel]) {
    ((void(*) (id, SEL, NSString*, id, id ,id))(void *)objc_msgSend)(observer, observerSel, keyPath, self, change, nil);
}

// 重置class指針耳幢,這樣再次調(diào)用對(duì)象方法會(huì)走到這里面
object_setClass(self, selfClass);

}


### KVO通知的接收處理
在監(jiān)聽(tīng)的對(duì)象中定義我們約定好的方法,類似Objective-C中的KVO進(jìn)行處理
  • (void)ytt_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    id new = change[NSKeyValueChangeNewKey];
    id old = change[NSKeyValueChangeOldKey];

    NSLog(@"%@-%@", old, new);
    }



---

#### 注意點(diǎn)  

- objc_msgSend 的使用注意  

我使用的是xcode8欧啤,ENABLE_STRICT_OBJC_MSGSEND開關(guān)默認(rèn)是打開的睛藻,需要進(jìn)行方法的指針強(qiáng)轉(zhuǎn)為對(duì)應(yīng)的方法才能調(diào)用

```objc
((void(*) (id, SEL, NSString*, id, id ,id))(void *)objc_msgSend)(observer, observerSel, keyPath, self, change, nil);

// 這句代碼的意思是把objc_msgSend這個(gè)任意類型的指針【(void *)】轉(zhuǎn)換為一個(gè)返回值為void的函數(shù)指針【void(*)】,并且參數(shù)列表為【(id, SEL, NSString*, id, id ,id)】

// void (*) 聲明一個(gè)函數(shù)指針邢隧,返回值是void類型
// (void *) 返回的是一個(gè)任意類型的指針

// 比如我們需要獲取person的name屬性店印,返回值是NSString*類型,那么objc_msgSend方法定義如下
id ret = ((NSString*(*)(id, SEL))objc_msgSend)(self.person, @selector(name));

也可以設(shè)置ENABLE_STRICT_OBJC_MSGSEND開關(guān)關(guān)閉倒慧,那么可以不額外的處理objc_msgSend強(qiáng)轉(zhuǎn)的問(wèn)題按摘,如果是一個(gè)庫(kù)項(xiàng)目包券,那么不建議手動(dòng)關(guān)閉這個(gè)開關(guān),這樣不管是ENABLE_STRICT_OBJC_MSGSEND開關(guān)打開還是關(guān)閉都不會(huì)影響炫贤。

  • objc_allocateClassPair 為空處理

objc_allocateClassPair 調(diào)用可能返回Nil溅固,這種情況在當(dāng)前的場(chǎng)景第二次調(diào)用同一個(gè)類對(duì)象的ytt_addObserver方法可能發(fā)生,這種情況照激,使用NSClassFromString方法創(chuàng)建Class发魄。

    Class kvoClass = objc_allocateClassPair(selfClass, kvoClassName, 0);
    if (!kvoClass) {
        // Nil if the class could not be created (for example, the desired name is already in use).
        kvoClass = NSClassFromString([NSString stringWithUTF8String:kvoClassName]);
    }
    objc_registerClassPair(kvoClass);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市俩垃,隨后出現(xiàn)的幾起案子励幼,更是在濱河造成了極大的恐慌,老刑警劉巖口柳,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苹粟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡跃闹,警方通過(guò)查閱死者的電腦和手機(jī)嵌削,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)望艺,“玉大人苛秕,你說(shuō)我怎么就攤上這事≌夷” “怎么了艇劫?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)惩激。 經(jīng)常有香客問(wèn)我店煞,道長(zhǎng),這世上最難降的妖魔是什么风钻? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任顷蟀,我火速辦了婚禮,結(jié)果婚禮上骡技,老公的妹妹穿的比我還像新娘鸣个。我一直安慰自己,他們只是感情好布朦,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布囤萤。 她就那樣靜靜地躺著,像睡著了一般喝滞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膏秫,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天右遭,我揣著相機(jī)與錄音做盅,去河邊找鬼。 笑死窘哈,一個(gè)胖子當(dāng)著我的面吹牛吹榴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滚婉,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼图筹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了让腹?” 一聲冷哼從身側(cè)響起远剩,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎骇窍,沒(méi)想到半個(gè)月后瓜晤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腹纳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年痢掠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘲恍。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡足画,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出佃牛,到底是詐尸還是另有隱情淹辞,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布吁脱,位于F島的核電站桑涎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏兼贡。R本人自食惡果不足惜攻冷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望遍希。 院中可真熱鬧等曼,春花似錦、人聲如沸凿蒜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)废封。三九已至州泊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間漂洋,已是汗流浹背遥皂。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工力喷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人演训。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓弟孟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親样悟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拂募,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

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