KVC KVO

kvc


1004.png

四壶笼、KVO ( ) KVO 是觀察者模式的另一實現(xiàn)覆劈。

從上面的輸出可以看到,如果使用對象的 -class 方面輸出類名始終為:Foo炮障,這是因為新誕生的派生類重寫了 -class 方法聲稱它就是起初的基類坤候,只有使用 runtime 函數(shù) object_getClass 才能一睹芳容:NSKVONotifying_Foo白筹。注意看:x,y 以及 xy 三個被觀察對象真正的類型都是 NSKVONotifying_Foo系馆,而且該類實現(xiàn)了:setY:, setX:, class, dealloc, _isKVOA 這些方法顽照。其中 setX:, setY:, class 和 dealloc 前面已經(jīng)講到過代兵,私有方法 _isKVOA 估計是用來標示該類是一個 KVO 機制聲稱的類。在這里 Objective C 做了一些優(yōu)化植影,它對所有被觀察對象只生成一個派生類何乎,該派生類實現(xiàn)所有被觀察對象的 setter 方法,這樣就減少了派生類的數(shù)量抢野,提供了效率各墨。所有 NSKVONotifying_Foo 這個派生類重寫了 setX,setY方法(留意:沒有必要重寫 setZ 方法)恃轩。

原理:
1:動態(tài)生成子類:NSKVONotifiy_A
1:防止實例變量的影響,我添加斷言異常排除
2;動態(tài)子類的過程: A:生成類 B:添加Class方法 C:注冊
3:同時也判斷類的存在性做了處理
2:給動態(tài)子類添加Setter方法
3:消息轉(zhuǎn)發(fā)給父類(runtime消息轉(zhuǎn)發(fā))

使用了 isa 混寫(isa-swizzling)來實現(xiàn) KVO
使用 setter 方法改變值 KVO 會生效叉跛,使用 setValue:forKey 即 KVC 改變值 KVO 也會生效蒸殿,因為 KVC 會去
調(diào)用 setter 方法

那么通過直接賦值成員變量會觸發(fā) KVO 嗎?
不會,因為不會調(diào)用 setter 方法酥艳,需要加上
willChangeValueForKey 和 didChangeValueForKey 方法來手動觸發(fā)才行

KVC(Key-value coding)

KVC 就是指 iOS 的開發(fā)中,可以允許開發(fā)者通過 Key 名直接訪問對象的屬性莫换,或者給對象的屬性賦值骤铃。而 不需要調(diào)用明確的存取方法。這樣就可以在運行時動態(tài)地訪問和修改對象的屬性。而不是在編譯時確定补鼻, 這也是 iOS 開發(fā)中的黑魔法之一雅任。很多高級的 iOS 開發(fā)技巧都是基于 KVC 實現(xiàn)的

當調(diào)用 setValue:屬性值 forKey:@”name“的代碼時,硼婿,底層的執(zhí)行機制如下:

  • ? 程序優(yōu)先調(diào)用set<Key>:屬性值方法寇漫,代碼通過setter方法完成設(shè)置殉摔。注意,這里的<key>是指成員 變量名栓撞,首字母大小寫要符合 KVC 的命名規(guī)則瓤湘,下同

  • ? 如果沒有找到setName:方法,KVC機制會檢查+(BOOL)accessInstanceVariablesDirectly方法有 沒有返回 YES弛说,默認該方法會返回 YES剃浇,如果你重寫了該方法讓其返回 NO 的話,那么在這一步 KVC 會執(zhí)行 setValue:forUndefinedKey:方法虎囚,不過一般開發(fā)者不會這么做淘讥。所以 KVC 機制會搜索該類 里面有沒有名為<key>的成員變量,無論該變量是在類接口處定義窒朋,還是在類實現(xiàn)處定義蝗岖,也無論用 了什么樣的訪問修飾符抵赢,只在存在以<key>命名的變量,KVC 都可以對該成員變量賦值划提。

  • ? 如果該類即沒有set<key>:方法邢享,也沒有_<key>成員變量,KVC機制會搜索_is<Key>的成員變量伊履。

  • ? 和上面一樣款违,如果該類即沒有set<Key>:方法奠货,也沒有_<key>和_is<Key>成員變量,KVC機制再會

    繼續(xù)搜索<key>和 is<Key>的成員變量柔滔。再給它們賦值萍虽。

? 如果上面列出的方法或者成員變量都不存在,系統(tǒng)將會執(zhí)行該對象的setValue:forUndefinedKey: 方法超全,默認是拋出異常。

即如果沒有找到 Set<Key>方法的話蛾坯,會按照_key疏遏,_iskey财异,key,iskey 的順序搜索成員并進行賦值操作戳寸。

如果開發(fā)者想讓這個類禁用 KVC疫鹊,那么重寫+(BOOL)accessInstanceVariablesDirectly方法讓其返回No 即可,這樣的話如果 KVC 沒有找到 set<Key>:屬性名時,會直接用 setValue: forUndefinedKey方法蚌吸。

當調(diào)用 valueForKey:@”name“的代碼時羹唠,KVC 對 key 的搜索方式不同于 setValue:屬性值 forKey:@"name",其搜索方式如下:

  • ? 首先按get<Key>,<key>,is<Key>的順序方法查找getter方法,找到的話會直接調(diào)用缝彬。如果是BOOL 或者 Int 等值類型谷浅, * 會將其包裝成一個 NSNumber對象奶卓。
  • ? * 如果上面的getter沒有找到,KVC則會查找 countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes 格式的方法墩邀。如果 countOf<Key>方法和另外兩個方法中的一個被找到盏浙,那么就會返回一個可以響應(yīng)
    NSArray 所有方法的代理集合(它是NSKeyValueArray,是NSArray的子類),調(diào)用這個代理集合的方法竹海,或者說給這個代理集合發(fā)送屬于的方法,就會以或<Key>AtIndexes 這幾個方法組合的形式調(diào)用坊萝。還有一個可選的get<Key>:range方法十偶,所以你想重新定義 KVC 的一些功能园细,你可以添加這些方法,需要注意的是你的方法名要符合 KVC 的標準命名方 法狮崩,包括方法簽名睦柴。
  • ? 如果上面的方法沒有找到毡熏,那么會同時查找countOf<Key>痢法,enumerateOf<key>,memberOf<key> 格式的方法。如果這三個方法都找到蘸炸,那么就返回一個可以響應(yīng) NSSet 所的方法的代理集合尖奔,和上 面一樣提茁,給這個代理集合發(fā) NSSet 的消息建蹄,就會以 countOf<Key>,enumerateOf<key>,memberOf<key> 組合的形式調(diào)用丹弱。

  • ? 如果還沒有找到铲咨,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回 YES(默認行為)纤勒,那么和先前的設(shè)值一樣,會按
    _<key>, _iskey恐仑,key,is<Key>的順序搜索成員變量名裳仆,這里不推薦這么做,因為這樣直接訪問實例變量破 壞了封裝性纯丸,使代碼更脆弱静袖。如果重寫了類方法+ (BOOL)accessInstanceVariablesDirectly 返回 NO 的話队橙,那 么會直接調(diào)用 valueForUndefinedKey:方法,默認是拋出異常畅姊。

#import "NSObject+LGKVC.h"
#import <objc/runtime.h>

@implementation NSObject (LGKVC)

- (void)lg_setValue:(nullable id)value forKey:(NSString *)key{
   
    // KVC 自定義
    // 1: 判斷什么 key
    if (key == nil || key.length == 0) {
        return;
    }
    
    // 2: setter set<Key>: or _set<Key>,
    // key 要大寫
    NSString *Key = key.capitalizedString;
    // 拼接方法
    NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
    NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
    
    if ([self lg_performSelectorWithMethodName:setKey value:value]) {
        NSLog(@"*********%@**********",setKey);
        return;
    }else if ([self lg_performSelectorWithMethodName:_setKey value:value]) {
        NSLog(@"*********%@**********",_setKey);
        return;
    }else if ([self lg_performSelectorWithMethodName:setIsKey value:value]) {
        NSLog(@"*********%@**********",setIsKey);
        return;
    }
    
    // 3: 判斷是否響應(yīng) accessInstanceVariablesDirectly 返回YES NO 奔潰
    // 3:判斷是否能夠直接賦值實例變量
    if (![self.class accessInstanceVariablesDirectly] ) {
        @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }
    
    // 4: 間接變量
    // 獲取 ivar -> 遍歷 containsObjct -
    // 4.1 定義一個收集實例變量的可變數(shù)組
    NSMutableArray *mArray = [self getIvarListName];
    // _<key> _is<Key> <key> is<Key>
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        // 4.2 獲取相應(yīng)的 ivar
       Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        // 4.3 對相應(yīng)的 ivar 設(shè)置值
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:_isKey]) {
       Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:key]) {
       Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:isKey]) {
       Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }
    
    // 5:如果找不到相關(guān)實例
    @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
    
}


- (nullable id)lg_valueForKey:(NSString *)key{
    
    // 1:刷選key 判斷非空
    if (key == nil  || key.length == 0) {
        return nil;
    }

    // 2:找到相關(guān)方法 get<Key> <key> countOf<Key>  objectIn<Key>AtIndex
    // key 要大寫
    NSString *Key = key.capitalizedString;
    // 拼接方法
    NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }else if ([self respondsToSelector:NSSelectorFromString(key)]){
        return [self performSelector:NSSelectorFromString(key)];
    }else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
        if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
            int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
            for (int i = 0; i<num-1; i++) {
                num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            }
            for (int j = 0; j<num; j++) {
                id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
                [mArray addObject:objc];
            }
            return mArray;
        }
    }
#pragma clang diagnostic pop
    
    // 3:判斷是否能夠直接賦值實例變量
    if (![self.class accessInstanceVariablesDirectly] ) {
        @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }
    
    // 4.找相關(guān)實例變量進行賦值
    // 4.1 定義一個收集實例變量的可變數(shù)組
    NSMutableArray *mArray = [self getIvarListName];
    // _<key> _is<Key> <key> is<Key>
    // _name -> _isName -> name -> isName
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:_isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:key]) {
        Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }

    return @"";
}


#pragma mark - 相關(guān)方法
- (BOOL)lg_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
 
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
        return YES;
    }
    return NO;
}

- (id)performSelectorWithMethodName:(NSString *)methodName{
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
    }
    return nil;
}

- (NSMutableArray *)getIvarListName{
    
    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivars[i];
        const char *ivarNameChar = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
        NSLog(@"ivarName == %@",ivarName);
        [mArray addObject:ivarName];
    }
    free(ivars);
    return mArray;
}

@end



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

static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";

@interface LGInfo : NSObject
@property (nonatomic, weak) NSObject  *observer;
@property (nonatomic, copy) NSString    *keyPath;
@property (nonatomic, copy) LGKVOBlock  handleBlock;
@end

@implementation LGInfo

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block{
    if (self=[super init]) {
        _observer = observer;
        _keyPath  = keyPath;
        _handleBlock = block;
    }
    return self;
}
@end

@implementation NSObject (LGKVO)

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block{
    
    // 1: 驗證是否存在setter方法 : 不讓實例進來
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2: 動態(tài)生成子類
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa的指向 : LGKVONotifying_LGPerson
    object_setClass(self, newClass);
    // 4: 保存信息
    LGInfo *info = [[LGInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    if (!mArray) {
        mArray = [NSMutableArray arrayWithCapacity:1];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [mArray addObject:info];
}

- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    if (observerArr.count<=0) {
        return;
    }
    
    for (LGInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            [observerArr removeObject:info];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break;
        }
    }
    
    if (observerArr.count<=0) {
        // 指回給父類
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}

#pragma mark - 驗證是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
    Class superClass    = object_getClass(self);
    SEL setterSeletor   = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
    if (!setterMethod) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老鐵沒有當前%@的setter",keyPath] userInfo:nil];
    }
}

#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    // 防止重復(fù)創(chuàng)建生成新類
    if (newClass) return newClass;
    /**
     * 如果內(nèi)存不存在,創(chuàng)建生成
     * 參數(shù)一: 父類
     * 參數(shù)二: 新類的名字
     * 參數(shù)三: 新類的開辟的額外空間
     */
    // 2.1 : 申請類
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.2 : 注冊類
    objc_registerClassPair(newClass);
    // 2.3.1 : 添加class : class的指向是LGPerson
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class], classSEL);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
    // 2.3.2 : 添加setter
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
    return newClass;
}

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"來了:%@",newValue);
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    // 4: 消息轉(zhuǎn)發(fā) : 轉(zhuǎn)發(fā)給父類
    // 改變父類的值 --- 可以強制類型轉(zhuǎn)換
    void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    // void /* struct objc_super *super, SEL op, ... */
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
    };
    //objc_msgSendSuper(&superStruct,_cmd,newValue)
    lg_msgSendSuper(&superStruct,_cmd,newValue);
    
    // 5: 信息數(shù)據(jù)回調(diào)
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
    for (LGInfo *info in mArray) {
        if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
            info.handleBlock(info.observer, keyPath, oldValue, newValue);
        }
    }
}

Class lg_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}

#pragma mark - 從get方法獲取set方法的名稱 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
    
    if (getter.length <= 0) { return nil;}
    
    NSString *firstString = [[getter substringToIndex:1] uppercaseString];
    NSString *leaveString = [getter substringFromIndex:1];
    
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}

#pragma mark - 從set方法獲取getter方法的名稱 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
    
    if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
    
    NSRange range = NSMakeRange(3, setter.length-4);
    NSString *getter = [setter substringWithRange:range];
    NSString *firstString = [[getter substringToIndex:1] lowercaseString];
    return  [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}


@end


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末磕道,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子溺蕉,更是在濱河造成了極大的恐慌,老刑警劉巖疯特,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漓雅,死亡現(xiàn)場離奇詭異,居然都是意外死亡组题,警方通過查閱死者的電腦和手機吃衅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峻呕,“玉大人趣效,你說我怎么就攤上這事⊙端剑” “怎么了西傀?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵拥褂,是天一觀的道長。 經(jīng)常有香客問我莫秆,道長悔详,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任缝驳,我火速辦了婚禮,結(jié)果婚禮上萎庭,老公的妹妹穿的比我還像新娘齿拂。我一直安慰自己,他們只是感情好吗购,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布砸狞。 她就那樣靜靜地躺著,像睡著了一般踱启。 火紅的嫁衣襯著肌膚如雪研底。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天冠蒋,我揣著相機與錄音乾胶,去河邊找鬼。 笑死斩郎,一個胖子當著我的面吹牛喻频,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播半抱,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼窿侈,長吁一口氣:“原來是場噩夢啊……” “哼秋茫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤跺讯,失蹤者是張志新(化名)和其女友劉穎刀脏,沒想到半個月后超凳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡暂雹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年杭跪,在試婚紗的時候發(fā)現(xiàn)自己被綠了驰吓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡现斋,死狀恐怖偎蘸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情限书,我是刑警寧澤章咧,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站扰柠,受9級特大地震影響疼约,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜程剥,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望溪胶。 院中可真熱鬧,春花似錦哗脖、人聲如沸扳还。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衔蹲。三九已至,卻和暖如春舆驶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拘荡。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工撬陵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蟋定。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓驶兜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親远寸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

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