属性

什么是属性?

属性=成员变量+set方法+get方法

我们在OC中用@property来声明一个属性,编译器会自动为你的实例变量生成setter和getter方法。添加实例变量是有一个前提的,就是对象还没有同名的成员变量,就是如果已经有_str了,就不再添加了。

实际上,一个类经过编译后,会生成变量列表ivar_list,方法列表method_list,每添加一个属性,在变量列表ivar_list会添加对应的变量,如_name,方法列表method_list中会添加对应的setter方法和getter方法。

也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter与getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.

我们可以用运行时验证一下。

#import "ViewController.h"

#include <objc/runtime.h>

@interface ViewController ()

@property (nonatomic, strong) NSString *str;

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    unsigned int count = 0;

    Ivar *varList = class_copyIvarList([self class], &count);

    for (unsigned int i = 0; i < count; i++) {

        const char *varName = ivar_getName(varList[i]);

        printf("成员变量-------%s\n",varName);

    }

    Method *methodList = class_copyMethodList([self class], &count);

    for (unsigned int i = 0; i < count; i++) {

        SEL methodName = method_getName(methodList[i]);

NSLog(@"方法-------%@",NSStringFromSelector(methodName));

    }

}

@end

 打印的日志:

成员变量-------_str

2018-07-13 10:32:09.115049+0800 TestAppIOS[445:214283] 方法-------str

2018-07-13 10:32:09.115161+0800 TestAppIOS[445:214283] 方法-------setStr:

2018-07-13 10:32:09.115233+0800 TestAppIOS[445:214283] 方法-------.cxx_destruct

2018-07-13 10:32:09.115264+0800 TestAppIOS[445:214283] 方法-------viewDidLoad

同时我们可以自定义属性的存取方法,如果我们定义了一个,编译器还是会为我们生成另一个。例如,我们自定义get方法, 编译器就会为我们生成set方法。如果我们同时自定义了属性的存取方法,编译器就不会为对象声明实例变量了。


@synthesize:

1.在MRC下,@synthesize str这样,编译器才会自动合成str的存取方法。不过在ARC下就不必了,无论你是否@synthesize str,编译器都会自动合成str的存取方法。

2.如果你声明的属性是str,系统自动给你添加的成员变量是_str,如果你对这个变量名字不满,可以这样@synthesize str = mystr;,自己给个名字。这样系统给添加的成员变量就是myStr,而不是_str,但是变量的存取方法没有变化。不过我建议最好不要这么办,因为都按照约定成俗的方式来命名变量,代码的可读性较高,大家都理解,所以我建议大家最好不要用这个关键字。

@dynamic:

@dynamic 关键字主要是告诉编译器不用为我们自动合成变量的存起方法, 我们会自己实现。即使我们没有实现,编译器也不会警告,因为它相信在运行阶段会实现。如果我们没有实现还调用了,就会报这个错误'-[ViewController setStr:]: unrecognized selector sent to instance 0x10040af10'。

如果我们在子类中重写父类的属性,就会报下面的警告

Autoproperty synthesis will not synthesize property'str';it will be implemented by its superclass,use @dynamicto acknowledge intention

因为我们同时在父类和子类中同时声明了str的属性,系统就不知道该在哪里(父类Father还是子类Son?)自动合成str的存取方法,系统默认是在父类中声明,因为子类可以调用。不过,系统希望我们显式的声明这一点,这样有利于提高代码的可读性。


属性关键字:

读写权限:readonly,readwrite

readwrite:读写。

readonly:只读。

属性默认都是readwrite的,表示可读可写,set/get方法编译器都会自动合成。如果用readonly修成属性,表示该属性是只读的,编译器只会自动合成get方法, 不会合成set方法。另外,我们可以在.h文件中用readonly中修饰属性,在.m文件类扩展中用readwrite修饰同一个属性,这样来防止外界篡改该属性。

原子性:nonatomic,atomic

nonatomic:非原子性。

atomic:原子性。

atomic 和 nonatomic 的区别在于,系统自动生成的 getter/setter 方法不一样。对于atomic的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。系统采用自旋锁,对资源进行保护。

然而,即使用atomic也不能保证绝对的线程安全。举例来说,假设有一个线程A在不断的读取属性name的值,同时有一个线程B修改了属性name的值,那么即使属性name是atomic,线程A读到的仍旧是修改后的值。

简单点说,只保证setter和getter的操作完整性,不保证属性的线程安全。

除了不能保证线程安全,atomic还会消耗系统资源,因此,开发iOS程序时一般都会使用 nonatomic 属性

内存管理语义:strong,weak,assign,copy, retain,unsafe_unretained

有六个关键字,对象默认strong,基本数据类型默认assign。

assign

基本数据类型用 assign 修饰,如果assign修饰的对象,当对象废弃之后,对象会变为野指针,不知道指向哪,再向该对象发消息,非常容易崩溃。栈上空间的分配和回收都是系统来处理的,因此开发者无需关注,也就不会产生野指针的问题。对象是在堆上的,所以对象不能用。

strong

strong的对象会持有对象, 表示一种“拥有关系”。为属性设置新值的时候,设置方法会先保留新值(新值的引用计数加一),并释放旧值(旧值的引用计数减一),然后将新值赋值上去。

weak

weak不会持有对象,表示一种“非拥有关系”。用weak修饰属性的时候,为属性设置新值的时候,设置方法既不会保留新值(新值的引用计数加一),也不会释放旧值(旧值的引用计数减一)。当属性所指的对象释放的时候,属性也会被置为nil。用于修饰UI控件,代理(delegate)。

@property (nonatomic, weak) UIView *weakViewA;

@property (nonatomic, weak) UIView *weakViewB;

@property (nonatomic, strong) UIView *strongView;

- (void)viewDidLoad {

    [super viewDidLoad];


    self.weakViewA = [[UIView alloc] init];

    NSLog(@"weakViewA = %@", self.weakViewA);


    UIView *tempView = [[UIView alloc] init];

    self.weakViewB = tempView;

    NSLog(@"weakViewB = %@", self.weakViewB);


    self.strongView = [[UIView alloc] init];

    NSLog(@"strongView = %@", self.strongView);   

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    NSLog(@"touchesBegan-----weakViewA = %@", self.weakViewA);

    NSLog(@"touchesBegan-----weakViewB = %@", self.weakViewB);

    NSLog(@"touchesBegan-----strongView = %@", self.strongView);

}

打印结果:

copy

通常,可变对象属性修饰符使用strong,不可变对象属性修饰符使用copy。

copy修饰的属性设置新值的时候,当新值是不可变的,和strong是一模一样的。当新值是可变的(开头是NSMutable),设置方法不会保留新值(新值的引用计数加一),而是对新值copy一份,不会影响新值的引用计数。copy常用来修饰NSString,因为当新值是可变的,防止属性在不知不觉中被修改。

使用strong和copy时都会使引用对象引用计数+1。但是使用copy修饰的属性在某些情况下赋值的时候会创建对象的副本,也就是深拷贝。

使用场景:

一般情况下,copy可以用于对不可变容易的属性修饰中,主要是NSArray /NSDictionary/NSString, 也可以用来修饰block。

在MRC和ARC下都可以使用。

其setter方法,与retain处理流程一样,先旧值release,再copy出新的

retain

使用场景:

一般情况下,retain用在MRC情况下,被retain修饰的对象,引用计数retainCount要加1的。

retain只能修饰oc对象,不能修饰非oc对象,比如说CoreFoundation对象就是C语言框架,它没有引用计数,也不能用retain进行修饰。

retain一般用来修饰非NSString 的NSObject类和其子类。

unsafe_unretained

在iOS5.0后,基本被weak替代。但是weak会额外消耗资源(后面说到weak原理会提到),所以并不全优于unsafe_unretained。

unsafe_unretained,不安全的所有权修饰符,和weak一样不会持有对象。其修饰的指针又名悬垂指针,当指针指向的对象被废弃时,指针成为了野指针,处理不好会导致程序崩溃。这点和weak不一样,weak指针在对象废弃时,会置为nil。

和assing修饰对象的时候是一模一样的。为属性设置新值的时候,设置方法既不会保留新值(新值的引用计数加一),也不会释放旧值(旧值的引用计数减一)。唯一的区别就是当属性所指的对象释放的时候,属性不会被置为nil,这就会产生野指针,所以是不安全的


面试题:

1.属性关键字有哪些?

原子性--- atomic nonatomic特质

读/写权限--- readwrite(读写),readonly (只读)

内存管理语义--- assign,strong,weak,unsafe_unretained,copy

2. 如何让自定义类可以用 copy 修饰符?如何重写带 copy 关键字的 setter?

如果想让自己的类具备copy方法,并返回不可边类型,必须遵循nscopying协议,并且实现

- (id)copyWithZone:(NSZone *)zone

如果让自己的类具备mutableCopy方法,并且放回可变类型,必须遵守NSMutableCopying,并实现- (id)mutableCopyWithZone:(nullable NSZone *)zone

注意:再此说的copy对应不可边类型和mutableCopy对应不可边类型方法,都是遵从系统规则而已。如果你想实现自己的规则,也是可以的。

3.用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

众所周知,我们知道,可变类型(NSMutableArray,NSMutableString等)是不可边类型(NSString,NSArray等)的子类,因为多态的原因,我们可以使用不可边类型去接受可变类型。

1.当我们使用strong修饰A不可边类型的时候,并且使用B可变类型给A赋值,再去修改可变类型B值的时候,A所指向的值也会发生改变。引文strong只是让创建的对象引用计数器+1,并返回当前对象的内容地址,当我们修改B指向的内容的时候,A指向的内容也同样发生了改变,因为他们指向的内存地址是相同的,是一份内容。

2.当我们使用copy修饰A不可边类型的时候,并且使用B可变类型给A赋值,再去修改可变类型B值的时候,A所指向的值不会发生改变。因为当时用copy的修饰的时候,会拷贝一份内容出来,并且返回指针给A,当我们修改B指向的内容的时候,A指向的内容是没有发生改变的。因为A指向的内存地址和B指向的内存地址是不相同的,是两份内容

3.copy修饰不可边类型(NSString,NSArray等)的时候,且使用不可边类型进行赋值,表示浅拷贝,只拷贝一份指针,和strong修饰一样,当修饰的是可变类型(NSMutableArray,NSMutableString等)的时候,且使用可边类型进行赋值,表示深拷贝,直接拷贝新一份内容,到内存中。表示两份内

4.@synthesize 和 @dynamic 分别有什么作用?

如果@synthesize和@dynamic都没写,那么替代的就是@syntheszie var = _var;

@dynamic :修饰的属性,其getter和setter方法编译器是不会自动帮你生成。必须自己是实现的。

@synthesize:修饰的属性,其getter和setter方法编译器是会自动帮你生成,不必自己实现。且指定与属性相对应的成员变量。

5.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?

代理是使用weak来修饰的。1.使用weak是为了避免循环引用。2.当使用weak修饰的属性,当对象释放的时候,系统会对属性赋值nil,object-c有个特性就是对nil对象发送消息也就是调用方法,不会cash。

delegate:传递的是事件(even),代理可以让A对象通知B对象,我(A)发生的变化,前提B遵循了A的代理,并且实现了A的代理方法。

dataSource: 传递的是数据。如果A对象声明了数据源,当我们创建A对象的时候,我们就该实现数据源,来告诉A,他所需要的一些数据。例如:tableView数据源方法,需要告诉它,我要实现几组cell,每组cell多少行cell,实现的cell什么样式,什么内容

相同点:代理和Block大多是我们都可以用来做倒序传值的。我们都得注意避免循环引用。不然我们去使用代理还是Block的时候,都需要判断它们是否实现

不同点:代理使用weak修饰,代理必须先声明方法。当我们调用代理的时候要判断是否已经实现。

block:使用的是copy来修饰,block保存的是一段代码,其实也就是一个函数。并且可以自动捕捉自动变量,如果想修改此自动变量,还必须使用__block修饰。

6.weak置为nil是如何实现的?

runtime对注册的类,会进行布局,对于弱对象会加入一个哈希表中。用弱指向的对象内存地址作为键,当此对象的引用计数为0的时候会dealloc,如果弱指向对象的内存地址是a,那么就会以a为键,在这个弱表中搜索,找到所有以a为键的弱对象,从而设置为nil。

runtime 如何实现 weak 属性具体流程大致分为 3 步:

1、初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。

2、添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向(指针可能原来指向着其他对象,这时候需要将该 weak 指针与旧对象解除绑定,会调用到 weak_unregister_no_lock),如果指针指向的新对象非空,则创建对应的弱引用表,将 weak 指针与新对象进行绑定,会调用到 weak_register_no_lock。在这个过程中,为了防止多线程中竞争冲突,会有一些锁的操作。

3、释放时:调用 clearDeallocating 函数,clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entry 从 weak 表中删除,最后清理对象的记录

7.@protocol 和 category 中如何使用 @property?

在协议中使用属性只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性

类别使用@property也是也会生成setter和getter方法的声明,如果我们真的需要给类别增加属性的实现,需要生成运行时的两个函数:

objc_setAssociatedObject

objc_getAssociatedObject

8.assign可以修饰OC对象吗?

不可以 

9.属性修饰符atomic的内部实现是怎么样的?能保证线程安全吗?

10.SideTable的结构是什么样的?最好可以画出来?

11.@peoperty的本质是什么?ivar getter setter是如何生成并添加到类中?

@property = ivar + getter + setter;

大致生成了五个东西

OBJC_IVAR_$类名$属性名称 :该属性的“偏移量”(偏移量),这个替换量是“硬编码”(硬编码),表示该变量距离放置对象的内存区域的起始地址有多远。

setter与getter方法对应的实现函数

ivar_list :成员变量列表

method_list :方法列表

prop_list :属性列表

也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter与getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的接近量,然后将setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,以便能够读取正确字节数,系统对象转变量的指针类型进行了类型强转

12.数组copy后里面的元素会复制一份新的吗?

不会,数组里面存的是之前对象的地址,不会改变,可以自己测试一下

13.如何访问私有成员变量?

通过runtime来获取对象的成员变量Ivar,然后再通过object_getIvar来获取某个对象的成员变量的值

Ivar userNameIvar = class_getInstanceVariable([model class], "_userName");

NSString *userName = object_getIvar(model, userNameIvar);

14.什么情况使用 weak 关键字,相比 assign 有什么不同?

在ARC中,在有可能出现循环引用的时候,经常要通过让其中一端使用

自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用弱,自定义IBOutlet中控件属性一般也使用弱;

assign: 一般用来修饰基本的数据类型,包括基础数据类型和C数据类型。assign声明的属性是不会增加引用计数的,声明的属性释放后,属性就不存在了。但是,指针没有置空过程,成为了野指针,如果新的对象被分配到了这个内存地址上,又会crash,所以一般只用来声明基本的数据类型。

weak:弱引用,不增加引用计数。防止循环引用时使用,并且在所指向的对象被释放之后,weak指针会被设置的为nil。weak引用通常是用于处理循环引用的问题,如代理及block的使用中,相对会较多的使用到weak。

只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。

当属性用weak修饰时,会将该指针存入一个weak表,该表是一个hash表,obj为键值,存储了一组obj_weak的地址。当属性使用weak修饰时,性能开销较大。

15.怎么用 copy 关键字?

NSString,NSArray,NSDictionary等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString,NSMutableArray,NSMutableDictionary;

块也经常使用copy关键字,

block使用copy是从MRC遗留下来的“传统”,在MRC中,方法内部的block是在栈区的,使用copy可以把它放到堆区。

在ARC中写不写都行:

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上

16. 这个写法会出什么问题: @property (copy) NSMutableArray *array?

两个问题:1,添加,删除,修改复制内部的元素的时候,程序会因为找不到对应的方法而崩溃。因为copy就是复制一个不可变NSArray的对象; 2,使用了atomic属性会严重影响性能

18.@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?默认的是哪些

属性是描述类的特征,也就是具备什么特性。三个部分,带下划线的成员变量,get、setter方法。

默认关键字:readwrite,assign, atomic; -- 是针对基本类型(NSInteger, BOOL, NSUInteger, int, 等)

但是针对引用类型, 默认:strong, readwrite, atomic (例如:NSString, NSArray, NSDictory等)

19. @synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?

实例变量=成员变量= ivar 

在类的实现代码里通过@synthesize语法来指定实例变量的名字:

@实现CYLPerson @synthesize firstName = _myFirstName; @synthesize lastName = _myLastName; @结束

语法上述会将生成的实例变量命名为_myFirstName与_myLastName,而不再使用默认的名字

总结下@synthesize合成实例变量的规则,有以下几点:

1.  如果指定了成员变量的名称,会生成一个指定的名称的成员变量,

2. 如果这个成员已经存在了就不再生成了。

3.  如果是@synthesize foo;即将生成一个名称为foo的成员变量,则:

如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,

4.  如果是@synthesize foo = _foo;就不会生成成员变量了。

20. 在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?

我们要搞清楚一个问题,什么情况下不会autosynthesis(自动合成)?

1. 同时重组了setter和getter时

2. 重建了预期属性的getter时

3. 使用了@dynamic时

4. 在@protocol中定义的所有属性

5. 在类别中定义的所有属性

6. 重写(overridden)的属性

21. 讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?

atomic会对属性的setter/getter方法进行加锁,这仅仅只能保证在操作setter/getter方法是安全的。不能保证其他线程的安全

例如:线程1调用了某一属性的setter方法并进行到了一半,线程2调用其getter方法,那么会执行完setter操作后,再执行getter操作,线程2会获取到线程1setter后的完整的值;当几个线程同时调用同一属性的setter、getter方法时,会获取到一个完整的值,但获取到的值不可控

22.被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?

23.关联对象有什么应用,系统如何管理关联对象?其被释放的时候需要手动将所有的关联对象的指针置空么?

24. assign可以修饰OC对象吗?

25.具体说一下循环强引用,可以吗?

26. 属性和成员变量的区别?

属性对成员变量扩充了存取方法 .

属性默认会生成带下划线的成员变量 .

但只声明了变量,是不会有属性的,

27.深copy和浅copy的区别?

浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

copy方法:如果是非可扩展类对象,则是浅拷贝。如果是可扩展类对象,则是深拷贝。

mutableCopy方法:无论是可扩展类对象还是不可扩展类对象,都是深拷贝。

[immutableObject copy] //浅复制

[immutableObject mutableCopy] //深复制

[mutableObject copy] //深复制

[mutableObject mutableCopy] //深复制

28.如何访问并修改一个类的私有属性?

有两种方法可以访问私有属性,一种是通过KVC获取,一种是通过runtime访问并修改私有属性

29.MRC下如何重写retain修饰变量的setter方法?

@property(nonatiomic,retain)id obj;

-(void)setObj:(id)obj{

       if(_obj != obj){

           [_obj release];

 _obj = [obj retain];}}

30. 

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