OC对象的本质<二> 实例对象,类对象,元类对象

OC对象的本质<一>

OC对象的分类

OC对象可以分为三类,分别是实例对象,类对象,元类对象。

实例对象(instance对象)

instance对象是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象。

  • instance对象在内存中存储的信息
    isa指针(因为几乎所有的对象都继承自NSObject,而NSObject对象的结构体重就是一个isa指针)
    其他成员变量(注意这里存储的是成员变量的值)
    我们看一下Demo:
@interface Person:NSObject
{
    @public
    int _age;
}
@property (nonatomic, assign)int height;
@end

@implementation Person
@end

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        Person *p1 = [[Person alloc] init];
        p1->_age = 3;
        
        Person *p2 = [[Person alloc] init];
        p2->_age = 4;
        
        return 0;
    }
}

那么这个时候首先为p1实例对象分配存储空间,先存储isa这个指针,然后存储成员变量_age=3;对于p2实例对象也是一样的。由于p1指针指向Person实例对象,也就是指向Person实例对象在内存中的首地址,而Person实例对象中存储的第一个成员变量是isa指针,所以p1指针指向的地址就是存储isa指针的地址。


17703D99-4D1C-45D2-85C2-5E053C2FA656.png

类对象(Class对象)

类对象的获取方式:

NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];

一个类的类对象在内存中是唯一的,这就说明我们通过这五种方式所创建的类方法是同一个对象。我们通过打印这5个类对象的内存地址来验证一下:

NSLog(@"%p \n %p \n %p \n %p \n %p \n", objectClass1, objectClass2, objectClass3, objectClass4, objectClass5);

打印结果:

 0x10b0a6ea8 
 0x10b0a6ea8 
 0x10b0a6ea8 
 0x10b0a6ea8 
 0x10b0a6ea8

这也就验证了这五个对象是同一个对象,也就是一个类只有一个类对象。
既然每个类的类对象只有唯一一个,那么在每个类的类对象中会存储什么东西呢?肯定是存放那么只需要存储一份的东西,不会是像成员变量的值一样,每个实例对象都可以有不同的成员变量值。

  • Class对象在内存中存储的信息主要包括:
    isa指针
    superclass指针
    类的属性信息(@property),类的对象方法信息(instance method)
    类的协议信息(@protocol),类的成员变量信息(ivars,类型,名称等)


    类对象包含的信息.png

元类对象

  • 元类对象的获取方法
//我们在object_getClass()方法中传入类对象就得到了元类对象,每个类的元类对象只有一个,所以objectMetaClass1和objectMetaClass2是同一个对象
 Class objectMetaClass1 = object_getClass([NSObject class]);
 Class objectMetaClass2 = object_getClass(objectClass1);

那么元类对象中存放的是什么信息呢?大家想一下实例对象和元类对象还有什么信息漏了就能明白元类信息中包含什么信息了。

  • meta-Class对象中包含的信息
    isa指针
    superclass指针
    类的类方法信息


    元类对象包含的信息.png
  • class_isMetaClass()
    class_isMetaClass()判断传进去的对象是否是元类对象。
    BOOL result = class_isMetaClass(objectMetaClass1);

isa指针

我们先看一个Person类:

@interface Person:NSObject<NSCopying>
{
    @public
    int _age;
}
@property (nonatomic, assign)int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

@implementation Person
- (void)personInstanceMethod{
    
    
}
+ (void)personClassMethod{
    
    
}

- (id)copyWithZone:(NSZone *)zone{
    
    return nil;
}

这个Person类有成员变量,属性,有遵守的协议,实例方法,类方法。首先我们通过实例对象调用实例方法:

Person *person = [[Person alloc] init];
[person personClassMethod];

[Person personClassMethod]这句代码在底层的实现一定是objc_msgSend(person,@selector(personInstanceMethod))。这里就有一个问题了,我们是给实例对象发消息,调用实例方法,可以实例对象中没有实例方法的信息呀。同样的,当我们调用类对象的时候[Person personInstanceMethod]这句话在底层的实现时一定是转化为objc_msgSend([Person class],@selector(personClassMethod))这样,也就是给一个类对象发送消息,调用类方法。但是类方法是在元类对象里面,不在类对象中呀,这是怎么调用的呢?这时isa指针就派上用场了。
Person实例对象的isa指针指向Person类对象,Person类对象的isa指针指向Person元类对象。

  • 当我们调用对象方法时,首先通过实例对象中的isa指针找到类对象,然后获得类对象中的实例方法信息。
  • 当我们调用类方法时,首先通过类对象的isa指针找到元类对象,再找到元类对象中的类方法信息,实现调用。

superclass指针

类对象的superclass指针

我们创建一个子类Student类继承自Person类。

@interface Student:Person <NSCoding>
{
    @public
    int _weight;
}
@property (nonatomic, assign)int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;

@end

@implementation Student
- (void)studentInstanceMethod{
    
}

+ (void)studentClassMethod{
    
}
- (id)initWithCoder:(NSCoder *)aDecoder{
    
    return nil;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
    
}
@end
![![85F2CFF1-F8EF-438A-B32D-CF65BCF10A5A.png](https://upload-images.jianshu.io/upload_images/5796542-fdab2d71d7b42f54.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ](https://upload-images.jianshu.io/upload_images/5796542-7e9a426e8fb01d06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

我们看一下student实例对象调用实例方法:

Student *student = [[Student alloc] init];
[student studentInstanceMethod];

这个过程我们已经很清楚了,就是通过student实例对象的isa指针来找到Student的类对象,然后在类对象中找到实例方法,完成调用。那么如果student实例对象调用的是父类Person类的实例方法呢?[student personInstanceMethod];这个调用过程又是怎样的呢?- (void)personInstanceMethod这个类方法肯定是存放在Person类的类方法里面的。这个时候就是superclass指针发挥作用的时候了。
student实例对象首先通过其isa指针找到自己的类对象,然后Student类对象查找自己有没有存储- (void)personInstanceMethod这个实例方法,发现自己并没有这个实例方法的信息,于是就通过自己的superclass指针来找到父类的类对象也就是Person类对象,Person类对象查看自己有没有存储- (void)personInstanceMethod这个实例方法的信息,结果找到了这个实例方法的信息,至此student实例对象(^-^)也就获取了- (void)personInstanceMethod实例方法的信息。
类对象的superclass指针指向父类的类对象

元类对象的superclass指针

85F2CFF1-F8EF-438A-B32D-CF65BCF10A5A.png

首先我们来看Student类对象调用自己的类方法:

[Student studentClassMethod];

这个调用过程应该已经很清楚了,Student类的类方法信息是存储在元类对象中的。Student类对象首先通过自己的isa指针找到Student元类对象,Student元类对象查看自己有没有studentClassMethod这个类方法的信息,查看后发现有,就传给类对象。那么如果要调用父类Person类的类方法呢?

[Student personClassMethod];

首先Student类对象通过自己的isa指针找到Student元类对象,Student元类对象查看自己有没有personClassMethod这个类方法的信息,查找后没有就利用自己的superclass指针找到父类的元类对象,也就是Person类的元类对象,Person类的元类对象查看后发现自己有personClassMethod这个类方法的信息,至此Student类对象就找到了personClassMethod这个类方法的信息。
元类对象的superclass指针指向父类的元类对象。

isa,superclass总结

放上一张经典的总结图

630FEC40-DF9E-4755-80CA-D65810CB3DF1.png

总结起来就是:
实例对象的isa指针指向该类的类对象。
类对象的isa指针该类的元类对象。
元类对象的isa指针指向基类的元类对象。
类对象的superclass指针指向父类的类对象
元类对象的superclass指向父类的元类对象(基类除外)
基类的元类对象的superclass指向基类的类对象。

isa指针的一个小细节

前面已经说了实例对象的isa指针指向类对象,类对象的isa指向元类对象。所以实例对象的isa指针的值就是类对象的地址值,类对象的isa指针的值就是元类对象的地址值。我们打印看一下结果:

Person *person = [[Person alloc] init];
Class personClass = [Person class];
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p, %p, %p", person, personClass, personMetaClass);

打印结果:

Student[1372:63404] 0x600000008390, 0x10e7a4130, 0x10e7a4108

然后我们再打个断点,查看一下各个对象的isa指针值。

1697CA48-0D78-4273-9B39-86D3581DF4DA.png

我们在调试框中输入p person->isa发现打印的是$0 = Person,这并不是我们想要的,我们输入p (long)person->is,这下打印出想要的结果了$1 = 4537860400,但是这是10进制表示,我们需要转化为16进制,再输入p/x (long)person->isa,打印得到16进制结果:$2 = 0x000000010e7a4130。这也就验证了实例对象的isa指针指向类对象。
然后我们试着打印类对象的isa指针的值:
p/x personClass->isa
打印结果是:
error: member reference base type 'Class' is not a structure or union所以这条路行不通了。
我们已经知道了这个personClass这个类对象中第一个成员变量一定是isa指针,所以我们可以自己创建一个结构体:

struct pd_objc_class{
    
    Class isa
};

然后我们把personClass这个类对象强制转化成pd_objc_class结构体类型:

 struct pd_objc_class *personClass2 = (__bridge struct pd_objc_class *)(personClass);

然后再通过p/x personClass2->isa打印isa指针的值:
$0 = 0x000000010583f108,同时我们从一开始的打印结果可以找到personMetaClass对象的地址为0x000000010583f108。
这样也就证明了类对象的isa指针是指向元类对象的。

验证实例对象,类对象,元类对象的结构

类对象和元类对象的类型都是Class类型,所以本质上来讲它们的结构是一致的。那么我们只需要搞清楚这个Class类型的结构就可以搞清楚类对象和元类对象的结构了。
按住command点击Class查看结构:

typedef struct objc_class *Class;

可以看到这是一个obkc_class类型的结构体指针。然后我们继续点击进objc_class查看:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

但是我们看到这个是条件编译,#if !OBJC2也就是如果不是OBJC2就编译,但是现在就是OBJC2,所以这段条件代码不会编译。因此这个代码就不足以作为参考。那么我们只好从源码中查看objc_class的结构。
我们在源码中搜索objc_class,从objc-runtime-new.h中找到了objc_class的结构:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
}

可以看到,objc_class这个结构体是继承自objc_object,我们点进objc_object结构体中看看,可以看到

struct objc_object {
private:
    isa_t isa;
}

里面只有一个成员变量isa指针。所以objc_class这个结构体的结构就等价于:

   struct objc_class : objc_object {
    void *isa;;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
}

第一个成员变量是isa指针,第二个是superclass指针。第三个cache是和方法的缓存有关的。第四个bits先不管。第五个是一个方法,返回值是class_rw_t类型的,我们点进class_rw_t看看它的结构:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;//方法列表
    property_array_t properties;//属性列表
    protocol_array_t protocols;//协议列表
}

然后我们再点进class_ro_t看看它的结构:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;//类名
    method_list_t * baseMethodList;//方法列表
    protocol_list_t * baseProtocols;//协议列表
    const ivar_list_t * ivars;//成员变量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;//属性列表

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

从这个角度确实可以证明objc_class的结构中有isa指针,superclass指针,方法列表,属性列表,成员变量列表,协议列表等。


2874E11F-27F3-4E2B-8276-443CC9CD3A24.png

通过转化为C++的源码来证实实例对象,类对象,元类对象的结构

类对象

@interface Person:NSObject<NSCopying>
{
    @public
    int _age;
}
@property (nonatomic, assign)int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

@implementation Person
- (void)personInstanceMethod{  
}
+ (void)personClassMethod{   
}
- (id)copyWithZone:(NSZone *)zone{
    return nil;
}

@end

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        Person *person = [[Person alloc] init];
        person->_age = 10;
        person.no = 3;
        Class personClass = [Person class];
        Class personMetaClass = object_getClass(personClass);
 
        return 0;
    }
}

我们把person类的代码转为C++的源码:
我们找到class_t类型的OBJC_CLASS$_Person这个结构体,这个结构体就死类对象的结构。

struct _class_t OBJC_CLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_Person,
    0, // &OBJC_CLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_CLASS_RO_$_Person,

我们找到_class_t这个结构体,查看结构

struct _class_t {
    struct _class_t *isa;
    struct _class_t *superclass;
    void *cache;
    void *vtable;
    struct _class_ro_t *ro;
};

我们看到其中第一个成员是isa指针,第二个成员是superclass指针,cache和vtable我们先不管。接着我们看到_class_ro_t这个结构体:

struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved;
    const unsigned char *ivarLayout;
    const char *name;
    const struct _method_list_t *baseMethods;
    const struct _objc_protocol_list *baseProtocols;
    const struct _ivar_list_t *ivars;
    const unsigned char *weakIvarLayout;
    const struct _prop_list_t *properties;
};

这个结构体我们前面见多过,应该已经比较熟悉了。然后我们找到类对象中这个结构的实现:

static struct _class_ro_t _OBJC_CLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    0, 
    __OFFSETOFIVAR__(struct Person, _age), 
    sizeof(struct Person_IMPL), 
    (unsigned int)0, 
    0, 
    "Person",
    (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_Person,
    (const struct _objc_protocol_list *)&_OBJC_CLASS_PROTOCOLS_$_Person,
    (const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_Person,
    0, 
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person,
};
  • 我们可以看到instanceSize对应的是sizeof(struct Person_IMPL)。
  • name也就是类名,对应的是"Person"。
  • baseMethods对应的是OBJC$_INSTANCE_METHODS_Person这个结构体,我们找到这个结构体的实现:
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[4];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    4,
    {{(struct objc_selector *)"personInstanceMethod", "v16@0:8", (void *)_I_Person_personInstanceMethod},
    {(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_Person_copyWithZone_},
    {(struct objc_selector *)"no", "i16@0:8", (void *)_I_Person_no},
    {(struct objc_selector *)"setNo:", "v20@0:8i16", (void *)_I_Person_setNo_}}
};

我们通过INSTANCE_METHODS这个名字知道这个里面存放的是实例方法,通过其初始化可以看到,有四个实例方法,方法名分别是"personInstanceMethod";"copyWithZone:";"no";"setNo:"。这是符合实际的。

  • baseProtocols根据名字应该是存放的协议信息,它是用OBJC_CLASS_PROTOCOLS$_Person这个结构体初始化的。我们找到这个结构体的实现:
static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CLASS_PROTOCOLS_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_NSCopying
};

可以看到,协议数量是1,协议是_OBJC_PROTOCOL_NSCopying,通过名称我们得知是NSCopying,这里不再展开。

  • ivars是变量的意思,其对应的是OBJC$_INSTANCE_VARIABLES_Person这个结构体,我们查看一下这个结构体的结构:
static struct /*_ivar_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_ivar_t),
    2,
    {{(unsigned long int *)&OBJC_IVAR_$_Person$_age, "_age", "i", 2, 4},
     {(unsigned long int *)&OBJC_IVAR_$_Person$_no, "_no", "i", 2, 4}}
};

可以看到它有两个成员变量,其中一个是_age,类型是int,大小是4字节,还有一个成员变量是_no,类型是int,大小是4字节。

  • properties是属性,可以猜测里面存储的是属性信息。我们看到其对应的结构体是_PROP_LIST_Person,查看一下其实现:
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"no","Ti,N,V_no"}}
};

我们可以看到有一个属性,属性名是no。

元类对象

我们找到class_t类型的结构体OBJC_METACLASS$_Person,这个结构体就是元类对象的实现:

struct _class_t OBJC_METACLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_NSObject,
    0, // &OBJC_METACLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_METACLASS_RO_$_Person,

我们找到OBJC_METACLASS_RO$_Person这个结构体

static struct _class_ro_t _OBJC_METACLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1, 
    sizeof(struct _class_t), 
    sizeof(struct _class_t), 
    (unsigned int)0, 
    0, 
    "Person",
    (const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_Person,
    0, 
    0, 
    0, 
    0, 
};

我们通过这个名称可以看出这是为初始化元类对象的。
还是贴一下_class_ro_t的结构:

struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved;
    const unsigned char *ivarLayout;
    const char *name;
    const struct _method_list_t *baseMethods;
    const struct _objc_protocol_list *baseProtocols;
    const struct _ivar_list_t *ivars;
    const unsigned char *weakIvarLayout;
    const struct _prop_list_t *properties;
};

对比来看,OBJC_METACLASS_RO_$_Person这个结构体的初始化就要简单很多。

  • name就是类名,是"Person"。
  • baseMethods存放的是方法,其是用OBJC$_CLASS_METHODS_Person这个结构体初始化的,我们查看一下这个结构体的结构:
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"personClassMethod", "v16@0:8", (void *)_C_Person_personClassMethod}}
};

类方法只有一个,方法名是“personClassMethod”。
其他的如baseProtocols,ivars,properties这些都是空的。
这也就证实了元类对象中只有isa指针,superclass指针,还有类方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容