技术学习

一:UI视图方面的问题

1.数据源同步问题处理
1.并发访问,数据拷贝
主线程 : 删除数据 reload
子线程 : 网络请求,数据解析
问题: 删除操作之后请求的数据会重新刷新回来
解决办法 : 记录删除操作,请求完数据后同步删除操作

2.UIView和CALayer
1.UIView负责提供内容和触摸事件,参与响应链
2.CALayer负责显示内容Content
分工明确 遵守单一职责

3.UI卡顿的优化
GPU:异步绘制防止离屏渲染
CPU:预排版和预渲染,对象的创建销毁全部放在子线程

异步绘制 : displayLayer方法

4.离屏渲染
当我们指定了UI视图的某些属性标记为他在未预合成之前不能用于当前屏幕上显示的时候就会触发离屏渲染
GPU在当前屏幕缓冲区外再开辟一个缓冲区进行渲染操作

圆角和masktobounds同时使用的时候回触发离屏渲染
阴影 光栅化 图层蒙版都会触发

5.为何要避免离屏渲染
在触发离屏渲染的时候会增加GPU的工作量,可能导致GPU+CPU的工作总耗时超过了16.7MS 会产生掉帧的问题
创建新的缓冲区,上下文切换 影响性能

二:Objective-C语言特性相关问题

分类和扩展
1.你用分类做了哪些事
声明私有方法 分解体积庞大的类文件 framework的私有方法公开化
2.分类的特点
运行时决议,可以为系统类添加分类
3.分类中可以添加哪些内容
1.实例方法 2.类方法 3.协议 4.属性(没有实例变量.需要通过关联对象实现)
总结: 1.分类添加的方法可以"覆盖"(效果上) 原类的方法
2.同名分类方法生效取决于编译顺序
3.名义相同的分类会引起编译报错
4.关联对象
关联对象由一个AssociationsManager管理并在AssociationsHashMap储存
所有的关联内容都放在一个全局的容器中

2.扩展可以做的事情
声明私有属性,声明私有方法,声明私有成员变量
3.分类和扩展的区别
扩展编译时决议 只能以声明形式存在,多数情况下存在于.M文件 不能为系统类添加扩展
分类运行时决议 有声明有实现 能为系统类添加分类

通知和代理
通知是使用观察者模式实现用于跨层传递消息的机制

怎样实现通知机制
key是add监听的通知名称 value是我们添加的observer
Observer是一个数组包含多个通知的观察者

KVO的相关
1.什么是KVO
kvo是观察者模式的一种实现
apple使用了isa-swizzling实现了KVO
2.KVO的实现是
系统在使用kvo的时候动态创建一个监听的类的子类
然后子类重写set方法通过set方法内部调用了
willChangeValueForKey 以及didChangeValueForKey
使用setter改变值KVO生效
使用KVC改变值KVO生效
成员变量直接修改需要手动添加willChangeValueForKey 以及didChangeValueForKey 才可以生效

KVC的相关
setvalue:forkey和 valueforkey流程
先查找是否有get set方法没有的话去寻找相关的成员变量 都没有的话抛出异常

三:RUNTIME

objc_object
包含 isa_t isa操作相关 弱引用相关 关联对象相关 内存管理相关
objc_class 继承Objc_object 是一个类对象
包含Class superClass / cache_t cache / class_data_bits_t bits

共用体isa_t
指针型isa isa值代表Class的地址
非指针型isa isa值的部分代表Class的地址

消息传递机制


image.png

缓存查找实质上是hash查找

对于已排序的列表使用二分查找算法查找方法对应执行函数
对于未排序的列表使用一般遍历查找方法对应执行函数

消息转发机制


image.png

method-swizzling


image.png

应用场景:开发过程需要对页面进出添加统计信息可以替换viewdidload或者viewwillAppear 然后在替换方法中添加关于统计信息的代码

runtime动态添加方法 运行时添加
动态方法解析
@dynamic
动态运行时语言将函数决议推迟到运行时
编译时语言在编译期进行函数决议

能否向编译后的类增加实例变量 否
能否向动态添加的类增加实例变量 是

四.内存管理

内存区域图


image.png

栈区:方法调用
堆区:通过alloc等分配的对象
bss:未初始化的全局变量
data:已初始化的全局变量
text:程序代码

问题一:iOS操作系统是怎样对内存进行管理的
要从场景上入手
TarggedPointer :对于小对象如NSNumber使用TarggedPointer
NONPOINTER_ISA:对于64位的采用NONPOINTER_ISA方案处理
散列表管理 :包括引用计数表和弱引用表

NONPOINTER_ISAd的内存管理

arm64架构

散列表方式

SideTable()结构

image.png

使用多个SideTable储存信息 如果是单个会有效率问题
所以使用了分离锁提高访问效率
分离锁可以把表分拆给多个部分操作的时候是并发操作提高了访问效率
SideTable实际上是一个Hash表

散列表的数据结构

1.spinlock_t:自旋锁
"忙等"的锁 适用轻量访问.
2.RefcountMap 引用计数
用指针通过HASH查找对象的引用计数
引用计数表

image.png

weakly_referenced:存储对象是否弱引用 0无1有
弱引用表weak_table_t
image.png

MRC和ARC的区别
image.png

image.png
引用计数管理
alloc实现

经过一系列调用,最终调用C函数的 calloc,此时并没有设置引用计数为1但是因为retainCount获取引用计数的操作所以会显示引用计数为1

retain实现

通过当前对象指针通过HASH函数计算快速的在SideTables里找到对应的SideTable。之后通过当前对象指针从SideTable当中用HASH查找获取当前对象的引用计数值
总结:通过俩次hash查找找到当前对象引用计数值并进行加一操作
引用计数的变量

release实现

和retain类似 最后实行减一操作

retainCount实现

会定义一个变量并复制为1
获取到引用计数并进行+=操作。这就是alloc并没有设置引用计数 但是你可以获取到引用计数为1的原因
dealloc的实现


image.png
object_dispose()实现会去实现object_destructInstance()方法
object_destructInstance的实现
image.png
clearDeallocating()的实现
image.png
弱引用管理
问题:关于添加弱引用变量的时候

可以通过弱引用对象进行HASH算法的计算查找对应的位置

问题:关于系统是怎样将一个weak变量添加到他对应的弱引用表

一个被声明为__weak的对象指针经过编译器编译之后会调用Objc_initWeak()方法,然后经过一系列的函数调用最终在weak_register_no_lock()函数中进行弱引用变量的添加,添加的位置是通过弱引用对象进行HASH算法的计算查找对应的位置
如果查找位置已经有了当前对象所对应的弱引用数组,就将新的弱引用变量添加到数组当中如果没有就重新创建一个弱引用数组并在0号位置添加最新弱引用指针,后面的初始化为0。

问题:当一个对象被释放或废弃之后weak变量是怎么处理的

清除weak变量,同时指向为nil

问题2:内部是怎么处理的了

当一个对象被dealloc之后,在dealloc之中会调用弱引用清除的相关函数,在函数的内部实现当中会根据当前对象指针查找弱引用表,把当前对象对应的弱引用都拿出来(一个数组),然后遍历数组所有的弱引用指针并置为nil

自动释放池
问题:什么是自动释放池/自动释放池的实现结构

自动释放池结构实际上是以栈为结点通过双向链表的形式组合而成的,并且和线程一一对应的.

image.png
问题:AutoreleasePool的实现原理是怎样的
aotureleasePoolPush
image.png

image.png
aotureleasePoolPop

1.根据传入的哨兵对象找到对应位置
2.给上次push操作之后添加的对象依次发送release消息
3.回退next指针到正确位置

image.png

释放时机:在当次Runloop将要结束的时候调用AotureleasePoolPage::pop()

问题:AutoreleasePool为何可以嵌套使用

多层嵌套调用就是多次插入哨兵对象

循环引用
问题:如何破除循环引用

1.避免产生循环引用
2.合适的时机手动断开

问题2:具体的解决方案有哪些

__weak __block __unsafe_unretained

__block破解
image.png
__unsafe_unretained(不建议使用因为会产生悬垂指针)
image.png
怎么解决循环引用的使用示例

NSTimer
第一个解决办法:timer置nil

image.png

第二个解决办法:添加中间对象


image.png

令中间对象持有俩个弱引用变量分别是NSTimer 和原对象 然后在nstimer中直接分派的回调 是在中间对象中实现的.在实现的方法当中对他所持有的target进行值的判断.值存在把NSTimer的回调给原对象,如果值不存在那么设置NSTimer为无效状态

五:Block

问题:什么是BLOCK

block是将函数及其上下文封装起来的对象

问题:什么是BLOCK调用

block调用就是函数的调用

问题:关于BLOCK截获特性,是否了解

1.对于基本数据类型的截获变量是截取其值
2.对于对象类型的局部变量连同所有权修饰符(属性关键字)一起截获(或者说强引用)
3.以指针形式截获局部静态变量
4.不截获全局变量,静态全局变量

__block修饰符
问题一:__block作用 __forwarding指针是用来干什么的

__block修饰的变量都变成了对象
栈上的__forwarding指针是指向自身的
进行COPY之后
栈上的__forwarding指针是指向堆上的__forwarding而堆上的__forwarding指针是指向自身的


image.png
问题二:__forwarding指针存在意义

不论在任何内存位置,都可以顺利的访问一个__block的变量.

image.png

block循环引用

1.block捕获的变量是当前对象的成员变量而block也是当前对象的成员变量.会造成自循环式的循环引用可以通过添加__weak修饰避免
2.__block造成的循环引用如下图
__block为啥会在ARC下循环引用而MRC下没有问题以及规避方法

image.png

可以在block内部给__block修饰的对象进行置nil操作断开一个强引用
弊端:如果不调用block 这个环会一直存在

六:第三方库相关面试问题

一 AFNetworking

image.png

image.png

AFURLSessionManager的作用

  • 创建和管理NSURLSession、NSURLSessionTask
  • 实现NSURLSessionDelegate等协议的代理方法
  • 引入AFSecurityPolicy保证请求安全
  • 引入AFNetworkReachabilityManager监听网络变化

二 SDWebImage

image.png

三 ReactCocoa

rac是一个函数响应式编程的第三方库


image.png

问:我们怎么使用订阅


image.png

七:多线程

GCD

问题一:
image.png

由队列引起的循环等待

问题二:
image.png

image.png

dispatch_async(global_queue) 这个Block会在GCD所维护的线程池中某个线程上去执行处理,这些线程池默认是没有RUNLOOP的 而performSelector 方法 是提交任务到RUNLOOP上的 所以无法执行

问题三:怎样利用GCD实现多读单写
image.png

image.png
问题三:

NSOperation

问题一:优势和特点

1.添加任务依赖
2.任务执行状态的控制
3.控制最大并发量

问题二:NSOperation的状态

isReady isExecuting(是否正在执行中) isFinished isCancelled

问题三:我们应该怎样控制NSOperation的状态

(主要看是不是重写了:NSOperation的main方法和start方法)
如果重写main方法,底层控制变更任务完成执行状态,以及任务退出。(我们不需要操作)
如果重写start方法,自行控制任务状态

NSThread

image.png
问题一:NSThread执行原理

内部创建了一个p_thread线程,当我们的main函数或者我们指定的target selector方法执行结束之后,系统会为我们进行线程的退出管理操作。如果我们想要维护一个常驻线程需要在NSThread所对应的selector方法中去维护一个Runloop事件循环

问题一.你都用过哪些锁?结合实际谈谈如何使用的
@synchronized

一般在创建单例对象的时候使用,保证多线程环境下创建的对象是唯一的

atomic

修饰属性的关键字
对被修饰对象进行原子操作(不负责使用)


image.png
OSSpinLock

循环等待访问,并不释放当前资源
用于轻量级数据访问,比如简单的Int +1 / -1操作

NSLock
image.png

可以使用NSRecursiveLock(递归锁)解决上述问题

dispatch_semaphore_t(信号量)
image.png

image.png

image.png

多线程问题总结

问题1.iOS系统为我们提供的几种多线程技术各自的特点是什么

GCD:实现简单的线程同步包括:1.子线程分派 2.多读单写
NSOperation:方便我们对任务的状态进行控制,可以添加移除依赖
NSThread:基本用于创建一个常驻线程

问题2.NSOperation对象在Finished之后是怎样从queue当中移除掉?

会使用KVO的方式通知queue达到了移除对象的目的

八:RUNLOOP

image.png
问题一:什么是RUNLOOP

RUNLOOP是通过内部维护的事件循环来对事件/消息进行管理的一个对象

问题二: 什么是事件循环

维护的事件循环可以用来处理消息或者事件对他们进行管理,同时在没有消息需要处理时,会从用户态发生一个到内核态的切换,由此可以实现当前线程的休眠.避免资源占用,同时在有消息需要处理时,会从内核态发生一个到用户态的切换,由此可以实现唤醒当前线程


image.png
问题二:main函数为什么能保持不退出

因为UIApplicationMain函数内部会启动主线程的一个Runloop,而Runloop他又是对事件循环的维护机制,有事做事没事会从用户态发生一个到内核态的切换.

RUNLOOP的数据结构

image.png
CFRunLoop:

包括
pthread 一一对应关系 (和RUNLOOP)
currentMode ------- CFRunLoopMode
modes ------- NSMutableSet<CFRunLoopMode *>
commonModes ------- NSMutableSet<NSString *>
commonModeItems ------- observe timer source

image.png
source0:需要手动唤醒线程
source1:具备唤醒线程的能力
CFRunLoopTimer:基于时间的定时器,和NSTimer是toll-free bridged(免费桥转换)的
CFRunLoopObserver:观测时间点
  • kCFRunLoopEntry: RunLoop准备启动时
  • kCFRunLoopBeforeTimers:RunLoop将要对Timer相关事件进行处理的时候
  • kCFRunLoopBeforeSoureces:将要处理source事件
  • kCFRunLoopBeforeWaiting:通知对应观察者当前RunLoop将要进行休眠
  • kCFRunLoopAfterWaiting:内核态切换到用户态之后不久
  • kCFRunLoopExit: RunLoop退出的通知
问题:RunLoop和mode以及他所对应的source time observer是怎样的关系

一对多的关系


image.png
问题:RunLoop为什么有多个mode

为了起一个屏蔽的效果,屏蔽其他Mode的影响

CommonMode

image.png

事件循环的实现机制

问题:我们的程序从点击一个图标到程序启动到最终被杀死这一过程系统是怎样实现的?

void CFRunLoopRun()

问题:当一个处于休眠状态的RunLoop我们可以用哪些方式来唤醒
image.png

image.png

image.png

RunLoop与多线程是什么关系

线程是和RunLoop一一对应的
自己创建的线程默认是没有RunLoop的

问题:怎样实现一个常驻线程
image.png

image.png

image.png

RunLoop面试总结

问题一:什么是RunLoop,它是怎样做到有事做事,没事休息的?

是由于我们在调用CFRunLoopRun()相关方法之后会调用系统的一个函数mash_message ,会从用户态发生一个到内核态的切换,由此可以实现当前线程的休眠.所以做到有事做事没事休息

问题二:怎样保证子线程数据回来更新UI的时候不打断用户的滑动操作

可以通过把子线程抛回给主线程进行UI更新的这块逻辑包装起来提交到主线程的default模式下面,而滑动的时候mode切换为UITrackingRunLoopMode。分派到default模式下的任务就不会执行,而手停止滑动时候当前mode切换到default模式下面,这时候会处理子线程上抛的任务。 就不会打断用户的滑动操作

九:网络

image.png

HTTP

问题一:你是怎样理解HTTP的,HTTP协议包含哪些内容了

超文本传输协议

  • 请求/响应报文
  • 连接建立流程
  • HTTP的特点
请求/响应报文
image.png

image.png
问题二:HTTP的请求方式

GET POST HEAD PUT DELETE OPTIONS

问题三:GET和POST请求方式的区别

GET请求参数以?分割拼接到 URL后面,POST请求参数在Body的里面
GET参数长度限制是2048个字符,POST一般没有该限制
GET请求不安全,POST比较安全(初级回答)
实际应该从语义的角度来回答 | 语义:指的是协议的一个定义规范
GET一般用于获取资源:具有安全的,幂等的,可缓存的特性;
POST一般用于处理资源:具有非安全的,非幂等的,不可缓存的特性

安全性:不应引起Server端的任何状态变化

GET HEAD OPTIONS都是具备安全性的

幂等性:同一个请求方法执行多次和执行一次的效果完全相同

PUT DELETE GET

可缓存性:请求是否可以缓存

HEAD GET

连接建立流程

image.png

HTTP的特点

  • 无连接 (由HTTP的持久链接进行补偿)
  • 无状态(引出Cookie/Session技术)
持久链接
image.png
问题一:为什么HTTP提供了持久链接这个方案

为了提升网络请求访问的效率.

头部字段
  • Connection : keep-alive(客户端希望使用持久链接)
  • time:这次持久链接(TCP链接)需要在多长时间内有效
  • max:这条链接最多可以发生多少个http请求
怎样判断一个请求是否结束
  • Content-length : 1024 (客户端可以根据所接受数据的字节数是否达到这个值,达到了就说明我们这条HTTP请求的响应已经全部接收,意味着请求结束了)
  • chunked : 当有多个块通过HTTP的TCP连接传输给客户端的时候,每个报文都带有chunked字段,而最后一个块的chunked是个为空的值,可以根据此判断是否请求结束.
Charles抓包原理是怎样的
  • 利用中间人攻击这个漏洞来实现的

HTTPS与网络安全

HTTPS和HTTP有怎样的区别
image.png
HTTPS连接的建立流程是怎样的
image.png
会话秘钥

会话秘钥 = random S + random C + 预主秘钥

HTTPS都使用了哪些加密手段?为什么?
  • 连接过程中使用的非对称加密,比较耗时!
  • 后续通信过程要使用对称加密
非对称加密
image.png
对称加密
image.png
TCP/UDP
image.png
UDP特点
  • 无连接
  • 尽最大可能交付
  • 面向报文: 既不合并也不拆分


    image.png
UDP功能
  • 复用
  • 分用
  • 差错检测
复用和分用
image.png
差错检测
image.png
TCP(传输控制协议)
特点
  • 面向连接 :
    数据传输开始之前,需要建立连接 三次握手 数据传输结束之后,需要释放连接 四次挥手
  • 可靠传输
  • 面向字节流
  • 流量控制
  • 拥塞控制
问题一:为什么要进行三次握手?

为了解决同步请求连接建立的报文超时的场景,规避这些连接建立产生的异常。

问题二:为什么要进行四次挥手?
可靠传输

无差错/不丢失/不重复/按序到达
停止等待协议
无差错情况/超时重传/确认丢失/确认迟到

流量控制

滑动窗口协议


image.png

接收窗口通过向TCP的报文首部字段去更改窗口值来调整发送方的发送速率的

流量控制

慢开始,拥塞避免
快恢复,快重传

TCP的慢启动特性:如下图
image.png

横轴:交互次数 纵轴:窗口值的大小

DNS解析

image.png

image.png

image.png
DNS解析存在哪些常见问题
- DNS劫持问题
image.png
DNS劫持和HTTP的关系是怎样的?

毫无关系的 因为DNS解析发生在HTTP连接建立之前的,DNS解析请求使用UDP数据报,端口号53

怎么解决DNS劫持

1.httpDNS
将使用DNS协议向DNS服务器的53端口进行请求
变为使用HTTP协议向DNS服务器的80端口进行请求


image.png

2.长连接


image.png
- DNS解析转发问题
image.png
Session/Cookie
image.png
什么是Cookie

Cookie主要用来记录用户状态,区分用户;状态保存在客户端


image.png

客户端发送的Cookie在http请求报文的Cookie首部字段中
服务端设置的http响应报文的Set-Cookie首部字段

怎样修改Cookie
  • 新Cookie覆盖旧Cookie
  • 覆盖规则: name path domain等需要与原cookie一致
怎样删除Cookie
  • 新Cookie覆盖旧Cookie
  • 覆盖规则: name path domain等需要与原cookie一致
  • 设置Cookie的expires=过去的一个时间点,或者maxAge = 0
怎样保证Cookie的安全
  • 对Cookie进行加密处理
  • 只在https上携带Cookie
  • 设置Cookie为httpOnly,防止跨站脚本攻击
什么是Session

Session主要用来记录用户状态,区分用户;状态保存在服务端

Session和Cookie的关系
  • Session需要依赖Cookie机制
Session工作流程
image.png

十:设计模式

六大设计原则

  • 单一职责原则
    一个类只负责一件事,比如UIView和CALayer
  • 开闭原则
    对修改关闭,对扩展开放
  • 接口隔离原则
    使用多个专门的协议,而不是一个庞大臃肿的协议
    协议中的方法尽量少
  • 依赖倒置原则
    抽象不应该依赖于具体实现,具体实现应该依赖于抽象
  • 里氏替换原则
    父类可以被子类无缝替换,且原有功能不受任何影响 (如KVO)
  • 迪米特法则
    一个对象应当对其他对象有尽可能少的了解。高内聚,低耦合

责任链模式

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