LBYFix-依賴Aspects的輕量級(jí)低風(fēng)險(xiǎn)的 iOS Hotfix 方案

在經(jīng)過由于Hotfix被下架大量App的風(fēng)波后绑咱,強(qiáng)大好用的JSPatch已成為過去式,雖然JSPatch團(tuán)隊(duì)聲稱是被蘋果誤殺旺拉,也已經(jīng)在和蘋果進(jìn)行溝通漾脂,并且提供了暫時(shí)的解決方案,但是在應(yīng)用中使用JSPatch還是有很大被下架的風(fēng)險(xiǎn)辆亏。

那現(xiàn)在有沒有輕量級(jí)低風(fēng)險(xiǎn)的庫可以實(shí)現(xiàn)Hotfix呢风秤?Aspects 就是這樣一個(gè)庫。雖然沒有JSpatch那么強(qiáng)大扮叨,那么完善缤弦,但也足夠應(yīng)付一般場(chǎng)景。

LBYFix 就是依賴 Aspects 實(shí)現(xiàn)的一套輕量級(jí)低風(fēng)險(xiǎn)的 iOS Hotfix 的方案彻磁,LBYFix 提供了三種能力:

    1. 通過JS代碼在任意方法前后注入代碼的能力碍沐。
    1. 通過JS代碼替換任意方法實(shí)現(xiàn)的能力。
    1. 通過JS代碼調(diào)用任意類/實(shí)例方法的能力衷蜓。

第一累提、二兩點(diǎn)就是用 Aspects 來實(shí)現(xiàn)的。第三點(diǎn)是用NSInvocation來實(shí)現(xiàn)的磁浇,當(dāng)然也可以用[NSObject performSelector:...]來調(diào)用任意類/實(shí)例方法斋陪,但是當(dāng)有兩個(gè)以上參數(shù)的時(shí)候[NSObject performSelector:...]就不好使了。

Aspects 使用姿勢(shì):

[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
    NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];

前插扯夭、后插鳍贾、替換某個(gè)方法都可以。使用類的方式很簡(jiǎn)單交洗,NSClassFromString 即可骑科,Selector 也一樣 NSSelectorFromString,這樣就能通過外部傳入 String构拳,內(nèi)部動(dòng)態(tài)構(gòu)造 Class 和 Selector 來達(dá)到 Fix 的效果了咆爽。

這種方式的安全性在于:

不需要中間 JS 文件,準(zhǔn)備工作全部在 Native 端完成置森。
沒有使用 App Store 不友好的類/方法斗埂。

LBYFix 使用姿勢(shì):

導(dǎo)入方式:
  • 方式一:直接將LBYFix和Aspects拖到項(xiàng)目中。
  • 方式二:通過pod庫導(dǎo)入凫海。

pod 'LBYFix', '~> 1.0.0'

使用流程:
  • 初始化LBYFix
    在application:didFinishLaunchingWithOptions:中初始化LBYFix
[LBYFix fixIt];
  • 替換方法實(shí)現(xiàn)
NSString *jsString = @"fixMethod('LBYFixDemo', 'instanceMightCrash:', 1, false, \
        function(instance, originInvocation, originArguments) { \
            if (originArguments[0] == null) { \
                runErrorBranch('LBYFixDemo', 'instanceMightCrash'); \
            } else { \
                runInvocation(originInvocation); \
            } \
        }); \
        ";
[LBYFix evalString:jsString];

上面的js代碼的意思是通過調(diào)用LBYFix暴露給JavaScript的fixMethod方法呛凶,將LBYFixDemo的instanceMightCrash實(shí)例方法替換成function實(shí)現(xiàn),如果function中originArguments[0]參數(shù)等于null行贪,則調(diào)用runErrorBranch方法漾稀,否則走原來的邏輯模闲。

  • 在方法前插入代碼
NSString *jsString = @"fixMethod('LBYFixDemo',  'runBeforeInstanceMethod', 2, false, \
        function(){ \
            runInstanceMethod('LBYFixDemo', 'beforeInstanceMethod:param2:', new Array('LBYFix', 888)); \
        });";
 [LBYFix evalString:jsString];

上面js代碼的意思是在LBYFixDemo類的runBeforeInstanceMethod方法前插入function實(shí)現(xiàn)。

  • 在方法后插入代碼
NSString *jsString = @"fixMethod('LBYFixDemo2', 'runAfterClassMethod', 0, true, \
        function(){ \
            runClassMethod('LBYFixDemo2', 'afterClassMethod:param2:', new Array('LBYFix', 999)); \
        }); \
        ";
[LBYFix evalString:jsString];

上面js代碼的意思是在LBYFixDemo2的runAfterClassMethod方法后插入function實(shí)現(xiàn)崭捍。

  • 執(zhí)行沒有參數(shù)的方法
NSString *jsString = @"runInstanceMethod('LBYFixDemo3', 'instanceMethodHasNoParams')";
[LBYFix evalString:jsString];

上面js代碼的意思是調(diào)用LBYFixDemo3類的instanceMethodHasNoParams實(shí)例方法尸折。

  • 執(zhí)行帶多個(gè)參數(shù)的方法
NSString *jsString = @"runInstanceMethod('LBYFixDemo3', 'instanceMethodHasMultipleParams:size:rect:', new Array({x: 1.1, y: 2.2},  {width: 3.3, height: 4.4}, {origin: {x: 5.5, y: 6.6}, size: {width: 7.7, height: 8.8}}))\
    ";
[LBYFix evalString:jsString];

上面js代碼的意思是調(diào)用LBYFixDemo3類的instanceMethodHasMultipleParams:size:rect:實(shí)例方法,并通過數(shù)組傳入?yún)?shù)。

LBYFix源碼分析

初始化方法主要是提供了幾個(gè)給JavaScript調(diào)用的方法。

+ (void)fixIt {
    JSContext *context = [self context];
    
    context[@"fixMethod"] = ^(NSString *instanceName, NSString *selectorName, LBYFixOptions options, BOOL isClassMethod, JSValue *fixImpl) {
        [self fixWithMethod:isClassMethod options:options instanceName:instanceName selectorName:selectorName fixImp:fixImpl];
    };
    
    context[@"runInvocation"] = ^(NSInvocation *invocation) {
        [invocation invoke];
    };
    
    context[@"runErrorBranch"] = ^(NSString *instanceName, NSString *selectorName) {
        NSLog(@"runErrorBranch: instanceName = %@, selectorName = %@", instanceName, selectorName);
    };
    
    context[@"runClassMethod"] = ^id(NSString *className, NSString *selectorName, NSArray *arguments) {
        return [self runClassWithClassName:className selector:selectorName arguments:arguments];
    };
    
    context[@"runInstanceMethod"] = ^id(NSString * className, NSString *selectorName, NSArray *arguments) {
        return [self runInstanceWithInstance:className selector:selectorName arguments:arguments];
    };
}

fixWithMethod: options:方法是通過Aspects進(jìn)行前插浙炼、后插、替換方法亮航。

+ (void)fixWithMethod:(BOOL)isClassMethod options:(LBYFixOptions)options instanceName:(NSString *)instanceName selectorName:(NSString *)selectorName fixImp:(JSValue *)fixImpl {
    Class klass = NSClassFromString(instanceName);
    if (isClassMethod) {
        klass = object_getClass(klass);
    }
    
    SEL sel = NSSelectorFromString(selectorName);
    [klass aspect_hookSelector:sel withOptions:(AspectOptions)options usingBlock:^(id<AspectInfo> aspectInfo) {
        [fixImpl callWithArguments:@[aspectInfo.instance, aspectInfo.originalInvocation, aspectInfo.arguments]];
    } error:nil];
}

runClassWithClassName:selector:arguments:通過NSInvocation調(diào)用類方法。

+ (id)runClassWithClassName:(NSString *)className selector:(NSString *)selector arguments:(NSArray *)arguments {
    Class klass = NSClassFromString(className);
    if (!klass) return nil;
    
    SEL sel = NSSelectorFromString(selector);
    if (!sel) return nil;
    
    if (![klass respondsToSelector:sel]) return nil;
    
    NSMethodSignature *signature = [klass methodSignatureForSelector:sel];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.selector = sel;
    [self setInv:invocation withSig:signature andArgs:arguments];
    [invocation invokeWithTarget:klass];
    
    return [self getReturnFromInv:invocation withSig:signature];
}

runInstanceWithInstance:selector:arguments:通過NSInvocation調(diào)用實(shí)例方法谍倦。

+ (id)runInstanceWithInstance:(NSString *)className selector:(NSString *)selector arguments:(NSArray *)arguments {
    Class klass = NSClassFromString(className);
    if (!klass) return nil;
    
    SEL sel = NSSelectorFromString(selector);
    if (!sel) return nil;
    
    id instance = [[klass alloc] init];
    
    if (![instance respondsToSelector:sel]) return nil;
    
    NSMethodSignature *signature = [instance methodSignatureForSelector:sel];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.selector = sel;
    [self setInv:invocation withSig:signature andArgs:arguments];
    [invocation invokeWithTarget:instance];
    
    return [self getReturnFromInv:invocation withSig:signature];
}

getReturnFromInv: withSig:用來獲取方法的返回值塞赂。

+ (id)getReturnFromInv:(NSInvocation *)inv withSig:(NSMethodSignature *)sig {
    NSUInteger length = [sig methodReturnLength];
    if (length == 0) return nil;
    
    char *type = (char *)[sig methodReturnType];
    while (*type == 'r' ||  // const
           *type == 'n' ||  // in
           *type == 'N' ||  // inout
           *type == 'o' ||  // out
           *type == 'O' ||  // bycopy
           *type == 'R' ||  // byref
           *type == 'V') {  // oneway
        type++; // cutoff useless prefix
    }
    
#define return_with_number(_type_) \
do { \
_type_ ret; \
[inv getReturnValue:&ret]; \
return @(ret); \
} while(0)
    
    switch (*type) {
        case 'v': return nil; // void
        case 'B': return_with_number(bool);
        case 'c': return_with_number(char);
        case 'C': return_with_number(unsigned char);
        case 's': return_with_number(short);
        case 'S': return_with_number(unsigned short);
        case 'i': return_with_number(int);
        case 'I': return_with_number(unsigned int);
        case 'l': return_with_number(int);
        case 'L': return_with_number(unsigned int);
        case 'q': return_with_number(long long);
        case 'Q': return_with_number(unsigned long long);
        case 'f': return_with_number(float);
        case 'd': return_with_number(double);
        case 'D': { // long double
            long double ret;
            [inv getReturnValue:&ret];
            return [NSNumber numberWithDouble:ret];
        };
            
        case '@': { // id
            void *ret;
            [inv getReturnValue:&ret];
            return (__bridge id)(ret);
        };
            
        case '#' : { // Class
            Class ret = nil;
            [inv getReturnValue:&ret];
            return ret;
        };
            
        default: { // struct / union / SEL / void* / unknown
            const char *objCType = [sig methodReturnType];
            char *buf = calloc(1, length);
            if (!buf) return nil;
            [inv getReturnValue:buf];
            NSValue *value = [NSValue valueWithBytes:buf objCType:objCType];
            free(buf);
            return value;
        };
    }
#undef return_with_number
}

setInv: withSig: andArgs:設(shè)置方法的參數(shù)泪勒。

+ (void)setInv:(NSInvocation *)inv withSig:(NSMethodSignature *)sig andArgs:(NSArray *)args {
    
#define args_length_judgments(_index_) \
[self argsLengthJudgment:args index:_index_] \
    
#define set_with_args(_index_, _type_, _sel_) \
do { \
_type_ arg; \
if (args_length_judgments(_index_-2)) { \
arg = [args[_index_-2] _sel_]; \
} \
[inv setArgument:&arg atIndex:_index_]; \
} while(0)
    
#define set_with_args_struct(_dic_, _struct_, _param_, _key_, _sel_) \
do { \
if (_dic_ && [_dic_ isKindOfClass:[NSDictionary class]]) { \
if ([_dic_.allKeys containsObject:_key_]) { \
_struct_._param_ = [_dic_[_key_] _sel_]; \
} \
} \
} while(0)

    NSUInteger count = [sig numberOfArguments];
    for (int index = 2; index < count; index++) {
        char *type = (char *)[sig getArgumentTypeAtIndex:index];
        while (*type == 'r' ||  // const
               *type == 'n' ||  // in
               *type == 'N' ||  // inout
               *type == 'o' ||  // out
               *type == 'O' ||  // bycopy
               *type == 'R' ||  // byref
               *type == 'V') {  // oneway
            type++;             // cutoff useless prefix
        }
        
        BOOL unsupportedType = NO;
        switch (*type) {
            case 'v':   // 1:void
            case 'B':   // 1:bool
            case 'c':   // 1: char / BOOL
            case 'C':   // 1: unsigned char
            case 's':   // 2: short
            case 'S':   // 2: unsigned short
            case 'i':   // 4: int / NSInteger(32bit)
            case 'I':   // 4: unsigned int / NSUInteger(32bit)
            case 'l':   // 4: long(32bit)
            case 'L':   // 4: unsigned long(32bit)
            { // 'char' and 'short' will be promoted to 'int'
                set_with_args(index, int, intValue);
            } break;
                
            case 'q':   // 8: long long / long(64bit) / NSInteger(64bit)
            case 'Q':   // 8: unsigned long long / unsigned long(64bit) / NSUInteger(64bit)
            {
                set_with_args(index, long long, longLongValue);
            } break;
                
            case 'f': // 4: float / CGFloat(32bit)
            {
                set_with_args(index, float, floatValue);
            } break;
                
            case 'd': // 8: double / CGFloat(64bit)
            case 'D': // 16: long double
            {
                set_with_args(index, double, doubleValue);
            } break;
                
            case '*': // char *
            {
                if (args_length_judgments(index-2)) {
                    NSString *arg = args[index-2];
                    if ([arg isKindOfClass:[NSString class]]) {
                        const void *c = [arg UTF8String];
                        [inv setArgument:&c atIndex:index];
                    }
                }
            } break;
                
            case '#': // Class
            {
                if (args_length_judgments(index-2)) {
                    NSString *arg = args[index-2];
                    if ([arg isKindOfClass:[NSString class]]) {
                        Class klass = NSClassFromString(arg);
                        if (klass) {
                            [inv setArgument:&klass atIndex:index];
                        }
                    }
                }
            } break;
                
            case '@': // id
            {
                if (args_length_judgments(index-2)) {
                    id arg = args[index-2];
                    [inv setArgument:&arg atIndex:index];
                }
            } break;

            case '{': // struct
            {
                if (strcmp(type, @encode(CGPoint)) == 0) {
                    CGPoint point = {0};
                    
                    if (args_length_judgments(index-2)) {
                        NSDictionary *dict = args[index-2];
                        set_with_args_struct(dict, point, x, @"x", doubleValue);
                        set_with_args_struct(dict, point, y, @"y", doubleValue);
                    }
                    [inv setArgument:&point atIndex:index];
                } else if (strcmp(type, @encode(CGSize)) == 0) {
                    CGSize size = {0};
                    
                    if (args_length_judgments(index-2)) {
                        NSDictionary *dict = args[index-2];
                        set_with_args_struct(dict, size, width, @"width", doubleValue);
                        set_with_args_struct(dict, size, height, @"height", doubleValue);
                    }
                    [inv setArgument:&size atIndex:index];
                } else if (strcmp(type, @encode(CGRect)) == 0) {
                    CGRect rect;
                    CGPoint origin = {0};
                    CGSize size = {0};
                    
                    if (args_length_judgments(index-2)) {
                        NSDictionary *dict = args[index-2];
                        NSDictionary *pDict = dict[@"origin"];
                        set_with_args_struct(pDict, origin, x, @"x", doubleValue);
                        set_with_args_struct(pDict, origin, y, @"y", doubleValue);
                        
                        NSDictionary *sDict = dict[@"size"];
                        set_with_args_struct(sDict, size, width, @"width", doubleValue);
                        set_with_args_struct(sDict, size, height, @"height", doubleValue);
                    }
                    rect.origin = origin;
                    rect.size = size;
                    [inv setArgument:&rect atIndex:index];
                } else if (strcmp(type, @encode(CGVector)) == 0) {
                    CGVector vector = {0};
                    
                    if (args_length_judgments(index-2)) {
                        NSDictionary *dict = args[index-2];
                        set_with_args_struct(dict, vector, dx, @"dx", doubleValue);
                        set_with_args_struct(dict, vector, dy, @"dy", doubleValue);
                    }
                    [inv setArgument:&vector atIndex:index];
                } else if (strcmp(type, @encode(CGAffineTransform)) == 0) {
                    CGAffineTransform form = {0};
                    
                    if (args_length_judgments(index-2)) {
                        NSDictionary *dict = args[index-2];
                        set_with_args_struct(dict, form, a, @"a", doubleValue);
                        set_with_args_struct(dict, form, b, @"b", doubleValue);
                        set_with_args_struct(dict, form, c, @"c", doubleValue);
                        set_with_args_struct(dict, form, d, @"d", doubleValue);
                        set_with_args_struct(dict, form, tx, @"tx", doubleValue);
                        set_with_args_struct(dict, form, ty, @"ty", doubleValue);
                    }
                    [inv setArgument:&form atIndex:index];
                } else if (strcmp(type, @encode(CATransform3D)) == 0) {
                    CATransform3D form3D = {0};
                    
                    if (args_length_judgments(index-2)) {
                        NSDictionary *dict = args[index-2];
                        set_with_args_struct(dict, form3D, m11, @"m11", doubleValue);
                        set_with_args_struct(dict, form3D, m12, @"m12", doubleValue);
                        set_with_args_struct(dict, form3D, m13, @"m13", doubleValue);
                        set_with_args_struct(dict, form3D, m14, @"m14", doubleValue);
                        set_with_args_struct(dict, form3D, m21, @"m21", doubleValue);
                        set_with_args_struct(dict, form3D, m22, @"m22", doubleValue);
                        set_with_args_struct(dict, form3D, m23, @"m23", doubleValue);
                        set_with_args_struct(dict, form3D, m24, @"m24", doubleValue);
                        set_with_args_struct(dict, form3D, m31, @"m31", doubleValue);
                        set_with_args_struct(dict, form3D, m32, @"m32", doubleValue);
                        set_with_args_struct(dict, form3D, m33, @"m33", doubleValue);
                        set_with_args_struct(dict, form3D, m34, @"m34", doubleValue);
                        set_with_args_struct(dict, form3D, m41, @"m41", doubleValue);
                        set_with_args_struct(dict, form3D, m42, @"m42", doubleValue);
                        set_with_args_struct(dict, form3D, m43, @"m43", doubleValue);
                        set_with_args_struct(dict, form3D, m44, @"m44", doubleValue);
                    }
                    [inv setArgument:&form3D atIndex:index];
                } else if (strcmp(type, @encode(NSRange)) == 0) {
                    NSRange range = {0};
                    
                    if (args_length_judgments(index-2)) {
                        NSDictionary *dict = args[index-2];
                        set_with_args_struct(dict, range, location, @"location", unsignedIntegerValue);
                        set_with_args_struct(dict, range, length, @"length", unsignedIntegerValue);
                    }
                    [inv setArgument:&range atIndex:index];
                } else if (strcmp(type, @encode(UIOffset)) == 0) {
                    UIOffset offset = {0};
                    
                    if (args_length_judgments(index-2)) {
                        NSDictionary *dict = args[index-2];
                        set_with_args_struct(dict, offset, horizontal, @"horizontal", doubleValue);
                        set_with_args_struct(dict, offset, vertical, @"vertical", doubleValue);
                    }
                    [inv setArgument:&offset atIndex:index];
                } else if (strcmp(type, @encode(UIEdgeInsets)) == 0) {
                    UIEdgeInsets insets = {0};
                    
                    if (args_length_judgments(index-2)) {
                        NSDictionary *dict = args[index-2];
                        set_with_args_struct(dict, insets, top, @"top", doubleValue);
                        set_with_args_struct(dict, insets, left, @"left", doubleValue);
                        set_with_args_struct(dict, insets, bottom, @"bottom", doubleValue);
                        set_with_args_struct(dict, insets, right, @"right", doubleValue);
                    }
                    [inv setArgument:&insets atIndex:index];
                } else {
                    unsupportedType = YES;
                }
            } break;
                
            case '^': // pointer
            {
                unsupportedType = YES;
            } break;
                
            case ':': // SEL
            {
                unsupportedType = YES;
            } break;
                
            case '(': // union
            {
                unsupportedType = YES;
            } break;
                
            case '[': // array
            {
                unsupportedType = YES;
            } break;
                
            default: // what?!
            {
                unsupportedType = YES;
            } break;
        }
        
        NSAssert(!unsupportedType, @"arg unsupportedType");
    }
}

參考

LBYFix Demo下載
輕量級(jí)低風(fēng)險(xiǎn) iOS Hotfix 方案
Aspects源碼解析
JavaScriptCore 使用
YYKit
Issues: "When exchanged a class method after a instance method exchanged, the class method will be invalid"

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昼蛀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子圆存,更是在濱河造成了極大的恐慌叼旋,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沦辙,死亡現(xiàn)場(chǎng)離奇詭異夫植,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)油讯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門详民,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人陌兑,你說我怎么就攤上這事沈跨。” “怎么了兔综?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵饿凛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我软驰,道長(zhǎng)涧窒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任锭亏,我火速辦了婚禮纠吴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慧瘤。我一直安慰自己戴已,他們只是感情好膳凝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著恭陡,像睡著了一般蹬音。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上休玩,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天著淆,我揣著相機(jī)與錄音,去河邊找鬼拴疤。 笑死永部,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的呐矾。 我是一名探鬼主播苔埋,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蜒犯!你這毒婦竟也來了组橄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤罚随,失蹤者是張志新(化名)和其女友劉穎玉工,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淘菩,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡遵班,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了潮改。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狭郑。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖汇在,靈堂內(nèi)的尸體忽然破棺而出翰萨,到底是詐尸還是另有隱情,我是刑警寧澤趾疚,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布缨历,位于F島的核電站,受9級(jí)特大地震影響糙麦,放射性物質(zhì)發(fā)生泄漏辛孵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一赡磅、第九天 我趴在偏房一處隱蔽的房頂上張望魄缚。 院中可真熱鬧,春花似錦、人聲如沸冶匹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嚼隘。三九已至诽里,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間飞蛹,已是汗流浹背谤狡。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卧檐,地道東北人墓懂。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像霉囚,于是被迫代替她去往敵國(guó)和親捕仔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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