iOS性能优化

一、如何提高一个应用程序的性能?
1、使用ARC减少内存失误,dealloc需要重写并对属性置nil。
2、重用。
3、尽量少使用透明或半透明。会产生额外的运算。
4、少用运算获得圆角,不论view.maskToBounds还是layer.clipToBounds都会有很大资源开销,必须要用圆角的话不如图片本身就做成圆角。
5、不要阻塞主线程。
6、使用正确的容器类型。
7、图片与imageView相同大小避免多余运算。
8、使用懒加载。
9、使用绘制。

二、如何加强iOS里的列表滚动时的顺畅感?
1、UITableViewCell里不要添加太多subview,最好只添加一个cellview。
2、UITableViewCell上的子View的opaque属性设为YES。其实默认也是不透明。UITableViewCell尽量不要包含透明的子View。
3、在cellview里,重写drawRect函数绘制UITableViewCell的内容。
4、在绘制字符串时,尽可能使用drawAtPoint: withFont:,而不要使用更复杂的drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withFont:(UIFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode;如果要绘制过长的字符串,建议自己先截断,然后使用drawAtPoint: withFont:方法绘制。
5、在绘制图片时,尽量使用drawAtPoint,而不要使用drawInRect。drawInRect如果在绘制过程中对图片进行放缩,会特别消耗CPU。
6、如果绘制cell过程中,需要下载cell中的图片,建议在绘制cell一段时间后再开启图片下载任务。譬如先画一个默认图片,然后在0.5S后开始下载本cell的图片。
7、即使下载cell图片是在子线程中进行,在绘制cell过程中,也不能开启过多的子线程。最好只有一个下载图片的子线程在活动。否则也会影响UITableViewCell的绘制,因而影响了UITableViewCell的滑动速度。(建议结合使用NSOpeartion和NSOperationQueue来下载图片,如果想尽可能找的下载图片,可以把[self.queuesetMaxConcurrentOperationCount:4];)
8、最好自己写一个cache,用来缓存UITableView中的UITableViewCell,这样在整个UITableView的生命周期里,一个cell只需绘制一次,并且如果发生内存不足,也可以有效的释放掉缓存的cell。
9、不要将tableview的背景颜色设置成一个图片。这回严重影响UITableView的滑动速度。在限时免费搜索里,我曾经翻过一个错误:self.tableView_.backgroundColor = [UIColorcolorWithPatternImage:[UIImageimageNamed:@"background.png"]];通过这种方式设置UITableView的背景颜色会严重影响UTIableView的滑动流畅性。修改成self.tableView_.backgroundColor = [UIColor clearColor];之后,fps从43上升到60左右。滑动比较流畅。
10、cell的行高不是固定值,需要计算,则要尽可能缓存行高值,避免重复计算行高。这里指的是UITableViewDelegate里的行高函数。

三、TableView是怎么优化的?tableView下拉加载数据的时候为什么会出现卡顿,如何解决?
(1)使用不透明视图
(2)不要重复创建不必要的table cell。
(3)减少视图的数目。
(4)不要做多余的绘制工作。
(5)预渲染图像。
(6)不要阻塞主线程。
四、什么时候会用到懒加载?如果需要展示大量图片的时候还要一个个去加载么?
懒加载,又称为延迟加载。说的通俗一点,就是在开发中,程序启动的时候不加载资源,只有在运行当需要一些资源时,再去加载这些资源。
我们知道iOS设备的内存有限,如果在程序在启动后就一次性加载将来会用到的所有资源,那么就有可能会耗尽iOS设备的内存。这些资源例如大量数据,图片,音频等等

五、工程中的图片存在哪里,如何保证刷新后内存不断增加问题,以及节约用户流量
工程中使用的图片可以自己创建个文件夹进行存放你需要用的图片,也可以在你工程中的Images.xcassets文件中存放你的图片.
解决刷新内存不断增加的问题,需要把你创建的控件布局写成对应类的属性,在ViewDidLoad中初始化一次.不要在其他的类方法里创建控件.
刷新节约用户流量的方法就是在一定时间段中判断当前的请求时间和上次刷新的时间并限定一个时间范围在某个范围内刷新不重新请求数据.

六、怎么解决缓存池满的问题(cell)
ios中不存在缓存池满的情况,因为通常我们ios中开发,对象都是在需要的时候才会创建,有种常用的说话叫做懒加载,还有在UITableView中一般只会创建刚开始出现在屏幕中的cell,之后都是从缓存池里取,不会在创建新对象。缓存池里最多也就一两个对象,缓存池满的这种情况一般在开发java中比较常见,java中一般把最近最少使用的对象先释放。

//清楚cell的缓存
NSArray *subviews = [[NSArray alloc] initWithArray:cell.contentView.subviews];
for (UIView *subview in subviews) {
[subview removeFromSuperview];

七、tableView的重用机制?(简单了解即可)
查看UITableView头文件,会找到NSMutableArray* visiableCells,和NSMutableDictnery*reusableTableCells两个结构。visiableCells内保存当前显示的cells,reusableTableCells保存可重用的cells。TableView显示之初,reusableTableCells为空,那么tableViewdequeueReusableCellWithIdentifier:CellIdentifier返回nil。开始的cell都是通过[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]来创建,而且cellForRowAtIndexPath只是调用最大显示cell数的次数。比如:有100条数据,iPhone一屏最多显示10个cell。程序最开始显示TableView的情况是:1.用

[[UITableViewCellalloc] initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:CellIdentifier]

创建10次cell,并给cell指定同样的重用标识(当然,可以为不同显示类型的cell指定不同的标识)。并且10个cell全部都加入到visiableCells数组,reusableTableCells为空。
2.向下拖动tableView,当cell1完全移出屏幕,并且cell11(它也是alloc出来的,原因同上)完全显示出来的时候。cell11加入到visiableCells,cell1移出visiableCells,cell1加入到reusableTableCells。
3.接着向下拖动tableView,因为reusableTableCells中已经有值,所以,当需要显示新的cell,cellForRowAtIndexPath再次被调用的时候,tableViewdequeueReusableCellWithIdentifier:CellIdentifier,返回cell1。cell1加入到visiableCells,cell1移出reusableTableCells;cell2移出visiableCells,cell2加入到reusableTableCells。之后再需要显示的Cell就可以正常重用了。

八、简单描述一下你在开发的过程中,如何实现程序的性能优化?
答:我在开发的过程中会注意一下几点来优化程序性能:
1).避免庞大的XIB、Storyboard,尽量多用纯代码开发
2).使用懒加载的方式延迟加载界面
3).避免反复处理数据
4).避免使用NSDateFormatter和NSCalendar。
5).图片缓存的取舍
UIImage加载图片方式一般有两种:
A:imagedNamed初始化
B:imageWithContentsOfFile初始化
二者不同之处在于,imageNamed默认加载图片成功后会内存中缓存图片,这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象.如果缓存中没有找到相应的图片对象,则从指定地方加载图片然后缓存对象,并返回这个图片对象.
而imageWithContentsOfFile则仅只加载图片,不缓存.
大量使用imageNamed方式会在不需要缓存的地方额外增加开销CPU的时间来做这件事.当应用程序需要加载一张比较大的图片并且使用一次性,那么其实是没有必要去缓存这个图片的,用imageWithContentsOfFile是最为经济的方式,这样不会因为UIImage元素较多情况下,CPU会被逐个分散在不必要缓存上浪费过多时间.使用场景需要编程时,应该根据实际应用场景加以区分,UIImage虽小,但使用元素较多问题会有所凸显.

九、 如何让你的应用程序更加省电?
答:
(1). 如果程序用到定位,需要在定位完毕之后关闭定位,或者降低定位的频率,不停的定位会消耗电量。
(2). 如果用到了蓝牙,需要使用蓝牙时候开启蓝牙,蓝牙用完之后关闭蓝牙,蓝牙也很耗电。
(3). 优化算法,减少循环次数,大量循环会让CPU一直处于忙碌状态,特别费电。
(4). 尽量不要使用网络轮询(心跳包、定时器),使用推送。
(5). timer的时间间隔不宜太短,满足需求即可。
(6). 不要频繁刷新页面,能刷新1行cell的最好刷新一行,尽量不要reloadData。
(7). 线程适量,不宜过多。

十、如何减小一个应用程序占用存储空间?
检查程序 去掉多余的xib。iOS App Store相关因素作为提交到App Store中app里的可执行文件是被加过密的。加密的副作用是可执行文件的压缩效果没有之前的好了。Build Settings编译选项,将build setting中的Optimization Level设置为Fastest, Smallest [-Os];将build setting中的Strip Debug Symbols During Copy设置为YES(COPY_PHASE_STRIP = YES),这样可以减小编译出二进制文件的尺寸。Target针对较少的CPUs对程序指定的特定CPU类型做优化处理,以生成相对于的可执行文件。不同的硬件,将运行不同的可执行代码。虽然这样优化后的程序,只能针对某些设备运行,但是这大大减小可执行程序的大小。要想只设定特定类型的CPUs,可以修改build setting中的Architectures,将其从Standard $(ARCHS_STANDARD)修改为你希望支持的列表中对应的特定类型CPU。有效的CPU名称列在Valid Architectures (VALID_ARCHS) build setting中。请不要修改Valid Architectures设置项,最好由Xcode管理。尽量使用8-bit图片。使用8-bit的PNG图片,比32-bit的图片能减少4倍的压缩率。由于8-bit的图片支持最多256种不同的颜色,所以8-bit的图片一般只应该用于一小部分的颜色图片。例如灰度图片最好使用8-bit

十一、如何优化内存?
(1).用ARC管理内存ARC(Automatic ReferenceCounting,自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露。它自动为你管理retain和release的过程,除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存。(2).在正确的地方使用reuseIdentifier一个开发中常见的错误就是没有给UITableViewCells,UICollectionViewCells,甚至是UITableViewHeaderFooterViews设置正确的reuseIdentifier。(3).尽量把views设置为透明如果你有透明的Views你应该设置它们的opaque属性为YES。原因是这会使系统用一个最优的方式渲染这些views。如果设为YES,渲染系统就认为这个view是完全不透明的,这使得渲染系统优化一些渲染过程和提高性能。如果设置为NO,渲染系统正常地和其它内容组成这个View。默认值是YES。(4).避免过于庞大的XIB当你加载一个XIB的时候所有内容都被放在了内存里,包括任何图片。如果有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。(5).不要阻塞主线程永远不要使主线程承担过多。因为UIKit在主线程上做所有工作,渲染,管理触摸反应,回应输入等都需要在它上面完成。一直使用主线程的风险就是如果你的代码真的block了主线程,你的app会失去反应。大部分阻碍主进程的情形是你的app在做一些牵涉到读写外部资源的I/O操作,比如存储或者网络。(6).在Image Views中调整图片大小如果要在UIImageView
中显示一个来自bundle的图片,你应保证图片的大小和UIImageView的大小相同。在运行中缩放图片是很耗费资源的,特别是UIImageView
嵌套在UIScrollView
中的情况下。如果图片是从远端服务加载的你不能控制图片大小,比如在下载前调整到合适大小的话,你可以在下载完成后,最好是用background thread,缩放一次,然后在UIImageView中使用缩放后的图片。(7).选择正确的Collection学会选择对业务场景最合适的类或者对象是写出能效高的代码的基础。当处理collections时这句话尤其正确。一些常见collection的总结:·Arrays:有序的一组值。使用index来lookup很快,使用value lookup很慢,插入/删除很慢。·Dictionaries:存储键值对。用键来查找比较快。·Sets:无序的一组值。用值来查找很快,插入/删除很快。(8).打开gzip压缩大量app依赖于远端资源和第三方API,你可能会开发一个需要从远端下载XML, JSON, HTML或者其它格式的app。问题是我们的目标是移动设备,因此你就不能指望网络状况有多好。一个用户现在还在edge网络,下一分钟可能就切换到了3G。不论什么场景,你肯定不想让你的用户等太长时间。减小文档的一个方式就是在服务端和你的app中打开gzip。这对于文字这种能有更高压缩率的数据来说会有更显著的效用。好消息是,iOS已经在NSURLConnection中默认支持了gzip压缩,当然AFNetworking这些基于它的框架亦然。像Google App Engine这些云服务提供者也已经支持了压缩输出。(9).重用和延迟加载(lazy load) Views更多的view意味着更多的渲染,也就是更多的CPU和内存消耗,对于那种嵌套了很多view在UIScrollView里边的app更是如此。这里我们用到的技巧就是模仿UITableView
和UICollectionView
的操作:不要一次创建所有的subview,而是当需要时才创建,当它们完成了使命,把他们放进一个可重用的队列中。这样的话你就只需要在滚动发生时创建你的views,避免了不划算的内存分配。创建views的能效问题也适用于你app的其它方面。想象一下一个用户点击一个按钮的时候需要呈现一个view的场景。有两种实现方法:1.创建并隐藏这个view当这个screen加载的时候,当需要时显示它;2.当需要时才创建并展示。每个方案都有其优缺点。用第一种方案的话因为你需要一开始就创建一个view并保持它直到不再使用,这就会更加消耗内存。然而这也会使你的app操作更敏感因为当用户点击按钮的时候它只需要改变一下这个view的可见性。第二种方案则相反-消耗更少内存,但是会在点击按钮的时候比第一种稍显卡顿。(10). Cache, Cache,还是Cache!一个极好的原则就是,缓存所需要的,也就是那些不大可能改变但是需要经常读取的东西。我们能缓存些什么呢?一些选项是,远端服务器的响应,图片,甚至计算结果,比如UITableView的行高。NSURLConnection默认会缓存资源在内存或者存储中根据它所加载的HTTP Headers。你甚至可以手动创建一个NSURLRequest然后使它只加载缓存的值。(11).权衡渲染方法在iOS中可以有很多方法做出漂亮的按钮。你可以用整幅的图片,可调大小的图片,uozhe可以用CALayer,CoreGraphics甚至OpenGL来画它们。当然每个不同的解决方法都有不同的复杂程度和相应的性能。简单来说,就是用事先渲染好的图片更快一些,因为如此一来iOS就免去了创建一个图片再画东西上去然后显示在屏幕上的程序。问题是你需要把所有你需要用到的图片放到app的bundle里面,这样就增加了体积–这就是使用可变大小的图片更好的地方了:你可以省去一些不必要的空间,也不需要再为不同的元素(比如按钮)来做不同的图。然而,使用图片也意味着你失去了使用代码调整图片的机动性,你需要一遍又一遍不断地重做他们,这样就很浪费时间了,而且你如果要做一个动画效果,虽然每幅图只是一些细节的变化你就需要很多的图片造成bundle大小的不断增大。总得来说,你需要权衡一下利弊,到底是要性能能还是要bundle保持合适的大小。(12).处理内存警告一旦系统内存过低,iOS会通知所有运行中app。如果你的app收到了内存警告,它就需要尽可能释放更多的内存。最佳方式是移除对缓存,图片object和其他一些可以重创建的objects的strong references.幸运的是,UIKit提供了几种收集低内存警告的方法:· 在app delegate中使用applicationDidReceiveMemoryWarning:
的方法· 在你的自定义UIViewController的子类(subclass)中覆盖didReceiveMemoryWarning
· 注册并接收UIApplicationDidReceiveMemoryWarningNotification的通知一旦收到这类通知,你就需要释放任何不必要的内存使用。例如,UIViewController的默认行为是移除一些不可见的view,它的一些子类则可以补充这个方法,删掉一些额外的数据结构。一个有图片缓存的app可以移除不在屏幕上显示的图片。(13).重用大开销对象一些objects的初始化很慢,比如NSDateFormatter和NSCalendar。然而,你又不可避免地需要使用它们,比如从JSON或者XML中解析数据。想要避免使用这个对象的瓶颈你就需要重用他们,可以通过添加属性到你的class里或者创建静态变量来实现。注意如果你要选择第二种方法,对象会在你的app运行时一直存在于内存中,和单例(singleton)很相似。还需要注意的是,其实设置一个NSDateFormatter的速度差不多是和创建新的一样慢的!所以如果你的app需要经常进行日期格式处理的话,你会从这个方法中得到不小的性能提升。(14).减少使用Web特性UIWebView很有用,用它来展示网页内容或者创建UIKit很难做到的动画效果是很简单的一件事。但是你可能有注意到UIWebView并不像不像驱动Safari的那么快。这是由于以JIT compilation为特色的Webkit的Nitro Engine的限制。所以想要更高的性能你就要调整下你的HTML了。第一件要做的事就是尽可能移除不必要的javascript,避免使用过大的框架。能只用原生js就更好了。另外,尽可能异步加载例如用户行为统计script这种不影响页面表达的javascript。最后,永远要注意你使用的图片,保证图片的符合你使用的大小。使用Sprite sheet提高加载速度和节约内存。(15).优化Table ViewTable view需要有很好的滚动性能,不然用户会在滚动过程中发现动画的瑕疵。为了保证table view平滑滚动,确保你采取了以下的措施:· 正确使用reuseIdentifier
来重用cells· 尽量使所有的view opaque,包括cell自身· 避免渐变,图片缩放,后台选人· 缓存行高· 如果cell内现实的内容来自web,使用异步加载,缓存请求结果· 使用shadowPath
来画阴影· 减少subviews的数量·尽量不适用cellForRowAtIndexPath:
,如果你需要用到它,只用一次然后缓存结果· 使用正确的数据结构来存储数据· 使用rowHeight
,sectionFooterHeight
和sectionHeaderHeight
来设定固定的高,不要请求delegate(16).使用Autorelease PoolNSAutoreleasePool
负责释放block中的autoreleased objects。一般情况下它会自动被UIKit调用。但是有些状况下你也需要手动去创建它。假如你创建很多临时对象,你会发现内存一直在减少直到这些对象被release的时候。这是因为只有当UIKit用光了autorelease pool的时候memory才会被释放。好消息是你可以在你自己的@autoreleasepool里创建临时的对象来避免这个行为:
NSArray *urls = <# An array of file URLs #>;for(NSURL *url in urls) {@autoreleasepool {NSError *error;NSString fileContents = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];/ Process the string, creating and autoreleasing more objects. */}}

这段代码在每次遍历后释放所有autorelease对象(17).选择是否缓存图片常见的从bundle中加载图片的方式有两种,一个是用imageNamed
,二是用imageWithContentsOfFile
,第一种比较常见一点。既然有两种类似的方法来实现相同的目的,那么他们之间的差别是什么呢?imageNamed
的优点是当加载时会缓存图片。imageNamed
的文档中这么说:这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象如果它存在的话。如果缓存中没有找到相应的图片,这个方法从指定的文档中加载然后缓存并返回这个对象。相反的,imageWithContentsOfFile
仅加载图片。如果你要加载一个大图片而且是一次性使用,那么就没必要缓存这个图片,用imageWithContentsOfFile
足矣,这样不会浪费内存来缓存它。然而,在图片反复重用的情况下imageNamed
是一个好得多的选择
十二、正常使用应用时,按HOME键退出。稍后再次打开,界面出现卡顿现象,尝试分析一下可能原因。
这是由iOS系统管理决定的,但APP退出在后台后,只有10秒的持续运行时间,然后暂停。但该APP还在内存中,当出现内存警告,也就是别的APP要运行,而此时内存又不足的情况下,系统会回收停在后台APP所占用的内存。如果出现这种情况,那么你再次打开你的APP,就会重新启动。
不知道你是为什么要让APP在后台还要继续运行,如果非得这样,那可以使用多线程技术中的gcd,可以让APP退出后继续运行很长一段时间(大概10分钟)
iOS APP类型:
1.保存现场。按下Home键10秒内直接杀死进程,并释放内存。

iOS支持的“多任务”。按下Home键转入多任务状态,保留在内存中,但只能系统允许的动作:比如GPS,比如VoIP,比如音乐等等。
3.真正的桌面级别的多任务。只有Safari/Mail是,苹果嫡系大都都不是。这个级别的app在后台没有任何限制动作。
无限制动作的程序,一会在用户无察觉的情况下耗光电力,二会有安全上面的问题(那些在后台依旧默默发送你的个人消息程序)
顺便提一句,后两种占用内存的app,也会在任意时间从内存中被砍掉,取决于你是否动用了其它app而导致内存不足。
真正不会被砍掉的后台,只有苹果那个通知系统。

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

推荐阅读更多精彩内容