iOS 底层 - 名词解析

目录

  • 前言
  • 名词解析
  • OC消息传递和转发机制
  • Runtime
  • runtime动态创建类
  • Runloop
  • Method Swizzling黑魔法
  • 自己动手写一个框架
  • Category实现原理 和 Protocol
  • 反射机制
  • Json到Model的转化
  • 快速归档
  • 访问私有变量
  • 。。。

名词解析

1. 源码 <objc/objc.h>/<objc-private.h> 中的名词

① 实例对象 ===> id 和结构体 objc_object
/// A pointer to an instance of a class.
typedef struct objc_object *id;

源码指出,id 是指向 objc_object 的指针。而注释告诉我们,id 就是一个指向某个类的实例对象的指针。那 objc_object 就是某个类的实例对象,如下:

// <objc/objc.h> 源码中
// Objective-C 2.0 已废弃  
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};


// <objc-private.h> 源码中
// Objective-C 2.0 支持的定义
struct objc_object {
private:
    isa_t isa;
public:
    Class ISA();
    Class getIsa();
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);
    ...
}

objc_object 表示一个类的实例objc_object 里面有一个 isa_t 类型的 isa 指针,这个指针指向了这个对象的类对象

② 类 ==== Class 和结构体 objc_class
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

从源码中可以看出,Class 是一个指向 objc_class 结构体的指针.注释告诉我们,Class 表示一个 Objective-C 类。那么 objc_class 又是什么?

// <objc/runtime.h> 源码中
// Objective-C 2.0 已经废弃
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
....
#endif
} OBJC2_UNAVAILABLE;


// <objc-runtime-new.h> 源码中
// Objective-C 2.0 支持
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             
    class_data_bits_t bits;    
    class_rw_t *data() { 
        return bits.data();
        // 这个结构体里有所有的变量列表,方法列表等
    }
    ...
}

我们可以从源码中看出来, objc_class 结构体继承自 objc_object. 说明 objc_class 也是个对象。那我们就很简单得推断出 objc_class 也有个 isa 指针。那么这个 isa 指针又指向的是什么呢?

其实这里仅仅从源码来看,我们并不知道这里的isa指向了哪里,反正不是父类,因为父类下面还有个 superclass 指向了它的父类
那 isa 指向了哪里呢? 只能通过网上的解答来解释了:
isa指向的是它的 metaclass(元类) , 元类也是 objc_class 的另一种对象


关于类和对象

我们常说的对象,一般指的是 实例对象。 但其实对象不仅仅是实例对象,还有其他的, 比如类对象. 我们上面有说到,objc_classobjc_object 几乎一致,那我们从 runtime 的源码下继续深入研究中看到

// <objc-runtime-new.h> 源码
struct objc_class : objc_object {
    ...
}

从这里看出,objc_class 是继承了 objc_object 的结构体。这里的源码我们其实可以先得出一个结论:
类也是一个对象

关于 metaclass 元类

在国外大牛的文章:What is a meta-class in Objective-C? 中对 metaclass 的解释总结过来有三个要点:

  1. metaclass 元类也是一个对象,是一个 obj_class 的一种,即是类对象的一种。这意味着, 你也可以对这些元类作为对象来发送消息。也因此每个Class都有其 metaclass 原类
  2. 所有的 metaclass 元类都使用同一个基础类的 元类。这意味着 几乎所有的metaclass 元类都是 ‘NSObject的元类’ 的派生类。
  3. 几乎所有的 metaclass元类 上的 isa 指针 指向的都是 ‘NSObject的元类’.元类也有个 isa 指针指向 root meteClass 根元,根元的 isa 指针指向自己

最后来一张经典图片收尾



③ 方法选择器 ==== SEL 和结构体 objc_selector
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

SELobjc_selector 结构体的指针,表示了一个方法的选择器。结构体的内容在 runtime.hobj.h 的源码中无法找到,所以我们无法得知 objc_selector 到底是什么。不过很多 Blog 和书籍中指出: " SEL 其实也是其中一种数据类型,用来定义类方法,是类成员方法的指针,本质上其实只是一串字符串。”(其实可以看做是类方法的identifier),接下来验证这种说法是否正确

// 首先,我们先定义一个 SEL, 定义 SEL 可以有多种方式
SEL aSelector = @selector(SEL selector);

SEL bSelector = SEL sel_registerName(const char *name);
// (NSSelectorFromString 方法就是通过 sel_registerName 方法来注册的 SEL)
SEL cSelector = NSSelectorFromString(NSString *cSelectorName);
// 其实从定义我们就可以看出来, SEL 和 字符串 可能存在某种转化。

// 除此之外, 我们输出下这个 SEL 发现:
// 尽管报了 warning ,但是你还是可以看到输出的就是个字符串,而没有报错
NSLog(@"%s",aSelector);


// 另外,我们还可以通过另一个方法来看看 SEL 其实也是个类似Key的东西
// 举例, dog 和 cat 是两个不同的实例对象,都有一个名为 description 的方法
SEL selector1 = @selector(description);
[dog performSelector:selector1];    // 调用 dog 的 description 方法
[cat performSelector:selector1];    // 调用 cat 的 description 方法
// 可以看出虽然 SEL 虽然相同, 但是调用的方法是不同的。

那么其实 我们是否可以大胆得得出一个结论:SEL 其实就只是 一个方法名 或者说 标识. 这一点其实从
后面的 objc_method 结构体中 SEL method_name 命名可以看出。

④ 方法实现 ==== IMP
#if !OBJC_OLD_DISPATCH_PROTOTYPES
// 指向C的函数
typedef void (*IMP)(void /* id, SEL, ... */ );                  // 无返回值
#else
// OC的指向方法的指针
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);    // 有返回值
#endif

IMP(Implementation)是一个指向 方法实现的地址 的指针,参数都包含 idSEL 类型。通过 id (实例对象)里的 SEL (方法名) 获取到唯一的实现方法。

  • 获取 IMP 的方式
IMP imp = class_getMethodImplementation(Class cls, SEL name)
IMP imp = method_getImplementation(Method _Nonnull m) 
  • 调用方式
// 无参无返回值的 IMP 调用。    (其实走的是C语言的指向函数)
imp();

// 对于有参数有返回值的 IMP 的调用比较麻烦。
// 这里给出一种常规操作: 强制类型转换 (例子中返回值和参数的类型都是 int)
int (*imp)(id, SEL, int) ;
imp = (int (*)(id, SEL, int))method_getImplementation(method); // 强制类型转换
Object *obj = [[Object alloc] init];
NSLog(@"%d", imp(obj, @selector(method_name:), 3));

当然,IMP还有更底层更广泛的应用

其实,直接调用方法的 IMP指针效率比调用方法本身更高。但是也不要盲目得去用IMP来提高速率,毕竟可读性肯定没有那么高了。

⑤ 布尔值 ==== bool 和 BOOL 和 Boolean、true 和 false 、 YES 和 NO
  • 关于 booltrue/false
// bool 的定义 在 <stdbool.h> 中 原代码较长,总结下来是这样的
// 如果是 C 语法
#define bool _Bool // 因为 C 语言 不具有 bool 值的,因此 C99 用 _Bool 来兼容 C++
#define true 1
#define false 0
// 如果是 C++ 语法
#define bool  bool
#define false false
#define true  true
  1. 这里注意, bool 不管在 C 还是在 C++, 都是只有 1个字节
  2. bool 值为 true/false, 对应的值只有 1/0
  3. 所有的 非0 数值, 强制转换成 bool 类型 都会转换成 1/true 。 如下:
NSLog(@"%d %d %d %d %d", (bool)1, (bool)-1, (bool)0, (bool)256, (bool)8);
//输出 1, 1, 0, 1, 1
  • BOOLYES/NO
// BOOL 的定义 在 <objc.h> 中
// OBJC_BOOL_IS_BOOL 的定义省略. 总结就如下
#if OBJC_BOOL_IS_BOOL // 当 64位iPhone 或 ARMv7K 时
    typedef bool BOOL;
#else // 当是 32位iPhone 以及 非ARMv7K 时
    typedef signed char BOOL; 
#endif

// objc_bool 的 类型与 系统位数有关
#if __has_feature(objc_bool) 
#define YES __objc_yes   // __objc_yes = signed char 1 (32位) / true (64位)
#define NO  __objc_no    // __objc_no = signed char 0 (32位) / false (64位)
#else
#define YES ((BOOL)1)
#define NO  ((BOOL)0)
#endif
  1. BOOL 不管是32位系统还是64位系统,都占了 1个字节
  2. 32 位系统 的类型是 signed char . 字符空间只有8位,超过8位的都只会取低8位的值。如 256 = 0b1 0000 0000 转换成 BOOL 会等于 0
  3. 所有的 非0 数值, 强制转换成 BOOL 类型,都不会转换成 1/true
NSLog(@"%d %d %d %d %d", (BOOL)1, (BOOL)-1, (BOOL)0, (BOOL)256, (BOOL)8);
//输出 1 -1 0 0 8

注意: 这里不会转换成0/1 会导致一个问题。如果在条件判断语句中,使用 condition == YES,或 condition != YES这种写法,等式会不成立。比如:

BOOL condition = 123;                               BOOL condition = 123;
if (condition == YES) {                             if (condition) {
    // 不会走这里                                       // 走这里, 正确
} else {                     ==用该方法改进==>        } else {
    // 走的是这里. 这是错误的
}                                                   }
  • 总结来说
  • 判断语句中不要用 == YES 的方式
  • 尽量避免赋值给 BOOL 。 如果要赋值给 BOOL 也请注意取值范围 (因为超过8位会截取)
  • Boolean
// Boolean 的定义在 <MacTypes.h> 中
typedef unsigned char Boolean;

Boolean 的注释中 有说到 Mac OS historic type, sizeof(Boolean)==1 说明Boolean只有一个字节

区别 nil Nil NULL 和 NSNull
// 源码中的 nill Nil 在 <objc.h>
// Nil 定义
#ifndef Nil
# if __has_feature(cxx_nullptr)
#   define Nil nullptr
# else
#   define Nil __DARWIN_NULL
# endif
#endif

// nil 定义
#ifndef nil
# if __has_feature(cxx_nullptr)
#   define nil nullptr
# else
#   define nil __DARWIN_NULL
# endif
#endif

// NULL 在 C 语言中的定义
#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif

// NSNull
@interface NSNull : NSObject <NSCopying, NSSecureCoding>
+ (NSNull *)null;
@end
  • 适用场景:

    • Nil 代表 类对象 指向一个 空指针 如: Class a = Nil
    • nil 代表 OC对象 指向一个 空指针 如: NSString *a = nil
    • NULL 一般用来使 基础类型 指向 空指针 如: int *pointerInt = NULL
    • NSNull 表示一个 空的占位对象
       //这里的 [NSNull null] 不能用 nil 替换。[NSNull null] 表示空对象
       NSArray *arr = [NSArray arrayWithObjects:@"1",[NSNull null],@"2", nil];
       NSLog(@"%@",arr);
      // 输出  (1,"<null>",2)
      
  • 注意事项:

    • 对象很可能会被销毁后不置nil。(这个其实也是很常见。比如你命名一个对象时用了 assign 而不是 weak 时。对象被销毁后不会置nil, 所以指针指向的地址以及不存在,成为野指针)
      这里有一点不置nil的对象 发送消息时会崩溃。 但是 置nil的对象 发送消息消息不会崩溃
      所以,为了安全起见, 在调用方法之前判断一下是否为空是很有必要的

      NSString *str = nil;
      if (str != nil) {
          // do Something
      }
      
    • 尽量不要用 NULL 去初始化 OC对象

    • iOS开发中 在向服务请求数据或者请求网页,对获得的数据解析时,如果遇到 (null) 或 <null> 时。因为这个数据不是 nil 而是 NSNull对象 ,所以在这个解析后的对象发送消息 会崩溃。崩溃消息:unrecognized selector sent to instance
      解决的办法会在后面说到

2. 源码<objc/runtime.h>/<objc-runtime-new.h>中的名词

① 方法 ==== Method 和结构体 method_t / objc_method
// <runtime.h> 源码中 (Objective-C 2.0 已废弃)
typedef struct objc_method *Method;
struct objc_method {
    SEL _Nonnull method_name                            OBJC2_UNAVAILABLE;
    char * _Nullable method_types                       OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                             OBJC2_UNAVAILABLE;
}OBJC2_UNAVAILABLE;

已经在 Objective-C 2.0 中废弃的,我们就不看了。重点关注下面的源码

// <objc-runtime-new.h> 源码中
typedef struct method_t *Method;

struct method_t {
    SEL name;               // 方法名
    const char *types;      // 返回值和参数
    MethodListIMP imp;      // 指向“方法的实现”的指针  注: using MethodListIMP = IMP;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    }; // 根据name的地址对方法进行排序的函数
};

这里我们可以知道 一个 Method 需要具备以下几个条件: 方法名、参数、返回值、实现方法
不同的类可以有相同的 Method 方法。查找 Method 的时候会在类的 Method 链表中进行查找。
定位到一个 Method 一般有下面两种:

// 获取实例方法
Method *method = class_getInstanceMethod([ClassName class], @selector(method_name))
// 获取类方法
Method *method = class_getClassMethod([ClassName class], @selector(method_name))

常用的方法

// 给类添加一个方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
// 替换 cls 类中的 name 方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
// 交换 m1, m2 方法
method_exchangeImplementations(Method  m1, Method  m2)
// 设置 指向方法的实现 指针。 返回 原来的指向方法实现的指针
IMP method_setImplementation(Method method, IMP imp)
② 成员变量 ==== Ivar 和结构体 ivar_t / objc_ivar
// <runtime.h> 源码中 (Objective-C 2.0 已废弃)
typedef struct objc_ivar *Ivar;
struct objc_ivar {
    char *ivar_name;  //实例变量名
    char *ivar_type;  //实例变量类型类型
    int ivar_offset;  //基地址偏移字节
    int space;
}

以上源码已经在Objective-C 2.0中被废弃

// <objc-private.h>
typedef struct ivar_t *Ivar;

// <objc-runtime-new.h> 源码中
struct ivar_t {
    int32_t *offset;            // 基地址 偏移字节
    const char *name;           // 实例变量名
    const char *type;           // 实例变量类型
    uint32_t alignment_raw;     //对齐
    uint32_t size;              //大小
    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
};

Ivar 是指向 对象的实例变量。Ivar 是指向 ivar_t 结构体 的指针,从结构体看出,Ivar 包括类型和名字等。

  • 常用的方法:
// 1. 获取 Ivar 实例变量列表
unsigned int count;
Ivar *ivarList = class_copyIvarList([Class class], &count);
// 这里如果想要知道每一个实例变量。用个 for循环 就可以了
for (int i = 0; i < count; i ++) {
    Ivar ivar = ivarList[i];
}
free(ivarList);  // 结束后要释放

// 2. 获取 Ivar 实例变量
Ivar ivar = class_getInstanceVariable([Class class], ivar_name);

// 3. 获取 结构体对应的值
ivar_getName(Ivar ivar)              // 获取 实例变量名
ivar_getTypeEncoding(Ivar ivar);     // 获取 实例类型
ivar_getOffset(Ivar ivar);           // 获取 实例偏移值

// 4.获取/设置 变量
object_setIvar(id obj, Ivar ivar, id value)     // 设置某个对象的 实例变量的值
object_getIvar(id obj, Ivar ivar)               // 获取某个对象的 实力变量的值

// 5.变量的添加 (无法在运行时添加)
class_addIvar(Class cls, const char *name, size_t size,alignment, const char *types)

这里有个注意点,很多人发现变量添加 会失败。因为 无法通过运行时添加ivar 或者说动态添加类的时,已经添加完成的类中,无法新增 Ivar
所以注释中有说 class_addIvar 需要 objc_allocateClassPair 和 objc_registerClassPair 之间。

③ 属性 objc_property_t 和结构体 property_t / objc_property
// <runtime.h> 源码中 (Objective-C 2.0 已废弃)
typedef struct objc_property *objc_property_t;

// <objc-private.h> 源码中的定义
typedef struct property_t *objc_property_t;
// <objc-runtime-new.h> 源码: 
struct property_t {
    const char *name;               // 属性名
    const char *attributes;         // 属性的特性
};
  • 常用方法:
// 1. 获取 属性 的列表
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([Class class], &count);
for (int i = 0; i < count; i++) {
    objc_property_t property = proList[i];
}
free(propertyList);

// 2. 根据属性名 获取 objc_property_t
class_getProperty(Class cls, const char* name)

// 3. 获取结构体的内容
property_getName(objc_property_t property)          // 获取属性名
property_getAttributes(objc_property_t property)    // 获取属性的特性

// 4. 对属性进行修改或新增
class_replaceProperty(Class cls, 
                      const char* name, 
                      const objc_property_attribute_t* attributes, 
                      unsigned int attributeCount)      // 替换属性
class_addProperty(Class cls, 
                  const char * name,
                  const objc_property_attribute_t*  attributes,
                  unsigned int attributeCount)          // 新增属性(同Ivar 一致)

// 5.属性 的特性 相关
// 获取属性的特性列表
property_copyAttributeList(objc_property_t property, unsigned int *outCount)
// 拷贝属性的特性的值
property_copyAttributeValue(objc_property_t property, const char *attributeName)

注意,这里的 新增属性方法 在动态添加类结束后 也会失败

  • 关于 attributes 特性 和 objc_property_attribute_t 结构体 :
    我们可以从结构体中看出 attributes 只是个字符串,但是其实从 replace 和 add 方法中可以看出 其实是个 objc_property_attribute_t 结构体的数组
    我们定义属性的时候用到了 类似 nonatomic、strong、assign 来形容属性,这些形容的内容都会 体现在 attributes 这个字符串中。
    /// 定义一个 attribute
    typedef struct {
        const char * _Nonnull name;             // attribute 的名字
        const char * _Nonnull value;            // attribute 的值 (经常是空的)
    } objc_property_attribute_t;
    
    // 新增 property 的时候, attributes 可以这么定义, 如:
    objc_property_attribute_t attr0 = { "T", "@\"NSString\"" };
    objc_property_attribute_t attr1 = { "&", "" };
    objc_property_attribute_t attr2  = { "V", "_newProperty" };
    objc_property_attribute_t attrs[] = { attr0, attr1, attr2};
    

    这里列出这里的 Name 所对应的意思:

name attribute
T 类型,如@"NSString"
& retain
W weak
N nonatomic
R readonly
C copy
G(name) getter=(name)
S(name) setter=(name)
D @dynamic
V 实例变量Ivar
P 用于垃圾回收机制
关于 Ivar(实例) 和 Property(属性) 的区别

这里有人会奇怪,Ivar (实例) 和 Property (属性) 有什么区别?
当你定义个一个新的属性时, @property NSString* newProperty;
你会发现多了一个实例变量 _newProperty 和两个方法 setter 和 getter 方法
所以我们 其实可以看做 Property = Ivar + setter + getter

④ 分类 ==== Category 和结构体 category_t 和 objc_category
// <runtime.h> 源码中 (Objective-C 2.0 已废弃)
typedef struct objc_category *Category;
struct objc_category {
    char * _Nonnull category_name                         OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                            OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods     OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols       OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

上面的源码已经在 Objective-C 2.0 中废弃.

// <objc-private.h> 和 <objc-runtime-new.h>源码中
typedef struct category_t *Category;
struct category_t {
    const char *name;                               // 分类名
    classref_t cls;                                 // 类                         
    struct method_list_t *instanceMethods;          // 实例方法 列表
    struct method_list_t *classMethods;             //  类方法 列表
    struct protocol_list_t *protocols;              //   协议  列表
    struct property_list_t *instanceProperties;     //  实例属性 列表
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;       //  类属性 列表

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

关于 category 的 实现原理,我们会在后面讲到。

小结

1. 关于博客中的源码内容
在查看 <objc/runtime.h> 的过程中,我发现头文件源码中很多都有用到宏定义 OBJC2_UNAVAILABLE 以及用 #if !__OBJC2__ endif 来区别是否是 Objective-C 2.0+ , 那么也就是说我们看到的这些方法其实很多都已经不适用于当前的 Objective-C 版本,最新的很多都看不到了。不过 runtime 的源代码苹果已经开源。我们可以从官网下载到源码 ->源码下载地址。在源码中我们可以看到这些对象里面都有些什么。

注意: 正如上面所说很多 <runtime.h><objc.h> 的大部分都已经在 Objective-C 2.0 的时候过期了。所以我们 Objective-C 2.0 相对应的 <objc-runtime-new.h><objc-private.h>文件等。 objc4源码 中也可以看到

2. 关于使用哪种方式来定义属性的类型
我注意到在源码 <objc/runtime.h> 的注释中有写到 /* Use Class' instead of 'struct objc_class *' */, 可以推断,苹果其实都不建议直接用结构体指针来命名该类型类型的值。

参考

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

推荐阅读更多精彩内容