iOS常见Crash案例总结

下一篇见iOS Crash 捕获处理

  • 1、集合类相关崩溃
  • 2、找不到方法的实现unrecognized selector sent to instance
  • 3、KVC、KVO造成的crash
  • 4、EXC_BAD_ACCESS
  • 5、多线程中的崩溃

头文件和.m实现的文件公共部分代码


#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
/**
 代理协议协议是为了解决多个VC有同名同参数的方法而内部实现的逻辑不同,(如果内部逻辑都相同就可以抽取成单例了),
 所以为了避免在每个VC的.h 中都声明一个同名同参的方法显得冗余就将这些方法抽取出来封装成一个协议,
 各个VC遵循这个协议就等同于在.h 中声明了这些方法。
 但因为有些协议方法VC是不需要的,可以用@optional 标识
 */
@protocol PublicMethodProtocol <NSObject>
- (void)requiredProtocolMethodA; //默认是@required

@optional
- (void)optionalProtocolMethodAB;
@end


@interface CrashCaseController : UIViewController
@property(nonatomic, weak) id<PublicMethodProtocol> delegate;
@end


@interface UnrecognizedSelectorVCObj : NSObject<PublicMethodProtocol>
- (void)onlyDefinMethod;
@end

@interface KvcKvoCrashVCObj : NSObject
@property (nonatomic, strong) NSString *age;
@end

@interface CrashCaseController (AssociatedObject)
@property (nonatomic, strong) UIView *associateView;
@end

NS_ASSUME_NONNULL_END


#import "CrashCaseController.h"
#import <objc/runtime.h>
@implementation UnrecognizedSelectorVCObj

- (void)requiredProtocolMethodA {
    
}
- (void)onlyDefinMethod {
    
}
@end


@implementation KvcKvoCrashVCObj
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"value = %@, key = %@", value,key);
}

- (id)valueForUndefinedKey:(NSString *)key {
    return nil;
}

@end

@implementation CrashCaseController (AssociatedObject)
- (void)setAssociateView:(UIView *)associateView {
    objc_setAssociatedObject(self, @selector(associateView), associateView, OBJC_ASSOCIATION_ASSIGN);
    //objc_setAssociatedObject(self, @selector(associateView), associateView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *)associateView {
    return objc_getAssociatedObject(self, _cmd);;
}
@end


@interface CrashCaseController ()
@property(nonatomic, strong) NSMutableArray *mutableArray;
@property(nonatomic, strong) NSMutableArray *unsafeArray;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^blcok)(void);
@property (nonatomic, weak) UIView *weakView;
@property (nonatomic, unsafe_unretained) UIView *unSafeView;
@property (nonatomic, assign) UIView *assignView;

@end

@implementation CrashCaseController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self GCDCrashCase];
}
@end

场景一:集合类操作crash

crash原因:越界、添加nil、多线程非原子性操作、遍历的同时移除元素
解决方案:使用前用if(长度判断)、在读写元素时正确使用遍历方式

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 场景一:集合类操作crash
 crash原因:越界、添加nil、多线程非原子性操作、遍历的同时移除元素
 解决方案:使用前用if(长度判断)、在移除元素时正确使用遍历方式
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)collectionCrash {
    [self collectionCrash1];
    [self collectionCrash2];
    [self collectionCrash3];
    [self collectionCrash4];
    [self collectionCrash5];
    [self collectionCrash6];
}
//1、字符串超长度
- (void)collectionCrash1 {
    NSString * string1 = @"0123456789";
    NSRange range1 = NSMakeRange(12, 1); //crash崩溃 :out of bounds
    NSLog(@"新字符串是:%@",[string1 substringWithRange:range1]);
}
//2、数组越界
- (void)collectionCrash2 {
    NSArray * nameArray = @[@"Roy", @"Mike", @"Jordan"];
    NSString * name = nameArray[3]; //crash崩溃 :index 3 beyond bounds [0 .. 2]'
    NSLog(@"name=%@",name);
}
//3、数组遍历的时候使用错误的方式移除元素
- (void)collectionCrash3 {
    NSMutableArray<NSNumber*>* array = [NSMutableArray array];
    [array addObject:@1];
    [array addObject:@2];
    [array addObject:@3];
    [array addObject:@4];
    [array addObject:@5];
    [array addObject:@6];
    /**
     * 遍历数组的方式大概有三种:for, for in, enumerateObjectsUsingBlock:。
     * 在这三种方法中,使用for和enumerateObjectsUsingBlock:迭代时,可以修改数组,不会有Crash等问题。
     */
    [array enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.integerValue == 1) {
            [array removeObject:obj]; // 不崩溃
        }
    }];
    NSLog(@"array enumerate = %@",array);
    
    for (int i = 0; i < array.count; i++) {
        NSNumber *num = array[i];
        if (num.integerValue == 2) {
            [array removeObject:@2]; // 不崩溃
        }
    }
    NSLog(@"array for  = %@",array);
    
    for (NSNumber* obj in array) {
        if (obj.integerValue == 3) {
            [array removeObject:obj];//crash崩溃 :Collection <__NSArrayM: 0x2829946f0> was mutated while being enumerated
        }
    }
    NSLog(@"array for in = %@",array);
}
//4、数组插入nil
- (void)collectionCrash4 {
    NSString * name;
    NSMutableArray *mutableAry = [NSMutableArray arrayWithObjects:@"Roy",name, @"Mike", @"Jordan", nil];
    NSLog(@"nameArray=%@",mutableAry); // nameArray=(Roy) 对可变数组arrayWithObjects初始化遇到nil 会直接结束
    [mutableAry addObject:name]; //crash崩溃 : object cannot be nil'
}
//5、字典插入nil
- (void)collectionCrash5 {
    NSNumber *jordanAge;
    NSDictionary *ages = @{@"Roy":@22, @"Mike":@24, @"Jordan":jordanAge}; //crash崩溃 :attempt to insert nil object
    NSLog(@"ages=%@",ages);
}
//6、NSURL插入nil
- (void)collectionCrash6 {
    NSString * urString;
    NSURL * url = [[NSURL alloc] initFileURLWithPath:urString];//crash崩溃:nil string parameter'
    NSURL * url2 = [[NSURL alloc] initWithString:urString];//crash崩溃:nil string parameter'
    NSLog(@"%@ %@",url, url2);
}

场景二:Unrecognized Selector

crash原因:找不到方法iOS系统抛出异常崩溃
解决方案:
1、方法调用前进行 respondsToSelector 判断
2、在.h中声明的方法如果用不到就去掉,用得到就同时在.m文件中实现
3、给NSObject添加一个分类,实现消息转发的几个方法


/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 场景二:Unrecognized Selector
 crash原因:找不到方法iOS系统抛出异常崩溃
 解决方案:
 1、方法调用前进行 respondsToSelector 判断
 2、在.h中声明的方法如果用不到就去掉,用得到就同时在.m文件中实现
 3、给NSObject添加一个分类,实现消息转发的几个方法
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)unrecognizedSelectorCrash {
    [self unrecognizedSelectorCrash1];
    [self unrecognizedSelectorCrash2];
    [self unrecognizedSelectorCrash3];
    [self unrecognizedSelectorCrash4];
    [self unrecognizedSelectorCrash5];
}
//1、未实现.h声明的方法
- (void)unrecognizedSelectorCrash1 {
    UnrecognizedSelectorVCObj* obj = [[UnrecognizedSelectorVCObj alloc] init];
    [obj onlyDefinMethod]; //crash崩溃:unrecognized selector sent to instance
}
//2、未实现遵守协议中的方法
- (void)unrecognizedSelectorCrash2 {
    UnrecognizedSelectorVCObj* obj = [[UnrecognizedSelectorVCObj alloc] init];
    [obj requiredProtocolMethodA];//crash崩溃:unrecognized selector sent to instance
}
//3、未实现代理方法
- (void)unrecognizedSelectorCrash3 {
    UnrecognizedSelectorVCObj* obj = [[UnrecognizedSelectorVCObj alloc] init];
    self.delegate = obj;
    [self.delegate optionalProtocolMethodAB]; //crash崩溃:unrecognized selector sent to instance
}
//4、可变对象copy返回不可变对象后调用了可变对象方法
/*
 *crash崩溃:unrecognized selector sent to instance
 *@property (nonatomic, copy) NSMutableArray *mArray;
 等同于
 - (void)setMutableArray:(NSMutableArray *)mutableArray {
 _mutableArray = [mutableArray copy]; //copy返回的对象都是不可变的
 修改为 _mutableArray = [mutableArray mutableCopy]; //对可变对象mutableCopy是深拷贝返回的新对象是可变的
 }
 解决办法:使用strong修饰或者重写set方法
 */
- (void)unrecognizedSelectorCrash4 {
    NSMutableArray* array = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
    [array addObject:@4];
    self.mutableArray = array;
    [self.mutableArray addObject:@5];//crash崩溃:unrecognized selector sent to instance
}
//5、低版本系统使用高版本API
- (void)unrecognizedSelectorCrash5 {
    if (@available(iOS 10.0, *)) {
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            
        }];
    } else {
        // Fallback on earlier versions
    }
}
//注释该该方法测试crash
- (void)setMutableArray:(NSMutableArray *)mutableArray {
    _mutableArray = [mutableArray mutableCopy];
}

场景三:KVC、KVO造成的crash

KVC造成crash原因:给不存在的key(包括key为nil)设置value
解决方案:重写类的setValue:forUndefinedKey:和valueForUndefinedKey:
KVO造成crash原因: 观察者没有实现observeValueForKeyPath:ofObject:changecontext:方法:,会崩溃或多次移除观察者
解决方案:观察者需要实现observeValueForKeyPath方法,addObserver和removeObserver一定要成对出现

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 场景三:KVC、KVO造成的crash
 KVC造成crash原因:给不存在的key(包括key为nil)设置value
 解决方案:重写类的setValue:forUndefinedKey:和valueForUndefinedKey:
 
 KVO造成crash原因: 观察者没有实现observeValueForKeyPath:ofObject:changecontext:方法:,会崩溃或多次移除观察者
 解决方案:观察者需要实现observeValueForKeyPath方法
 addObserver和removeObserver一定要成对出现,
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)KvcKvoCrashCase {
    [self KvcKvoCrashCase1];
    [self KvcKvoCrashCase2];
    [self KvcKvoCrashCase3];
    [self KvcKvoCrashCase4];
    [self KvcKvoCrashCase5];
}
//1、key为nil
- (void)KvcKvoCrashCase1 {
    KvcKvoCrashVCObj* obj = [[KvcKvoCrashVCObj alloc]init];
    NSString *value, *key;
    // value 为nil不会崩溃
    [obj setValue:value forKey:@"age"];
    // key为nil会崩溃
    [obj setValue:@"value" forKey:key];//crash崩溃:attempt to set a value for a nil key
}
//2、key不是obj的属性
- (void)KvcKvoCrashCase2 {
    KvcKvoCrashVCObj* obj = [[KvcKvoCrashVCObj alloc]init];
    [obj setValue:@"性别女" forKey:@"sex"];//crash崩溃:this class is not key value coding-compliant for the key age.
}
- (void)KvcKvoCrashCase3 {
    KvcKvoCrashVCObj* obj = [[KvcKvoCrashVCObj alloc]init];
    //1、没有有实现observeValueForKeyPath:ofObject:changecontext:方法:,会崩溃
    [obj addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew
             context:nil];
    obj.age = @"18岁";//crash崩溃:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
}
//2、重复添加观察者,不会崩溃,但是添加多少次,一次改变就会被观察多少次
- (void)KvcKvoCrashCase4 {
    KvcKvoCrashVCObj* obj = [[KvcKvoCrashVCObj alloc]init];
    [obj addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew
             context:nil];
    [obj addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew
             context:nil];
    
    obj.age = @"18岁";//crash崩溃:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
}
//3、重复移除观察者,会崩溃
- (void)KvcKvoCrashCase5 {
    KvcKvoCrashVCObj* obj = [[KvcKvoCrashVCObj alloc]init];
    [obj addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew
             context:nil];
    obj.age = @"18岁";
    [obj removeObserver:self forKeyPath:@"age"];
    [obj removeObserver:self forKeyPath:@"age"]; //crash崩溃:Cannot remove an observer for the key path "age"  because it is not registered as an observer.
}
//注释掉该方法测试crash
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"object = %@, keyPath = %@", object,keyPath);
}

场景四:EXC_BAD_ACCESS

crash原因:出现悬挂指针,对象没有被初始化,或者访问的对象被释放
解决方案:调用block的时候,做判断; 对象的属性使用正确的修饰方式(strong/weak/assign)

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 场景四:EXC_BAD_ACCESS
 crash原因:出现悬挂指针,对象没有被初始化,或者访问的对象被释放
 解决方案:
 调用block的时候,做判断
 对象的属性使用正确的修饰方式(strong/weak)
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)exc_bad_access_Crash {
    [self exc_bad_access_Crash1];
    [self exc_bad_access_Crash2];
    [self exc_bad_access_Crash3];
}
//1、访问没有实现的blcok
- (void)exc_bad_access_Crash1 {
    self.blcok(); //crash崩溃:Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)
}
//2、对象没有被初始化
- (void)exc_bad_access_Crash2 {
    UIView* view = [UIView alloc];
    view.backgroundColor = [UIColor blackColor];
    [self.view addSubview:view];
}
//3、访问的对象已经被释放掉
- (void)exc_bad_access_Crash3 {
    {
        UIView* view = [[UIView alloc]init];
        view.backgroundColor = [UIColor blackColor];
        self.weakView = view;
        self.unSafeView = view;
        self.assignView = view;
        self.associateView = view;
    }
    // ARC下weak对象释放后会自动置nil,因此下面的代码不会崩溃
    [self.view addSubview:self.weakView];
    // 野指针场景一:unsafe_unretained修饰的对象释放后,不会自动置nil,变成野指针,因此下面的代码会崩溃
    [self.view addSubview:self.unSafeView];
    // 野指针场景二:应该使用strong/weak修饰的对象,却错误的使用assign修饰,释放后不会自动置nil
    [self.view addSubview:self.assignView];
    // 野指针场景三:给类添加添加关联变量的时候,类似场景二,应该使用OBJC_ASSOCIATION_RETAIN_NONATOMIC修饰,却错误使用OBJC_ASSOCIATION_ASSIGN
    [self.view addSubview:self.associateView];
}

场景五:多线程中的崩溃

crash原因:死锁、子线程中更新UI、多个线程同时释放一个对象
解决方案: 多线程遇到需要同步的时候,加锁,添加信号量等进行同步操作。一般多线程发生的Crash,会收到SIGSEGV信号,表明试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 场景五:多线程中的崩溃
 crash原因:死锁、子线程中更新UI、多个线程同时释放一个对象
 解决方案:
 多线程遇到需要同步的时候,加锁,添加信号量等进行同步操作。一般多线程发生的Crash,会收到SIGSEGV信号,
 表明试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)GCDCrashCase {
    [self GCDCrashCase1];
    [self GCDCrashCase2];
    [self GCDCrashCase3];
    [self GCDCrashCase4];
}
//1、 dispatch_group_leave比dispatch_group_enter执行的次数多
- (void)GCDCrashCase1 {
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_leave(group);//crash崩溃:Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
}
//2、在子线程更新UI,未crash但终端打印出Main Thread Checker: UI API called on a background thread: -[UIViewController view]
- (void)GCDCrashCase2 {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        self.view.backgroundColor = [UIColor redColor];//[UIView setBackgroundColor:] must be used from main thread only
    });
}
//3、多个线程同时释放一个对象
- (void)GCDCrashCase3 {
    self.unsafeArray = [[NSMutableArray alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        for (int i = 0; i < 1000; i ++) {
            self.unsafeArray = [[NSMutableArray alloc] init];
        }
    });
    for (int i = 0; i < 1000; i ++) {
        self.unsafeArray = [[NSMutableArray alloc] init];
    }
}
/**
 A线程先完成初始化并赋值(这个实例我们叫它a), 然后继续往后走到其他逻辑.而这时候, B线程开始做初始化并赋值(这个实例我们叫它b), handler将指向B线程初始化出来的对象. 而A初始化出来的实例a因为引用计数减少1(减少到0)而被释放. 但在A线程中, 代码还会尝试访问a所在的地址, 这个地址里的内容因为被释放而变得无法预测, 从而导致野指针.
 */
- (void)setUnsafeArray:(NSMutableArray *)unsafeArray {
    _unsafeArray = unsafeArray;//crash崩溃:Thread 1: EXC_BAD_ACCESS
    //    加锁解决
    //    @synchronized (self) {
    //        _unsafeArray = unsafeArray;
    //    }
}
// 4、多线程中的数组扩容、深复制
/**扩容:数组的地址已经改变,报错was mutated while being enumerated
深复制 数组内的对象被 其他线程释放,访问僵尸对象
[NSArray copy] 浅复制、[NSArray mutableCopy]深复制
[NSMutableArray copy]深复制 、[NSMutableArray mutableCopy]深复制
 */
-(void)GCDCrashCase4 {
    
    {
        NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
        NSArray *copyArray = [array copy];
        NSMutableArray *mCopyArray = [array mutableCopy];
        NSLog(@"array = %p,copyArray = %p,mCopyArray = %p", array, copyArray, mCopyArray);
        //array = 0x600000140060,copyArray = 0x600000140060,mCopyArray = 0x600000f78720
    }
    {
        NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
        NSArray *copyArray = [array copy];
        NSMutableArray *mCopyArray = [array mutableCopy];
        NSLog(@"array = %p,copyArray = %p,mCopyArray = %p", array, copyArray, mCopyArray);
        //array = 0x600000f4cb70,copyArray = 0x600000f4e850,mCopyArray = 0x600000f4cf00
    }
    
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", 0);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", 0);
    
    NSMutableArray* mutableArray = [NSMutableArray array];
    
    dispatch_async(queue1, ^{
        while (true) {
            if (mutableArray.count < 100) {
                [mutableArray addObject:@(mutableArray.count)];
            }
            else {
                [mutableArray removeAllObjects];
            }
        }
    });
    
    dispatch_async(queue2, ^{
        // case 4:深复制 数组内的对象被 其他线程释放,访问僵尸对象
        // 在 [mutableArray copy] 的过程,copy 方法内部调用initWithArray:range:copyItems: 时
        // 数组被另一个线程清空,range 不一致导致抛出 exception
        while (true) {
            NSArray* immutableArray1 = [mutableArray copy];
            for (NSNumber* number in immutableArray1) {
                NSLog(@"%@", number);
            }
        }
    });
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容