2018春季iOS面试总结.

最近刚经历完苦逼的面试,抓紧吧技术面试题小结一下

1.tableView如何优化?

  >1.cell重用

  >2.减少透明度设置

  >3.cell数据采用异步子线程网络请求

  >4.复杂的cell可以采用异步绘图方式渲染图片到屏幕

  >5.对cell的高度做计算并缓存起来方便以后直接取

  >6.减少耗时操作

  >7.异步加载图片方法最好写在scrollview的代理方法(当滑动减速时才进行加载图片操作)中

  >8.tableView滚动时最好不要使用一些动画效果

2.框架设计模式—MVC/MVVM模式

MVC:

  >1.Model,用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“ Model ”有对数据直接访问的权力,例如对数据库的访问。“Model”不依赖“View”和“Controller”,也就是说, Model 不关心它会被如何显示或是如何被操作。但是 Model 中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此 Model 的 View 必须事先在此 Model 上注册,从而,View 可以了解在数据 Model 上发生的改变。

  >2.View,能够实现数据有目的的显示(理论上,这不是必需的)。在 View 中一般没有程序上的逻辑。为了实现 View 上的刷新功能,View 需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册

  >3.Controller,起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据 Model 上的改变

  >4.Controller中如果想对View进行更改,可以直接通过outlet拿到View,对其进行操作;View如果想把自己的一些想法告诉Controller,只能通过target,代理或数据源方法

  >5.Controller中如果想对Model进行更改,可以直接拿到Model这个对象,对其进行更改;Model如果想把自己更改的一些数据信息,向Controller汇报,就需要通过广播的方式发送出去

  >6.View和Model之间的通信是完全禁止的

MVVM:

它是将 Controller 中的展示逻辑抽取出来,放置到一个专门的地方,而这个地方就是 viewModel 。MVVM衍生于MVC,是对 MVC 的一种演进,它促进了 UI 代码与业务逻辑的分离。它正式规范了视图和控制器紧耦合的性质,并引入新的组件

3.Xib 和 Storyboard 的优缺点

  >1.XIB:在编译前就提供了可视化界面,可以直接拖控件,也可以直接给控件添加约束,更直接一些,而且类文件中就少了创建控件的代码,确实简化不少,通常每个XIB对应一个类,

  >2.Storyboard:在编译前提供了可视化界面,可拖控件,可加约束,在开发时比较直观,而且一个Storyboard可以有很多的界面,每个界面对应一个类文件,通过storyboard,可以直观地看出整个App的结构

  >3.XIB:需求变动时,需要修改XIB很大,有时候甚至需要重新添加约束,导致开发周期变长,XIB载入相比纯代码自然要慢一些。对于比较复杂逻辑控制不同状态下 显示不同内容时,使用XIB是比较困难的。 当多人开发或者多团队开发时, 如果 XIB 文件被改动,极易导致冲突,而且解决冲突相对要困难很多;Storyboard也是如此.

  >4.Xib是轻量级的,用来描述局部 UI界面;StoryBoard是重量级的,用来描述整个软件的多个界面,并且能展示多个界面之间的跳转关系.

4.通知,代理,block(修饰符,场景,循环引用问题)

  >1.通知:当我们的View嵌套很深,无论使用代理还是block都比较麻烦或者两个控制器之间没有必然的联系时使用;一对一或者一对多,最后切记要移除通知

  >2.代理(weak/assign):两者之间有一定联系,常见的有反向传值;只能一对一

  >3.block(copy):适用于两者间的传值,回调;回调中使用self要注意循环引用问题(使用__weak修饰:__weak typeof (self) weakSelf = self;)

5.谈谈你对runtime的理解

  >1.runtime底层都是C语言实现的,可以通过终端输入clang -rewrite-objc main.m生成main.cpp文件来查看.

  >2.可以在程序运行过程中动态地生成一个类,成员变量和方法,动态地进行修改和删除.

6.定时器NSTimer使用及注意点?

  >1.在ViewWillDisappear方法里执行[self.timer invalidate]; 和 self.timer = nil;这两步操作

  >2.NSTimer要添加到运行循环中才能开启生效

  >3.NSTimer被调用时会对调用对象做retain操作,所以不需要定时器时要销毁,默认执行一次的定时器会自动调用invalidate方法销毁,而重复执行的定时器要手动执行invalidate操作才能销毁,并且要做置空操作.

7.多线程创建方式以及他们的区别(NSThread / GCD / NSOperation)

  >1.NSThread比其他两个轻量级,需要自己管理线程的生命周期,线程同步,线程同步对数据的加锁会有一定的系统开销

  >2.GCD是基于C的底层API,NSOperation属于object-c类;iOS首先引入的是NSOperation,IOS4之后引入了GCD和NSOperationQueue并且其内部是用GCD实现的.NSOperation便是基于GCD的封装

  >3.GCD能够开多个线程 但是并不能管理,但是NSOperation可以管理线程还可以设置最大操作并发数,而GCD可以使用延时方法 after 和sleep 还有一次性once方法 创建单例对象.

  >4.GCD一般适用于单利创建和异步加载图片.GCD核心是队列管理,和NSOperationQueue很相似,但GCD是函数实现,效率高推荐使用.

  >5.NSOperation的start方法默认是同步执行任务,只有将NSOperation与NSOperationQueue进行结合,才会发挥出这种多线程技术的最大功效.当NSOperation被添加到NSOperationQueue中后,就会全自动地执行异步操作.NSOperation还可以设置最大并发数和取消/暂停/恢复等操作.通过设置操作间的依赖,可以确定这些操作的执行顺序.

  >6.处理大量并发数据,又追求性能效率的选择GCD,简单而安全的选择NSOperation实现多线程即可.

8. 谈谈你对单利的理解,来,写一下单利...

  >1.单利对象的生命周期是和APP的整个生命周期是等同的

  >2.单利对象所占的内存空间只有在程序结束时才会释放

  >3.单利对象创建后,会不再被创建,放在静态区,节省了内存空间

  >4.利用单利的设计模式,一些封装类可以使用单利创建模式

  >5.虽然单利对象只创建一次,但是并不代表所有的对象都要使用单利创建方式,这样会占用过多的内存空间

  + (instancetype)shareInstance {

       static Model *instance;

       static dispatch_once_t onceToken;

       dispatch_once(&onceToken,^{

           instance = [[Model alloc] init];

       });

          return instance;

     }


9.说说你对AFN 和 SDWebImage的认识

  >1.AFN是对NSURLConnection的封装,里面封装了最常用的GET/POST请求,支持文件上传和下载,还可以检测网络状态.

  >2.AFN 3.0舍弃了NSURLConnection类

  >3.AFN默认对服务器返回的json数据进行解析,如果不是会报错.如果返回数据格式不一致,需要添加Content-Type:text/xml属性.

  >4.AFN使用中可以自己创建一个单利工具类继承自AFN,方便以后框架更改管理.

  >5.AFN发送的所有请求默认都是异步,所以不会阻塞主线程.block回调默认是在主线程进行

 >1.SDWebImage默认底层的缓存分三块,即内存图片缓存/内存操作缓存/磁盘沙盒缓存.

 >2.SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片.

 >3.SDWebImage内部有通知观察机制:内存接收到警告时要记得做清理缓存(clearMemory)的操作,接收到应用程序将要终止通知时,执行cleanDisk方法,程序进入后台后执行后台清理磁盘操作(backgroundCleanDisk).

 >4.通过NSCache来实现缓存,图片文件缓存时长一周,最大并发数是 6.

 >5.图片缓存思路:先检测内存中是否有缓存,如果有就返回image如果没有就去磁盘中检测有没有缓存,有就返回没有就去重新下载图片,然后把图片缓存到内存再保存到磁盘.

10.内存管理

 ARC:

 >1.系统自动帮我们对对象进行管理

 >2.一般只需要注意循环引用问题即可:定时器导致的循环引用;代理导致的循环引用;block导致的循环引用.

 MRC:

 >1.在每次进行alloc,new,copy,retain等操作时,就要对应的匹配上release操作.

11.GET/POST的区别 Socket / TCP/IP / IM / HTTP ?

 >1.GET请求是向服务器获取数据,效率比较高.POST请求是向服务器发送数据,效率较低.

 >2.GET请求默认是做缓存的而POST请求是默认不会做缓存的.

 >3.GET请求的参数暴露在URL中,相对不安全;而POST请求的参数封装在请求体中,相对GET请求要安全一些,但是仍然要对数据进行加密操作.

 >4.GET请求参数长度有限制而POST请求参数长度无限制.

12.数据存储有哪些方式(归档接档 /偏好设置 /plist/SQLite/CoreData),以及各自的适用场景

  >1.plist文件存储适用于存储基本数据类型,一般都是存取字典和数组,直接写成plist文件,把它存到应用沙盒当中

  >2.偏好设置NSUserDefaults适用于存储基本数据类型,它的底层就是封装了一个字典,利用字典的方式生成plist文件不需要关心文件名(它会自动生成)快速进行键值对存储.

  >3.归档一般适用于保存自定义对象,要遵守NSCoding协议.

  >4.SQLite是一款轻型的嵌入式数据库,属于关系型数据库.

  >5.Core Data是对SQLite数据库的封装,但是没有SQLite高效.

13.数据加密

  >1.一般使用MD5加密,再做加盐操作,即在明文的固定位置插入字符串,然后再进行MD5.

>2.时间戳加密

14.功能原理(无限轮播/上拉加载/下拉刷新/抽屉效果)

  >1.无限轮播采用UICollectionView + NSTimer来实现.

  >2.上拉加载/下拉刷新,采用MJRefresh框架实现

  >3.抽屉效果采用MMDrawerController框架实现

15.登录判断

  >1.HTTP:短连接,通过token机制来验证用户的安全性.

  >2.token值特点:是一个字符串/大整数,只需要保证唯一性.是服务器根据用户的信息(账号/密码/身份认证机制(电话号/身份证号/支付宝账号/银行卡信息)...)来生成的用于标识用户身份的值!当用户在首次登录成功后,服务器端就会生成一个token值,保存在服务器端数据库中,同时将它返回给客户端,客户端接收到token值后一般会保存在cookie中或者保存在沙盒中来作为一个公共参数传递,以后客户端再次发送网络请求(一般不是登录请求)的时候,就会将这个 token 值附带到参数中发送给服务器,服务器接收到客户端的请求之后,会取出token值与保存在本地(数据库)中的token值做对比,如果两个token值相同即说明用户登录成功过!当前用户处于登录状态,如果token值不同说明原来的登录信息已经失效,让用户重新登录.

16.说说响应链

定义:当用户的手指点击屏幕的时候,iOS操作系统通过触摸屏获取用户的点击行为,然后把这个点击信息包装成UITouch和UIEvent形式的实例,然后找到当前运行的程序,在这个程序中逐级寻找能够响应这个事件的所有对象,然后把这些对象放入一个链表,这个链表就是iOS的响应链。所以iOS中的对象想要响应事件都需要直接或间接的继承UIResponder,AppDelegate,UIApplication,UIWindow,ViewController都直接或者间接的继承了UIResponder,所以它们可以作为响应链中的对象来处理我们用户的点击事件

//示例情景:屏幕上有个大的AView,AView里面有BView和CView,CView里有DView;现在用户点击DView

    AView上面添加了BView和CView,CView上面添加了DView,现在用户点击了DView,然后系统会接收到这个点击事件,然后调用 -(BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;方法,该方法是判断点击的点是够在本对象内,如果返回true则继续调用 -(nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;返回当前的这个对象,在我们上图的这歌机构中判断顺序是这样的:

    1.触摸的这个点坐标在AView上吗?true,然后AView加入响应链,继续遍历AView的子页面BView和CView。

    2.在BView上吗?false。该分支结束。

    3.在CView上吗?true,CView加入响应链,继续遍历CView的子视图DView。

    3.在DView上吗?在,DView加入响应链,DView没有子页面,这个检测结束。

    经过以上检测就形成了这样一个链:AView -->CView

-->DView。需要注意的是,响应链的建立一定是在一个subview的关系,如果只是一个页面在另一个页面上面,没有包含关系的话,这个响应链就不会传递

    总结:事件的传递是从上到下的,事件的响应是从下到上的。

    响应链已经建立起来,那么下面就该响应用户刚才的那次点击了,首先找到第一响应者DView,看他有没有处理这次点击事件,如果DView不处理就通过响应链找到它的nextResponder-CView,CView如果也不处理就会一直向上寻找,如果最终找到响应链的最后一个响应者AppDelegate也不处理,就会丢弃这次点击事件。

17.假如现在有4个网络请求ABCD,当四个网络请求都完成之后再去去做任务E,应该如何去做?

//创建信号量            

dispatch_semaphore_t

semaphore = dispatch_semaphore_create(0);

//创建全局并行            

dispatch_queue_t

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);            

dispatch_group_tgroup = dispatch_group_create();

dispatch_group_async(group,queue, ^{                

NSLog(@"处理事件A");                

for(int i = 0; i<10000;i++) {                    

NSLog(@"打印i %d",i);                 }                

dispatch_semaphore_signal(semaphore);            

});

dispatch_group_async(group,queue, ^{                

NSLog(@"处理事件B");                

for(int i = 0; i<10000;i++) {                    

NSLog(@"打印j %d",i);                 

}                

dispatch_semaphore_signal(semaphore);            

});            

dispatch_group_async(group,queue, ^{                

NSLog(@"处理事件C");                

for(int i = 0; i<10000;i++) {                    

NSLog(@"打印k %d",i);                 

}                

dispatch_semaphore_signal(semaphore);            

});            

dispatch_group_async(group,queue, ^{                

NSLog(@"处理事件D");                

for(int i = 0; i<10000;i++) {                    

NSLog(@"打印l %d",i);                 

}                

dispatch_semaphore_signal(semaphore);            

});             


dispatch_group_notify(group,queue, ^{                     

/四个请求对应四次信号等待/                     dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);                    dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);                    dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);                    dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);                    

NSLog(@"处理事件E");            

});

注意:如果需求中改为:同时存在A,B,C,D四个网络请求任务,要求ABCD依次进行,当上一个完成时再进行下一个任务,当四个任务都完成时再处理事件E。这时只需要将队列改为串行队列即可不在需要信号量控制了.

18.有没有用过runtime?什么场景使用?

runtime 交换方法

runtime给分类动态添加属性

runtime字典转模型

runtime动态添加方法

runtime动态变量控制

runtime实现NSCoding的自动归档和解档

19.RunLoop了解吗?有几种mode?mode有什么作用?

当程序启动时,main入口代码会被调用,主线程也随之开始运行,RunLoop 也会随着启动.在UIApplicationMain()方法里面完成了程序初始化,并设置程序的Delegate任务,而且随之开启主线程的RunLoop,就开始接受事件处理.

RunLoop 是一个循环,在里面它接受线程的输入,通过事件处理函数来处理事件.你的代码中应该提供 while or for 循环来驱动 runloop.在你的循环中,用 runloop 对象驱动事件处理相关的内容,接受事件,并做响应的处理.

RunLoop 接受的事件源有两种大类: 异步的input sources, 同步的 Timer sources. 这两种事件的处理方法,系统所规定

RunLoopMode 可以理解成为一个集合, 包括所有要监视的事件源(前面提到的两种源)和要通知的 RunLoop 中注册的观察者.每次运行 RunLoop 时,都需要显示或者隐式的指定其运行在哪一种 Mode(RunLoop 每次只能运行在一个 mode中).在设置RunLoopMode 以后,你的 RunLoop 就会自动过滤和其他 Mode 相关的事件源,而只监视和当前设置 Mode 相关的源(以及通知相关的观察者).大多数时候 RunLoop 都运行在系统定义的默认的模式上.

在代码中,你可以通过mode 名称区分不同的 mode. Cocoa & CoreFoundation 框架通过不同名称(NSString,CFString)定义了缺省 mode 和一系列其他的 mode.你也可以使用不同的名称,定义自己的 mode,然后在这个 mode 中添加一些 source 以及 observer.使用这些 modes 可以从不想要的事件源中过滤事件.大多数情况下,我们都将 runloop 设置成default mode.

RunLoopMode 是基于事件的source源头,而不是事件的type类型去区分的.比如你不能通过 RunLoopMode 去只选择鼠标点击事件或者键盘输入事件.你可以使用 RunLoopMode 去监听端口,暂停计时器或者或者改变添加或删除一些 mode 中关注的sources or observers

Cocoa 和 CoreFoundation 为我们定义了默认和常用的 Mode.RunLoopMode 的名称可以使用字符串来标识,我们也可以使用字符串指定一个 Mode 名称来自定义Model.

下面列出iOS 中已经定义的RunLoopMode:

NSDefaultRunLoopMode,kCFRunLoopDefaultMode: 大多数工作中默认的运行方式。

NSConnectionReplyMode: 使用这个Mode去监听NSConnection对象的状态,我们很少需要自己使用这个Mode。

NSModalPanelRunLoopMode: 使用这个Mode在Model Panel情况下去区分事件(OS X开发中会遇到)。

UITrackingRunLoopMode: 使用这个Mode去跟踪来自用户交互的事件(比如UITableView上下滑动)。

GSEventReceiveRunLoopMode: 用来接受系统事件,内部的Run Loop Mode。

NSRunLoopCommonModes, kCFRunLoopCommomModes: 这是一个伪模式,其为一组run loop mode的集合。如果将Input source加入此模式,意味着关联Input source到Common Modes中包含的所有模式下。在iOS系统中NSRunLoopCommonMode包含NSDefaultRunLoopMode、NSTaskDeathCheckMode、UITrackingRunLoopMode.同时,我们可以使用 CoreFoundation 中的 CFRunLoopAddCommomMode()函数,将自定义的mode 加入其中。

注意 RunLoop 运行时只能以一种固定的 Mode运行,只会监控这个Mode 下添加的 Timer source 和 Input source.如果这个 Mode下没有添加时间源,RunLoop 就会立即返回.

RunLoop 不能运行在 NSRunLoopCommonModes,因为 NSRunLoopModes 是个 Mode 的集合,而不是一个具体的 Mode.我们可以在添加事件源的时候使用 NSRunLoopCommomModes,只要 RunLoop 运行在 NSRunLoopModes 中任何一个 Mode,这个事件源就会被触发.

20讲讲kvc和KVO

KVC是一种键值编码,利用key或者keypath进行属性赋值,或者是字典转模型的实现KVO是键值监听.

KVO底层实现:当一个类的属性被观察的时候,系统会通过runtime动态的创建一个该类的派生类,并且会在这个类中重写基类被观察的属性的setter方法,而且系统将这个类的isa指针指向了派生类,从而实现了给监听的属性赋值时调用的是派生类的setter方法。重写的setter方法会在调用原setter方法前后,通知观察对象值的改变

21.谈谈你对block的理解以及他的注意事项,为什么block内部捕获的变量不能修改?

局部变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值,需要使用__block进行修饰.

而静态变量,静态全局变量,全局变量是可以在block内部进行修改的, 静态变量传递给Block是内存地址值,所以能在Block里面直接改变值;静态全局变量和全局变量由于存储在全局区所以也是可以被block捕获并修改的.

22.为什么NSString 要用copy修饰?

因为:大部分声明一个属性都是用copy修饰字符串,因为系统实现set方法中,拷贝了一份这个属性的赋值,所以以后外界再次修改了这个属性的值,不会影响原来的值.

copy的作用:

1.产生一个副本(如果是不可变字符串,调用这个方法不产生新的对象,只是地址拷贝,如果是可变字符串调用这个方法,则一定会产生一个新的副本对象,地址不一样,可以进行打印验证)

2.修改了副本,并不会影响源对象;

23.为什么tableViewController的delegate属性都是assign而不是retain?

为了防止内存泄漏

24.你对OC内存管理是怎么理解的?需要注意什么?怎么检测的?

需要注意循环引用的情况也是会导致内存问题的

静态检测: 使用XCode分析功能,Product->Analyze

动态检测: 使用instruments里的Leaks工具

25. APP的崩溃原因一般有哪些?以及crash排查手段

EXC_CRASH:向不存在的示例发送消息

SIGSEGV: 引用了released对象 / 引用未init的对象 / 数组越界/ 试图往没有写权限的内存地址写数据,例如在子线程中刷新UI.

SIGABRT: 逻辑错误导致的Crash,比如尝试多次释放同一个内存

可以通过第三方如友盟的bug抓取定位.

如有不当之处,烦请多多指正,在下有礼了...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容