KVO與KVC的原理及其應用

一猴蹂、關(guān)于KVO

1. KVO實現(xiàn)原理

實現(xiàn)kvo監(jiān)聽某一屬性值變化的相關(guān)代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    People *p = [[People alloc] init];
    [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    p.name = @"123";
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);
}

當一個對象的屬性被觀察時瓶摆,系統(tǒng)會動態(tài)創(chuàng)建了一個子類榆鼠;
并且改變了原有對象的isa指針指向遥昧,指向動態(tài)創(chuàng)建的子類;
子類中重寫了被觀察屬性的set方法润歉,在使用點方法和set方法給屬性賦值時模狭,最終調(diào)用的是子類中的set方法。

在addObserver處設(shè)置斷點觀察對象isa指針變化踩衩,被觀察前isa指針指向的是原始類如圖:

而執(zhí)行代碼被觀察后嚼鹉,指針指向的是NSKVONotifying_People類,可自行實驗驱富。

2. 自定義KVO

創(chuàng)建一個分類新增一個方法HBaddObserver锚赤,在方法中創(chuàng)建子類注冊并指向子類,再為子類添加set方法既可褐鸥。自定義kvo過程中线脚,主要使用到的系統(tǒng)方法:

//// 1.創(chuàng)建一個子類
/** 
 superclass:設(shè)置新類的父類
 name:新類名稱
 extraBytes:額外字節(jié)數(shù)設(shè)置為0
*/
 objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes)

//// 2.注冊該類
/** 
 cls:當前要注冊的類,注冊后才可以使用
*/
 objc_registerClassPair(Class _Nonnull cls)

//// 3.設(shè)置當前對象指向其他類
/** 
 obj:要設(shè)置的對象
 cls:指向的類
*/
 object_setClass(id _Nullable obj, Class _Nonnull cls)

//// 4.動態(tài)添加一個方法
/** 
 cls:設(shè)置添加方法對應的類
 name:選擇子(選擇器)名稱叫榕,描述了方法的格式浑侥,并不會指向方法
 imp:函數(shù)名稱(函數(shù)指針),和選擇子一一對應晰绎,指向方法實現(xiàn)的地址
*/
 class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)

代碼示例:

  1. 主控制器代碼:
#import "ViewController.h"
#import <objc/message.h>
#import "Person.h"
#import "NSObject+HBKVO.h"

@interface ViewController (){
    Person *p;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    p = [[Person alloc] init];
    [p HBaddObserver:self forKeyPath:@"Name" options:NSKeyValueObservingOptionNew context:nil];
    
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"change:%@",change);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    static int num = 0;
    p.Name = [NSString stringWithFormat:@"%d",num++];
}
@end
  1. person類頭文件代碼(.m中無相關(guān)代碼):
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
@property (nonatomic,strong)NSString *Name;
@end

NS_ASSUME_NONNULL_END
  1. 自定義KVO相關(guān)代碼(NSObject分類):
NSObject+HBKVO.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (HBKVO)
- (void)HBaddObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end

NS_ASSUME_NONNULL_END
NSObject+HBKVO.m

#import "NSObject+HBKVO.h"
#import <objc/message.h>

@implementation NSObject (HBKVO)
-(void)HBaddObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
    NSString *oldName = NSStringFromClass(self.class);
    NSString *newName = [@"HBKVO_" stringByAppendingString:oldName];
    //1寓落、創(chuàng)建一個子類
    Class newClass = objc_allocateClassPair(self.class, newName.UTF8String, 0);
    //2、注冊該類
    objc_registerClassPair(newClass);
    //3荞下、指向子類
    object_setClass(self, newClass);
    //4伶选、動態(tài)添加一個方法
    NSString *first = [keyPath substringWithRange:NSMakeRange(0, 1)];
    NSString *other = [keyPath substringFromIndex:1];
    NSString *setName = [NSString stringWithFormat:@"set%@%@:",first.uppercaseString,other];//設(shè)置一個屬性名首字母大寫的方法
    Method method = class_getInstanceMethod(self.class, sel_registerName(setName.UTF8String));
    const char *types = method_getTypeEncoding(method);
    class_addMethod(newClass, sel_registerName(setName.UTF8String), (IMP)setValue, types);
    //class_addMethod(newClass, sel_registerName(setMethod.UTF8String), (IMP)setName, "v@:@");
    
    //設(shè)置關(guān)聯(lián)數(shù)據(jù)
    //獲取元類舊值使用
    objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //設(shè)置新值的時候使用
    objc_setAssociatedObject(self, "setName", setName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //通知值變化
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //傳進來的內(nèi)容需要回傳
    objc_setAssociatedObject(self, "context", (__bridge id _Nullable)(context), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
void setValue(id self,SEL _cmd,NSString *newValue){
    NSLog(@"newValue:%@",newValue);
    NSString *keyPath = objc_getAssociatedObject(self, "keyPath");
    NSString *setName = objc_getAssociatedObject(self, "setName");
    id observer = objc_getAssociatedObject(self, "observer");
    id context = objc_getAssociatedObject(self, "context");
    //存儲新類
    Class newClass = [self class];
    //指向父類獲取舊值
    object_setClass(self, class_getSuperclass(newClass));
    NSString *oldValue = objc_msgSend(self,sel_registerName(keyPath.UTF8String));
    //對原始類屬性或成員變量復制
    objc_msgSend(self, sel_registerName(setName.UTF8String),newValue);
    NSMutableDictionary *change = [NSMutableDictionary dictionary];
    if (oldValue) {
        change[NSKeyValueChangeOldKey] = oldValue;
    }
    if (newValue) {
        change[NSKeyValueChangeNewKey] = newValue;
    }
    //調(diào)用observer的回調(diào)方法
    objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),keyPath,observer,change,context);
    //操作完成后指回動態(tài)創(chuàng)建的新類
    object_setClass(self, newClass);
}
@end

一、關(guān)于KVC

KVC的全稱為KeyValueCoding尖昏,是對NSObjcet的擴展仰税,分類名為 : NSKeyValueCoding。我們經(jīng)常用KVC或者setter方法來觸發(fā)KVO抽诉,實現(xiàn)鍵值變化監(jiān)聽陨簇,實現(xiàn)一些功能。

1. KVC常用的方法說明

// 1迹淌、將鍵字符串key所對應的屬性的值設(shè)置為value河绽。不能設(shè)定屬性值時,將會引起接收器調(diào)用方法2
- (void)setValue:(nullable id)value forKey:(NSString *)key

// 2巍沙、當屬性值設(shè)置失敗,調(diào)用此方法
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key

// 3荷鼠、返回標識屬性的鍵字符串所對應的值句携。如果獲取失敗,將會引起接收器調(diào)用方法4
- (nullable id)valueForKey:(NSString *)key

// 4允乐、取值失敗矮嫉,調(diào)用此方法
- (nullable id)valueForUndefinedKey:(NSString *)key

// 5削咆、在鍵字符串key所對應的"標量"型屬性值設(shè)為nil,調(diào)用此方法蠢笋,并拋出NSInvalidArgumentException異常(可demo測試)
- (void)setNilValueForKey:(NSString *)key


// 6拨齐、默認返回值YES,代表如果沒有找到Set方法的話昨寞,會按照_key瞻惋,_iskey,key援岩,iskey的順序搜索成員歼狼,設(shè)置成NO就不這樣搜索
+ (BOOL)accessInstanceVariablesDirectly

標量 : 屬性中的單純的數(shù)值(整數(shù)、實數(shù)享怀、布爾值等)
在賦值的時候羽峰,如果是結(jié)構(gòu)體,必須包裝成NSValue實例添瓷;
如果是標量型屬性梅屉,必須包裝成NSNumber實例。

2. KVC賦值的實現(xiàn)原理

  1. 查找是否實現(xiàn)setter鳞贷、_setter 方法坯汤,如果有,優(yōu)先調(diào)用setter方法完成賦值(注意:set后面的鍵的第一字字母必須是大寫G幕巍玫霎!)
  2. 當沒找到setter方法,調(diào)用accessInstanceVariablesDirectly詢問妈橄。如果返回YES庶近,順序匹配變量名與 _<key>,_is<Key>,<key>,is<Key>,匹配到則設(shè)定其值眷蚓;如果返回NO,結(jié)束查找鼻种。并調(diào)用 setValue:forUndefinedKey:報異常
  3. 如果既沒有setter也沒有實例變量時,調(diào)用 setValue:forUndefinedKey:

結(jié)合demo沙热,寫下基本實現(xiàn)原理

// .h文件
#import <Foundation/Foundation.h>

@interface Peson : NSObject {
    //_<key>, _is<Key>, <key>, or is<Key>  注意順序!!!
    //NSString *_name;
    //NSString *_isName;
   // NSString *name;
    NSString *isName;
}
@end

// .m文件
#import "Peson.h"
#import <objc/runtime.h>

@implementation Peson

- (void)setValue:(id)value forKey:(NSString *)key
{
    NSString *setter = [[@"set" stringByAppendingString:[key capitalizedString]] stringByAppendingString:@":"];
    
    // 1叉钥、檢查是否存在setter方法
    if ([self respondsToSelector:NSSelectorFromString(setter)]) {
        // 1.1 如果是標量型屬性賦值,且值為nil篙贸,賦值失敗
        if (![value isKindOfClass:[NSObject class]] && value == nil)
        {
            [self setNilValueForKey:key];
            // 1.2 如果是對象指針類型投队,直接進行賦值操作
        }else{
          
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [self performSelector:NSSelectorFromString(setter) withObject:value];
#pragma clang diagnostic pop
        }
    // 2、詢問 accessInstanceVariablesDirectly,默認YES,繼續(xù)往下查找
    }else{
        //獲取所有屬性列表
        unsigned int count = 0;
        Ivar *ivar = class_copyIvarList([self class], &count);
        //_<key>, _is<Key>, <key>, or is<Key>  注意順序!!!
        NSArray *searchPropretys = @[@"_name",@"_isName",@"name",@"isName"];
        // 是否找到變量名的標志位爵川,判斷是否需要拋出異常
        BOOL flag  =  false;
        
        //遍歷屬性,依次匹配
        for (int i = 0; i < count; i++) {
            //如果找到了敷鸦,結(jié)束循環(huán)
            if (flag) {
                break;
            }else{
                Ivar var = ivar[i];
                NSString *name = [NSString stringWithUTF8String:ivar_getName(var)];
                
                for (int j = 0; j < searchPropretys.count; j++) {
                    //找到了,結(jié)束循環(huán)
                    if ([name isEqualToString:searchPropretys[j]]) {
                        flag = YES;
                        object_setIvar(self, var, value);
                        break;
                    }
                }              
            }
        }
        
        //記得釋放
        free(ivar);
        
        //如果沒找到扒披,調(diào)用setValue: forUndefinedKey: 拋出異常
        if (!flag) {
            [self setValue:value forUndefinedKey:key];
        }
    }
}

+(BOOL)accessInstanceVariablesDirectly {
    return YES;
}

注意: 上面有一個細節(jié)需要說下值依,對于標量型屬性賦值,如果是純數(shù)值碟案,需要使用包裝類NSNumber愿险,對于結(jié)構(gòu)體,需要用NSValue實例包裝价说。

通過上面我們也可以發(fā)現(xiàn)辆亏,為什么KVC和setter方法都可以觸發(fā)KVO 。

3. KVC取值的實現(xiàn)原理

  1. 查找是否實現(xiàn)getter方法熔任,依次匹配-get<Key>-<key>is<Key>褒链,如果找到,直接返回疑苔。需要注意的是 :如果返回的是對象指針類型甫匹,則返回結(jié)果;如果返回的是NSNumber轉(zhuǎn)換所支持的標量類型之一惦费,則返回一個NSNumber兵迅,否則,將返回一個NSValue
  2. 當沒有找到getter方法薪贫,調(diào)用accessInstanceVariablesDirectly詢問恍箭,如果返回YES, _<key>瞧省,_is<Key>,<key>,is<Key>扯夭,找到了返回對應的值;如果返回NO鞍匾,結(jié)束查找交洗。并調(diào)用 valueForUndefinedKey: 報異常
  3. 如果沒找到getter方法和屬性值,調(diào)用 valueForUndefinedKey: 報異常橡淑。

緊接著實現(xiàn)上面demo的取值方法:

- (id)valueForKey:(NSString *)key
{
    // 1. 查找getter方法
    // -get<Key>
     NSString *getKey = [@"get" stringByAppendingString:[key capitalizedString]];
     NSString *isKey = [@"is" stringByAppendingString:[key capitalizedString]];
    //  1.1 優(yōu)先查找 -get<Key>
    if ([self respondsToSelector:NSSelectorFromString(getKey)])
    {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(getKey)];
#pragma clang diagnostic pop
        
      // 1.2 查找   -<key>
    }else if ([self respondsToSelector:NSSelectorFromString(key)])
    {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(key)];
#pragma clang diagnostic pop
      
     // 1.3 查找 is<Key>
    }else if ([self respondsToSelector:NSSelectorFromString(isKey)])
    {
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(isKey)];
#pragma clang diagnostic pop
        
      
     // 2. 詢問 accessInstanceVariablesDirectly构拳,是否繼續(xù)查找屬性,默認返回YES
    }else{
        //_<key>, _is<Key>, <key>, or is<Key>  注意順序!!!

        //獲取所有屬性列表
        unsigned int count = 0;
        Ivar *ivar = class_copyIvarList([self class], &count);
        //_<key>, _is<Key>, <key>, or is<Key>  注意順序!!!
        NSArray *searchPropretys = @[@"_name",@"_isName",@"name",@"isName"];
        
        BOOL flag  =  false;
        
        //遍歷屬性,依次匹配
        for (int i = 0; i < count; i++) {
            //如果找到了梁棠,跳出外重循環(huán)
            if (flag) {
                break;
            }else{
                Ivar var = ivar[i];
                NSString *name = [NSString stringWithUTF8String:ivar_getName(var)];
                for (int j = 0; j < searchPropretys.count; j++) {
                    //找到了置森,結(jié)束循環(huán)
                    if ([name isEqualToString:searchPropretys[j]]) {
                        flag = YES;
                        return  object_getIvar(self, var);
                        break;
                    }
                }
            }
        }
        free(ivar);
        //如果沒找到,調(diào)用
        if (!flag) {
            [self valueForUndefinedKey:key];
        }
    }
    return nil;
}

參考鏈接:
KVO實現(xiàn)原理

KVC和字典
iOS KVC實現(xiàn)原理
KVC的底層原理符糊,及自定義KVC

KVC的運用場景
1.動態(tài)的取值和設(shè)值
2.用KVC來訪問和修改私有變量
3.Model和字典轉(zhuǎn)換
4.修改一些控件的內(nèi)部屬性

最常用的是個性化UITextField中的placeHolderText
這里的關(guān)鍵點是如何獲取你要修改的樣式屬性名也就是key or keyPath名

KVC 能夠觸發(fā) KVO凫海,在 KVC 底層有手動觸發(fā) KVO的代碼,監(jiān)聽willChangeValueForKey 和 didChangeValueForKey可得到驗證男娄。

三行贪、KVO/KVC圖示

以Person類為示例把兔,在添加addObserver方法之前,實例對象瓮顽、類對象的關(guān)系:

實例addObserver之前,對象與類得關(guān)系

當對一個對象進行kvo監(jiān)聽的時候围橡,會生成一個NSKVONotifying_前綴的類暖混,然后我們實際的操作是對這個類進行的。某對象在addObserver:之后翁授,這個對象的isa指針已經(jīng)指向了NSKVONotifying_前綴的類拣播,其父類被設(shè)置為Person類。

//// 驗證1
// 如果在原有工程中收擦,創(chuàng)建NSKVONotifying_Person類贮配,運行代碼會報 KVO failed to allocate class pair for name NSKVONotifying_Person, automatic key-value observing will not work for this class 錯誤,
//因為原有工程中已經(jīng)存在該類塞赂,故無法運行時生成該類泪勒。

//// 驗證2
// 我們可以在addObserver:前后斷點打印對象的isa指針,會發(fā)現(xiàn)兩個實例對應的打印結(jié)果不同宴猾。
NSLog(@"KVO之前 - %@", object_getClass(self.person));
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:@"keyOfMy"];
NSLog(@"KVO之后 - %@", object_getClass(self.person));

//log
//2021-06-22 20:02:02.101462+0800 TestProj[81369:8704592] KVO之前 - Person
//2021-06-22 20:02:07.003729+0800 TestProj[81369:8704592] KVO之后 - NSKVONotifying_Person

//// 驗證3
//在 person 對象調(diào)用 addObserver: forKeyPath: options: context: 方法前后添加如下代碼圆存,打印結(jié)果不同 。
NSLog(@"KVO之前 - %p", [self.person methodForSelector:@selector(setAge:)]);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:@"keyOfMy"];
NSLog(@"KVO之后 - %p", [self.person methodForSelector:@selector(setAge:)]);

//log
2021-06-22 20:12:15.245515+0800 TestProj[81577:8714132] KVO之前 - 0x10414be60
2021-06-22 20:12:23.842112+0800 TestProj[81577:8714132] KVO之后 - 0x7fff207bf79f
實例addObserver之后仇哆,對象與類得關(guān)系

在addObserver:后調(diào)用setage方法沦辙,會根據(jù)對象的isa找到NSKVONotifying_Person,然后在類的方法列表中找到setage讹剔。

通過下面方法油讯,查看NSKVONotifying_Person 的內(nèi)部結(jié)構(gòu):

- (void)printMethodNamesOfClass:(Class)cls{
    unsigned int count;
    // 獲得方法數(shù)組
    Method *methodList = class_copyMethodList(cls, &count);
    // 存儲方法名
    NSMutableString *methodNames = [NSMutableString string];
    // 遍歷所有的方法
    for (int i = 0; i < count; i++) {
        // 獲得方法
        Method method = methodList[I];
        // 獲得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    // 釋放
    free(methodList);
    // 打印方法名
    NSLog(@"%@ %@", cls, methodNames);
}

// log,在kvo監(jiān)聽下包含了四個方法:
// NSKVONotifying_MJPerson setAge:, class, dealloc, _isKVOA,

可見Apple是不希望暴露NSKVONotifyin_Person延欠,重寫了class方法陌兑,大概實現(xiàn)如下:

- (Class) class {
     // 得到類對象,在找到類對象父類
     return class_getSuperclass(object_getClass(self));
}

重寫setName: 的內(nèi)部實現(xiàn)衫冻,其中調(diào)用了"_NSSetObjectValueAndNotify()" :

- (void)setName:(NSString *)name {
    _NSSetObjectValueAndNotify()
}

- (void)willChangeValueForKey:(NSString *)key {
    [super willChangeValueForKey:key];
}

- (void)didChangeValueForKey:(NSString *)key {
    [super didChangeValueForKey:key];
    [observer observeValueForKeyPath:@"name"];
}

void _NSSetObjectValueAndNotify() {
    [self willChangeValueForKey:@"name"];
    [super setName:name];
    [self didChangeValueForKey:@"name"];
}

手動觸發(fā)KVO

因為 KVO 的本質(zhì)是重寫了 set 方法诀紊, set 方法內(nèi)部調(diào)用了willChangeValueForKey 和 didChangeValueForKey 方法,直接修改成員變量并不會調(diào)用 set 方法隅俘。由此可知邻奠,KVO 的觸發(fā)條件一般是修改監(jiān)聽對象屬性值,但也可在不修改被監(jiān)聽屬性值的情況下觸發(fā) KVO 監(jiān)聽回調(diào)为居。

[self.person1 willChangeValueForKey:@"age"];
[self.person1 didChangeValueForKey:@"age"];

注:直接修改成員變量不會觸發(fā) KVO 監(jiān)聽方法碌宴,

KVC觸發(fā)KVO

kvc常見的API有:

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;

setValue:forKey:的原理,

setValue:forKey:流程

方法accessInstanceVariablesDirectly:(是否允許訪問成員變量)蒙畴,默認返回YES贰镣。該方法有個應用場景就是如果你自己寫框架呜象,你的一些私有的變量不想被外部通過KVC的方式去修改,就可以重寫這個方法碑隆,返回 NO 即可恭陡!

valueForKey:的原理,

valueForKey:流程

KVC修改屬性值上煤,是會觸發(fā)KVO的休玩,原因是系統(tǒng)自動實現(xiàn)了set方法,并且底層都會調(diào)用 willChangeValueForKey和 didChangeValueForKey劫狠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拴疤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子独泞,更是在濱河造成了極大的恐慌呐矾,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懦砂,死亡現(xiàn)場離奇詭異蜒犯,居然都是意外死亡,警方通過查閱死者的電腦和手機荞膘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門愧薛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衫画,你說我怎么就攤上這事毫炉。” “怎么了削罩?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵瞄勾,是天一觀的道長。 經(jīng)常有香客問我弥激,道長进陡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任微服,我火速辦了婚禮趾疚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘以蕴。我一直安慰自己糙麦,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布丛肮。 她就那樣靜靜地躺著赡磅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宝与。 梳的紋絲不亂的頭發(fā)上焚廊,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天冶匹,我揣著相機與錄音,去河邊找鬼咆瘟。 笑死嚼隘,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的袒餐。 我是一名探鬼主播嗓蘑,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼匿乃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起豌汇,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤幢炸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拒贱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宛徊,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年逻澳,在試婚紗的時候發(fā)現(xiàn)自己被綠了闸天。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡斜做,死狀恐怖苞氮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瓤逼,我是刑警寧澤笼吟,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站霸旗,受9級特大地震影響贷帮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诱告,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一撵枢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧精居,春花似錦锄禽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至空猜,卻和暖如春绽慈,著一層夾襖步出監(jiān)牢的瞬間恨旱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工坝疼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搜贤,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓钝凶,卻偏偏與公主長得像仪芒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子耕陷,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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