iOS全解1-1:基础/内存管理、Block、GCD与多线程

面试系列:


基本概念

一、OC的三大特性为:封装继承多态

1、封装:
  • 成员变量的封装:setter、getter 方法,self调用方便高效
  • 普通方法的封装:简洁、高效,不用关心内部过程
  • SDK的封装:使用方便、私有隐藏,安全性高(不会泄露数据和代码)
2、继承
  • 抽取了重复代码:子类可以拥有父类中的所有成员变量和方法
  • 建立了类与类之间的联系
  • 每个类中都有一个superclass指针指向父类
  • 所有类的根类都是NSObject,便于isa指针 查询
    • 调用某个对象的方法时,优先去当前类中找,如果找不到,再去父类中找
    • 子类重新实现父类的某个方法,会覆盖父类以前的方法。

继承的缺点:耦合性太高(类与类之间的关系过于紧密),没有多继承,OC里面都是单继承,多继承可以用protocol委托代理来模拟实现;可以通过实现多个接口完成OC的多重继承。

3、多态

例如: -(void) animal:(id); id 就是多态,传入时,才识别 具体类的真实形象。runtime 也是运行时,才识别具体类。

  1. 要想使用多态必须使用继承(继承是多态的前提)

  2. 多态:父类指针指向子类对象 Animal *aa = [Dog new]; 调用方法时会检测对象的真实形象

  3. 好处:如果函数或方法参数中使用的是父类类型,可以传入父类,子类对象。

  4. 局限性:父类类型的变量不能直接调用子类特有的方法,必须强制转换为子类类型变量后,才能使用。


property的关键字分三类:

1.原子性(也就线程安全)

有atomic和nonatomic, acomic就是线程安全,但是一般使用nonacomic,因为acomic的线程安全开销太大,影响性能,即使需要保证线程安全,我们也可以通过自已的代码控制,而不用acomic。

2.引用计数:

assign:

assign用于非指针变量,基本修饰数据类型,统一由系统的栈区进行内存管理。(int、double、float等)

weak:

对对象的弱引用,不增加引用计数,也不持有对象,当对象消失后指针自动变为nil。

copy:分为深拷贝、浅拷贝,可变复制、不可变复制

浅拷贝:对内存地址的复制,让目标对象指针和原对象指向同一片内存空间会增加引用计数

深拷贝:对对象内容的复制,开辟新的内存空间

单层深复制,也就是我们经常说的深复制,我这里说的单层深复制是对于集合类所说的(即NSArray、NSDictionary、NSSet),单层深复制指的是只复制了该集合类的最外层,里边的元素没有复制,(即这两个集合类的地址不一样,但是两个集合里所存储的元素的地址是一样的)

完全复制,指的是完全复制整个集合类,也就是说两个集合地址不一样,里边所存储的元素地址也不一样

引用:深拷贝和浅拷贝,可变复制,不可变复制(这里解释更详细)https://www.jianshu.com/p/e3da07684bf1

  • 非集合类(NSString,NSNumber)
[noMutableObject copy]        //浅复制  (复制不可变对象)
[noMutableObject mutableCopy] //深复制  (复制不可变对象)
[mutableObject copy]        //深复制  (复制可变对象)
[mutableObject mutableCopy] //深复制  (复制可变对象)
  • 集合类(NSArray,NSDictionary, NSSet)
 [noMutableObject copy]        //浅复制    (复制不可变对象)
 [noMutableObject mutableCopy] //单层深复制 (复制不可变对象)
 [mutableObject copy]        //单层深复制 (复制可变对象)
 [mutableObject mutableCopy] //单层深复制 (复制可变对象)
  • 那么如何实现多层复制呢?我们以NSArray举例说明
 // 完全复制
NSArray *copyArray = [[NSArray alloc] initWithArray:array copyItems:YES]; 
需要特别注意的是

以上我们所说的两种情况默认都实现了NSCopyingNSMutableCopying协议。
对于自定义继承自NSObject的类

  • copy需要实现NSCopying协议,然后实现以下方法,否则copy会crash
-(id)copyWithZone:(NSZone *)zone {
     CopyObject  *copy = [[[self class] alloc] init];
     copy.name = self.name;
     copy.mobile = self.mobile;
     copy.company = self.company;
     copy.descInfo = self.descInfo;
     return copy;
 } 
  • mutableCopy时,需要实现NSMutableCopying协议,否则mutableCopy会crash
 -(id)mutableCopyWithZone:(NSZone *)zone {
     MutableCopyObject  *mutableCopy = [[[self class] alloc] init];
     mutableCopy.name = self.name;
     mutableCopy.mobile = self.mobile;
     mutableCopy.company = self.company;
     mutableCopy.descInfo = self.descInfo;
     return mutableCopy;
 }
strong:浅拷贝,也就是指针引用

是每对这个属性引用一次,retainCount 就会+1,只能修饰 NSObject 对象,不能修饰基本数据类型。是 id 和 对象 的默认修饰符。

unsafe_unretained:和week 非常相似

可以同时修饰基本数据类型和 NSObject 对象 ,其实它本身是 week 的前身 , 在 iOS5 之后,基本都用 week 代替了 unsafe_unretained 。 但它们之间还是稍微有点区别的,并不是完全一样,对上层代码来说,能用 unsafe_unretained 的地方,都可以用 week 代替。“同时要注意一点,这个修饰符修饰的变量不属于编译器的内存管理对象”


二、类别Category

重写一个类的方式用「继承」还是「分类」,取决于具体情况:

  • 假如目标类有许多的子类,我们需要拓展这个类又不希望影响到原有的代码,继承后比较好。
  • 如果仅仅是拓展方法,分类更好.(不需要涉及到原先的代码)
优点:
  • 分类中方法的优先级比原来类中的方法高,也就是说,在分类中重写了原来类中的方法,那么分类中的方法会覆盖原来类中的方法。+(void)load方法是一个特例,它会在当前类执行完之后,category中的再执行。)
  • 可以用runtime进行method swizzling(方法的偷梁换柱)来处理异常调用的方法。
缺点:

通过观察头文件我们可以发现,Cocoa框架中的许多类都是通过category来实现功能的,可能不经意间你就覆盖了这些方法中的其一,有时候就会产生一些无法排查的异常原因。


其他问题

浅拷贝和深拷贝的区别?

浅拷贝:只复制指向对象的指针,指针指向同一个地址,而不复制引用对象本身。
深拷贝:复制引用对象本身。内存中存在了两份独立对象本身,当修改A 时,B 不变。(即:B = [A copy]; )



一、内存管理

案例:办公室开关灯,有人开灯,无人关灯;进人加一,出人减一;第一个开灯的人持有此灯,第一个人能废弃此灯,其他人只是弱引用。

内存管理原则:

1.自己生成的对象,自己持有
2.非自己生成的对象,自己也能持有
3.不再需要自己持有的对象时,释放掉
4.非自己持有的对象,无法释放(释放会崩溃)

对象操作与Objective-C方法的对应
对象操作 Objective-C方法
生成并持有对象 alloc、new、copy、mutableCopy
持有对象 retain (引用计数加1)
释放对象 release(引用计数减1)
废弃对象 dealloc(引用计数为0)

相关释放函数:autorelease、NSAutoreleasePool、@autoreleasepool

2、非自己生成的对象,自己也能持有

 id obj = [NSMutableArray array];
 [obj retain];

* 使用autorelease方法,可以取得对象的存在,但是自己不持有对象(obj不立刻释放掉,注册到autoreleasePool中)

- (id)object {
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    return obj;
}


ARC 环境下的使用功能规则

  • 不能使用retain、release、retainCount、autorelease
  • 不能使用NSAllocateObject、NSDeallocateObject
  • 必须遵循内存管理命名规则(驼峰命名法)
  • 不要显式调用dealloc
  • 使用 @autorelease 代替 NSAutoreleasePool
  • 不能使用区域 (NSZone)
  • 对象型变量不能作为C语言结构体(struct/union)的成员
  • 显式转换 “id” 和 “void *”


会让对象引用计数增加的操作:

1.  new、alloc、retain、copy、mutableCopy
2.  添加视图:用 addview 把一个控件添加到视图上,这个控件的引用计数+1;
3.  添加数组:把一个对象添加到数组中,数组内部会把这个对象的引用计数+1;
4.  属性赋值:会让对象的引用计数+1;
5.  属性关联:xib中的控件,跟代码关联后,会让对象的引用计数+1;
6.  push这个操作会让对象的引用计数增加。


ARC、MRC混编

  • ARC环境下 使用MRC代码,使用 -fno-objc-arc转换
  • MRC环境下 使用ARC代码,使用 -fobjc-arc转换

所有权修饰符

  • _ _strong 修饰符
  • _ _weak 修饰符
  • _ _unsafe_unretained 修饰符(MRC环境下使用)
  • _ _autoreleasing 修饰符 (ARC环境下使用)

1、_ _strong修饰符是id类型和对象类型的默认修饰符,一般是隐式的;

id obj = [[NSObject alloc] init];
//同上
id _ _strong obj = [[NSObject alloc] init];

2、 _ _weak 解决 循环引用的 修饰符,防止内存泄漏。在持有某对象的弱引用时,若该对象被废弃,则弱引用自动失效,且被赋值nil,不在持有该对象(持有的对象超出作用域,会被废弃销毁)。

3、_ _unsafe_unretained 和 _ _weak修饰的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会被立即释放。

4、_ _autoreleasing 隐式修饰符:非自己生成的对象,自己也能持有,会注册到自动释放池中。


属性声明的属性 与 所有权修饰符的对应关系

声明的属性 所有权修饰符 说明
assign _ _unsafe_unretained 修饰基本数据类型,如NSInteger和CGFloat,这些数值主要存在于栈上。
copy _ _strong 每次复制会在内存中拷贝一份对象,指针指向不同的地址
retain _ _strong 用来持有对象
strong _ _strong 持有对象,引用计数会增加1。该对象只要引用计数不为0则不会被销毁。当然强行将其设为nil可以销毁它。
unsafe_unretained _ _unsafe_unretained 不安全持有
weak _ _weak 不拥有该对象。其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中销毁。


释放对象时,废弃对象不被持有的动作:

1、objc_release (释放)
2、因为引用计数为0,所以执行dealloc(释放内存)
3、_objc_rootDealloc(根解除)
4、object_dispose(废弃处理)
5、objc_destructInstance(销毁)
6、objc_clear_deallocating(清除)


对象被废弃时,最后调用objc_clear_deallocating的动作(相关表 SideTables):

1、从weak表中获取废弃对象的记录(地址为键值)。address= h(key)
2、将包含在记录中的所有附有_ _weak修饰符变量的地址,赋值nil。
3、从weak表中删除该记录。
4、从引用计数表中删除废弃对象的记录(地址为键值)。


/* MRC环境
 * NSAutoreleasePool
 * autorelease
 */
- (void)test2 {
    
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    [pool drain];
   
    //同上
    @autoreleasepool {
        id obj = [[NSObject alloc] init];
        [obj autorelease];
    }
    
 //同上 (ARC:ReferenceCountingVC2 )
 @autoreleasepool {
        id __autoreleaseing obj2;
        obj2 =  obj
    }
}



下面了解几个概念:

内存泄漏:应当废弃的对象在超出其生存周期后继续存在。

野指针:指针变量未初始化,其值是随机的;指针释放后未置空,指向不存在的内存。

悬垂指针: 指向曾经存在的对象,但是该对象已经不存在了。

标记指针: Tagged Pointer
是一种特殊的“指针”,其特殊在于,其实它存储的并不是地址,而是真实的数据和一些附加的信息。(后面做专门讲解)

isa 指针: NONPOINTER_ISA
对象的isa指针,用来表明对象所属的类类型和一些附加信息。(nonPointer_isa 后面做专门讲解)


标记指针: Tagged Pointer

tagged pointer是一种特殊的“指针”,其特殊在于,其实它存储的并不是地址,而是真实的数据和一些附加的信息。

image.png

可以看到,利用tagged pointer后,“指针”即存储了对象本身,也存储了和对象相关的标记。这时的tagged pointer里面存储的不是地址,而是一个数据集合。同时,其占用的内存空间也由16字节缩减为8字节。
(即:Tagged Pointer = 对象本身 + 对象相关标记)

我们可以在WWDC2013的《Session 404 Advanced in Objective-C》视频中,看到苹果对于Tagged Pointer特点的介绍:
• Tagged Pointer专门用来存储小的对象,例如NSNumber, NSDate, NSString

• Tagged Pointer指针的值不再是地址了,而是真正的值。实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。

• 在内存读取上有着3倍的效率,创建时比以前快10倍

例如:NSString 其输出的class类型为 NSTaggedPointerString。在字符串长度在9个以内时,iOS其实使用了tagged pointer做了优化的。
直到字符串长度大于9,字符串才真正成为了__NSCFString类型。
首先,iOS需要一个标志位来判断当前指针是真正的指针还是tagged pointer。

在runtime源码的objc-internal.h中,有关于标志位的定义如下:

#if __has_feature(objc_fixed_enum)  ||  __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
enum
#endif
{
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, // 标志位是2的 tagger pointer表示这是一个NSString对象。
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6, 
    OBJC_TAG_RESERVED_7        = 7, 

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, 
    OBJC_TAG_Last52BitPayload  = 263, 

    OBJC_TAG_RESERVED_264      = 264
};
#if __has_feature(objc_fixed_enum)  &&  !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif
  • ‘地址’以 0xa开头,转换为二进制是1010, 首位1表示这是一个tagged pointer,而010转换为十进制是2,表示这是一个NSString类型

  • ‘地址’以0xb开头, 转换为二进制是1011, 首位1表示这是一个tagged pointer, 而011转换为十进制是3,表示这是一个NSNumber类型
    (即:标志位是2的tagger pointer表示这是一个NSString对象。)
    由于一个tagged pointer所指向的并不是一个真正的OC对象,它其实是没有isa属性的。


isa 指针(NONPOINTER_ISA)

isa:是一个指向对象所属Class类型的指针。(nonPointer_isa)
对象的isa指针,用来表明对象所属的类类型和一些附加信息。

如果isa指针仅表示类型的话,对内存显然也是一个极大的浪费。于是,就像tagged pointer一样,对于isa指针,苹果同样进行了优化。isa指针表示的内容变得更为丰富,除了表明对象属于哪个类之外,还附加了 「引用计数」extra_rc,是否有被weak引用标志位weakly_referenced,是否有附加对象标志位has_assoc等信息。(rc:reference counter 引用计数)

//------- NSObject(实质) ------- 
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

//------- 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 *加上自定义rr/alloc标志
    //。。。
}

//------- isa指针(指向)------- 
struct objc_object {
private:
    isa_t isa;

public:
    Class ISA();        // ISA() assumes this is NOT a tagged pointer object    假设这不是一个标记的指针对象
    Class getIsa(); // getIsa() allows this to be a tagged pointer object   允许这是一个标记的指针对象
    //。。。
}

//------- 联合体(定义)------- 
union isa_t 
{
    isa_t() { }                             //构造函数1
    isa_t(uintptr_t value) : bits(value) { }    //构造函数2
    Class cls;          //成员1(占据64位内存空间)
    uintptr_t bits;     //成员2(占据64位内存空间)

#if SUPPORT_PACKED_ISA
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {                //成员3(占据64位内存空间:从低位到高位依次是nonpointer到extra_rc。成员后面的:表明了该成员占用几个bit。)
        uintptr_t nonpointer        : 1;  //(低位)注意:标志位,表明isa_t *是否是一个真正的指针!!!
        uintptr_t has_assoc         : 1;  // 关联对象
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;  //弱引用
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;  //引用计数 相关成员1
        uintptr_t extra_rc          : 19; //引用计数 相关成员2 (用19位来 记录对象的引用次数)
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
}



引用于书籍《Objective-C高级编程》
GNUstep 是Cocoa 框架的互换框架。
CF:Core Foundation
CFRutime.c
苹果的实现大概是采用散列表(引用计数表)来管理引用计数。

在runtime中,有四个数据结构非常重要,分别是SideTablesSideTableweak_table_tweak_entry_t。它们和对象的引用计数,以及weak引用相关。
(相关记录,我的电脑文件:101-Runtime基础)

引用计数表的关系

其实在绝大多数情况下,仅用优化的isa_t 来记录对象的引用计数就足够了。只有在19位的extra_rc盛放不了那么大的引用计数时,才会借助SideTable出马。
SideTable:是一个 全局的引用计数表,它记录了所有对象的引用计数。

先说一下这四个数据结构的关系。 在runtime内存空间中,SideTables 是一个长度为8个元素 的hash数组,里面存储了SideTableSideTables的hash键值就是一个对象obj的address。即:table1 = h(address1)
因此可以说,一个obj,对应了一个SideTable。但是一个SideTable,会对应多个obj。因为SideTable的数量只有64个,所以会有很多obj共用同一个SideTable

SideTables

SideTable地址 关键字
table1 = h ( address1 ) address1
table2 = h ( address2 ) address1
table3 = h ( address3 ) address1
... ...

SideTable

obj对象地址 关键字
address1 = h ( key1 ) 、RefcountMap1、weak_table1 key1
address2 = h ( key2 ) 、RefcountMap2、weak_table2 key2
address3 = h ( key3 )、RefcountMap3、weak_table3 key3
... ...

而在一个SideTable中,又有3个成员,分别是:

  • spinlock_t slock: 自旋锁,用于上锁/解锁 SideTable。
  • RefcountMap refcnts :以DisguisedPtr<objc_object>为key的hash表,用来存储OC对象的引用计数(仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到)。
  • weak_table_t weak_table : 存储对象弱引用指针的hash表。是OC weak功能实现的核心数据结构。

struct weak_table_t {
weak_entry_t *weak_entries; // 保存了所有指向指定对象的 weak 指针
size_t num_entries; // 存储空间
uintptr_t mask; // 参与判断引用计数辅助量
uintptr_t max_hash_displacement; // hash key 最大偏移值
};

其中,refcents是一个hash map,其key是obj的地址,而value,则是obj对象的引用计数。 即:referenceCount = h ( address )

weak_table则存储了弱引用obj的指针的地址,其本质是一个以obj地址为key,弱引用obj的指针的地址作为value的hash表。hash表的节点类型是weak_entry_t。 即:objPointer = h ( address )

SideTables关系图.png

二、Block

iOS全解1-2:Block 详解

三、GCD与多线程

iOS全解1-3:锁、GCD与多线程
iOS全解1-4:NSURLSession




参考文章,如有问题请联系本人QQ1178690076
Object-C高级编程读书笔记(1)——Block的基本概念
Object-C高级编程读书笔记(2)——Block的实质
Object-C高级编程读书笔记(3)——Block的变量截取
Object-C高级编程读书笔记(4)——__block说明符
Object-C高级编程读书笔记(5)——Block的对象类型截取


Objective-C runtime机制(1)——基本数据结构:objc_object & objc_class
Objective-C runtime机制(2)——消息机制
Objective-C runtime机制(3)——method swizzling
Objective-C runtime机制(4)——深入理解Category
Objective-C runtime机制(5)——iOS 内存管理
Objective-C runtime机制(6)——weak引用的底层实现原理

Objective-C runtime机制(7)——SideTables, SideTable, weak_table, weak_entry_t
Objective-C runtime机制(8)——OC对象从创建到销毁
Objective-C runtime机制(9)——main函数前发生了什么
Objective-C runtime机制(10)——KVO的实现机制
Objective-C runtime机制(11)——结业考试

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

推荐阅读更多精彩内容