iOS 开发 21 年 6 月面试总结(未完待续~)
SDWebImageView
流程
sd_setImageWithUrl
sd_internalSetImageWithUrl
loadImageWithUrl
quryCacheForKey
diskResult
downloadImage
storImage
SDWebImageCache
内存磁盘双缓存
内存缓存 SDMemoryCache
shouldUseWeakMemoryCache
存在 NSCache,不可控,容易被清理
存在 NSCache 并存在 MemoryCache,可以保证 NSCache 被清理之后,从 MemoryCache 中获取
磁盘缓存
创建缓存路径
内存缓存查找
磁盘缓存查找,存入内存缓存
内存泄露
产生原因
循环引用造成
检查方式
Product -> Analyze
Instruments -> Leaks
具体产生场景
Timer 没有释放,在合适的时机,调用[timer invalidate]
blcok 持有了对象,不能释放。使用 weak 属性
Network 结束后,取消 task
delegate 的传递,尽量使用 weak
消息转发机制
快速查找
是否支持 tagged pointer 对象
为空则直接返回 Zero
获取
isa
获取 isa, 然后获取 class isa & iSA_Mask
开启缓存查找
通过 isa 平移 16 获取到 cache
通过 cache&掩码获取到 buckets
通过平移获取到 mask(arm64 为中为右移 48 位)
在通过 mask & sel 获取到方法下标 index
在通过平移在 buckets 中获取到查找的 buket,取到 imp
比较通过 index 获取到的 bucket 中的 sel 与我们查找的 sel 是否为同一个
缓存命中,返回 imp
如果不相等,则判断此 bucket 是否为第 buckets 的第一个元素
如果是第一个元素,则将 bucket 设置为 buckets 的最后一个元素,进行第二次递归查找
如果不是第一个元素,则从最后一个元素递归向前查找
如果一直没有找到则退出递归,进入
__objc_msgSend_uncached
慢速查找流程
慢速查找 (lookUpImpOrForward)
排除一些干扰因素,是否是已知类呀,是否完成初始化
确认继承链,因为这里会存在类方法和实例方法的区别,所以需要确定其继承链
然后进去死循环查找流程
当前查找的类是否是不断优化的类
查找其缓存
找到了就直接返回 imp
没找到循环继续
通过二分查找,在方法列表中查找
找到了,写入 cache,返回 imp
查找其父类 curClass = curClass -> superClass
现在父类缓存中找
找到了是否为 forward_imp
结束循环,进入动态方法决议
存入查找类的 cache,返回 imp
一直没有找到,则 imp 为 forward_imp,进入动态方法决议
imp = forward_imp 动态方法决议
动态方法决议
判断是否是元类
resolveInstanceMethod
resolveClassMethod
如果动态方法决议处理了,则返回 IMP,如果没有处理则进入消息转发
消息转发
快速转发快速转发,可以转发给其它类或对象,其它的类会对象如果实现了查找方法的类方法或者对象方法,则不报错,如果没有则进入慢速转发
慢速转发这里在
methodSignatureForSelector
中返回方法签名,在 forwardInvocation 中可以处理,也可以不处理,处理方式这里也需要要在对应的类里面有对应的实现
多线程的使用
NSThread 的使用
通过 alloc 来启用,需要手动开启
通过 detachNewThread 直接开启
通过 performSelector 来开启
NSThread 支持 KVO,可以监听到 threa 的执行状态
isExecuting
是否正在执行
isCancelled
是否被取消
isFinished
是否完成
isMainThread
是否是主线程
threadPriority
优先级
GCD
dispatch_after
dispatch_once
dispatch_apply
dispatch_group_t
dispatch_semaphore_t 控制并发数
dispatch_source_t 可以实现 timer 不依赖于 runloop,精度比 timer 高
NSOperation
Block
block 类型
__NSGlobalBlock__
:全局 block,存储在全局区
此时的
block
没有访问外界变量,无参也无返回值
__NSMallocBlock__
:堆区 block
此时的 block 会访问外界变量,即底层拷贝 a,所以是堆区 block
__NSStackBlock__
:栈区 block
在完成 a 的底层拷贝前,此时的 block 还是栈区 block,拷贝完成之后,从上面的堆区 block 可以看出,就变成堆区 block 了
可以通过__weak 不进行强持有,block 就还是栈区 block
总结
block 直接存储在全局区
如果 block 访问外界变量,并进行 block 相应拷贝
如果此时的 block 是强引用,则 block 存储在堆区
如果此时的 block 通过
__weak
变成了弱引用,则 block 存储在栈区
block 的循环引用
造成循环引用的原因
互相持有,导致释放不掉
解决循环引用的方法
weak-strong-dance
如果 block 未嵌套 block,直接使用__weak 修饰的 self 即可,否则,需要搭配__strong 来使用
__block 修饰对象(需要注意在 block 内部使用完成之后置为 nil,block 必须调用)
传递 self 作为 block 的参数,提供给 block 内部使用
block 为什么要用 copy 修饰,使用 strong 修饰会有什么问题吗?
block 用 strong 和 copy 修饰都可以,对于 block,编译器重写了 strong 底层逻辑,使其和 copy 是一样的原理,即把 block 从栈区复制到堆区
使用 copy 是因为 block 初始化时位于栈区,copy 可以把栈区的对象复制到堆区,而栈上的 block 对象在作用域结束后释放
block 的的底层结构
block 的底层结构是一个结构体,也可以说 block 其实是一个对象、函数
block 为什么需要调用
在底层 block 的类型是
__main_block_impl_0
结构体,通过其同名构造函数创建,第一个传入的 block 的内部实现代码块,即_main_block_func_0
,用 fp 来表示,然后赋值给 impl 的 FuncPtr,然后再 main 中进行了调用,这也是 block 为什么需要调用的原因,如果不调用,内部实现的代码块将无法执行
函数生命,block 的内部实现声明成了一个函数
__main_block_impl_0
通过 block 的
FuncPtr
指针,调用 block 执行
block 是如何捕获外界变量的
对外界变量没有__weak 修饰时,是进行了值拷贝
对外界变量实现__weak 修饰时,是进行了指针拷贝
__weak 原理
block 使用使用__weak 修饰的变量时,会生成
__Block_byref_x_0
结构体结构体用来保存原始变量的指针和值
将变量生成的结构体对象的指针地址传递给 block,然后再 block 内部就可以对外界变量进行操作了
block 的三重拷贝
通过
__block_copy
实现对象的自身拷贝,从栈区到堆区通过
__block_byref_copy
方法,将对象拷贝为block_byref
结构体类型调用
_block_object_assign
方法,对_block
修饰的当前变量进行拷贝
只有_block 修饰的对象,block 的 copy 才有三层
Extension 类扩展
类扩展在编译器,会作为类的一部分,和类一起编译进来
类的扩展只是声明,依赖当前的主类,没有.m 文件,可以理解为一个.h 文件
声明属性和成员变量,也可以声明方法
在当前类.h 中声明的属性和方法是共有的,在.m 中声明的方法和属性是私有的
通过 Extention 新建的声明的属性与方法是私有的
Category 分类
给类添加新的方法
不能给类添加成员属性,添加了成员属性,也无法取到
分类中使用 @property 定义的变量,只会生成 Setter,Getter 方法的声明,不会生成对应的实现
可以通过 runtime 给分类添加属性
关联对象
Objc_setAssociatedObject
创建一个 AssociationsManager 管理类
获取全局静态 Hasmap
判断插入的关联值是否存在
创建一个空的 ObjctAssociationMap 去取查询的简直对
如果发现没有这个 key 就插入一个空的 BucketT 进去
标记关联对象
用当前的 policy 和 value 组成一个 ObjcAssociation 替代原来的 BucketT 中的空
标记一下 ObjctAssociationMap 的第一次为 false
Objec_getAssociatedObject
创建一个 AssociationsManager 管理类
获取全局静态 Hasmap
根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
如果迭代查询器不是最后能获取
找到 ObjctAssociationMap 的迭代查询器获取一个经过 policy 修饰的 value
返回 value
AssociationsManager
AssociationsHashMap
ObjctAssociationMap
ObjcAssociation
KVO
kvo 与 NSNotificationCenter 有什么区别
相同点
两者的实现都是观察者模式,都是用于监听
都能实现一对多的操作
不同点
KVO 用于监听对象属性的变化,并且属性名都是通过 NSString 来查找,编译器不会检测对错与自动补全
NSNotification 的发送监听的操作我们可以控制,KVO 的由系统控制
KVO 可以记录新旧值得变化
KVO 对可变集合的监听
通过[arr addObject:object]这种方式向数组添加元素,是不会触发 KVO
对可变集合需要调用对应的 KVC 方法,监听才能生效
[[self.person mutableArrayValueForkey:@"dataArray"] addObject:@"1"]
KVO 的实现原理
添加 KVO 之后,实例对象的 isa 指向了一个新的派生类
NSKVONotifying_Class
重写了原本类的观察属性的 setter 方法
新增了_isKVO 来判断当前是否是 KVO 类
在观察移除之前对象的 isa 指向一直是派生类
移除观察之后对象的呃 isa 指向原有类
派生类一旦产生就会一直存在内存中,不会被销毁
KVC
API
valueForKey、setValueForKey
valueForKeyPath、setValue:ForKeyPath
KVC 设值原理
查找是否有这三种
setter
方法set<Key>
_set<Key>
setIs<Key>
如果能找到上面三个中的任意一个,则直接设值属性的 value
查找
accessInstanceVariablesDirectly
是否返回 YES如果返回 YES,则查找间接访问的实例变量进行赋值,查找顺序为
_<Key>
_is<Key>
<key>
is<Key>
如果返回 NO,则进入 3
如果能找到任意一个实例变量 i,ii,iii,iv,则直接赋值,否则进入 3
如果 setter 方法或者实例变量都没有找到,系统会执行该对象的
setValue:forUndefinedKey:
方法,默认抛出NSUndefinedKeyException
类型异常
KVC 取值原理
首先查找 getter 方法,顺序为
get<Key>
<Key>
is<Key>
_<Key>
如果找到执行 5
继续查找
countOf<Key>
、objectIn<Key>AtInde
、<Key>AtIndex
如果找到
countOf<Key>
和其他两个中的一个,则会创建一个响应所有NSArray
方法的集合代理对象,并返回该对象,即NSKeyValueArray
,是NSArray
的子类。代理对象随后将接收到的所有NSArray
消息转换为countOf<Key>
,objectIn<Key>AtIndex:
和<key>AtIndexes:
消息的某种组合,用来创建键值编码对象。如果原始对象还实现了一个名为 get:range:之类的可选方法,则代理对象也将在适当时使用该方法(注意:方法名的命名规则要符合 KVC 的标准命名方法,包括方法签名。)查找
countOf <Key>
,enumeratorOf<Key>
和memberOf<Key>
这三个方法如果这三个方法都找到,则会创建一个响应所有
NSSet
方法的集合代理对象,并返回该对象,此代理对象随后将其收到的所有NSSet
消息转换为countOf<Key>
,enumeratorOf<Key>
和memberOf<Key>:
消息的某种组合,用于创建它的对象检查类方法
InstanceVariablesDirectly
是否YES
,依次搜索_<key>
_is<Key>
<key>
is<Key>
根据属性的类型值,返回不同的结果
对象指针,直接返回结果
如果是 NSNumber 支持标量的类型,则将其存储在 NSNumber 实例中并返回
如果是 NSNumber 不支持的标量类型,请转换为 NSValue 对象
如果一直没有找到,系统会执行该对象的
valueForUndefinedKey:
方法,默认抛出NSUndefinedException
异常
RunLoop
怎么保证子线程的数据回来更新 UI 操作不打断用户的滑动操作
将更新 UI 的事件,放到主线程的 NSDefaultRunloopModel 上执行,这样就会等用户不再滑动,主线程的 RunLoop 由 UITrakingRunLoopModel 切换到 NSDefaultRunloopModel 时再去更新 UI
RunLoop 有几种模式
kCFRunloopDefultModel:默认模式,主线程也是在此 model 下执行
UITrakingRunLoopModel:跟踪用户事件
UIInitializetionRunLoopModel:在刚启动 APP 时第一个 Model,启动完成后就不再使用
GSEventReceiveRunloopModel:接受内部事件,一般用不到
kCFRunloopCommonModels:伪 model,是同步 source/timer/observer 到多个 Model 的一种解决方案
面向对象的三大特性
封装
继承
多肽
网络
http 与 https 有什么区别
Http 协议 = Http 协议 + SSL/TLS 协议
SSL 全程是
Secure Sockets Layer
,即安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。TLS 全称是Transport Layer Security
,即安全传输层协议,即 HTTPS 是安全的 HTTP
TCP
三次握手
发出链接请求
客户端的 TCP 首先向服务端的 TCP 发送一条特殊的 SYN 报文
发送 SYN 报文后,客户端进入 SYN_SENT 状态,等待服务端确认,并将 SYN 比特置为 1 的报文段
授予链接
收到 SYN 报文后,服务端会为该 TCP 链接分配 TCP 缓存和变量,服务端的 TCP 进入 SYN_RCVD 状态,等待客户端 TCP 发送确认报文
向客户端发送允许链接的 SYNACK 报文段
确认,并建立链接
收到 SYNACK 报文段后,客户端也要为 TCP 分配缓存和变量,客户端的 TCP 进入 ESTABLISHED 状态
向服务端 TCP 发送一个报文段,这最后一个报文段对服务端的允许连接的报文表示了确认(将 server_isn + 1 放到报文段首部的确认字段中)。因为连接已经建立了,所以该 SYN 比特被置为 0。 这个阶段,可以在报文段负载中携带应用层数据
收到客户端该报文段后,服务端 TCP 也会进入 ESTABLISHED 状态,可以发送和接收包含有效载荷数 据的报文段。
四次挥手
客户端发出终止 FIN=1 报文段
客户端向服务端发送 FIN=1 的报文段,并进入 FIN_WAIT_1 状态
服务端向客户端发送确认报文 ACK=1
收到客户端发来的 FIN=1 的报文后,向客户端发送确认报文
服务端 TCP 进入 CLOSE_WAIT 状态
客户端送到确认报文后,进入 FINAL_WAIT_2 状态,等待服务端 FIN=1 的报文
服务端向客户端发送 FIN=1 报文段
服务端发送 FIN=1 的报文
服务端进入 LAST_ACK 状态
客户端向服务端发送 ACK=1 报文段
收到服务端的终止报文后,向服务端发送一个确认报文,并进入 TIME_WAIT 状态
如果 ACK 丢失,TIME_WAIT 会使客户端 TCP 重传 ACK 报文。最后关闭,进入 CLOSE 状态,释放缓存和变量
服务端收到之后,TCP 也会进入 CLOSE 状态,释放资源
为什么建立链接需要三次握手,而断开链接缺需要四次挥手
因为,在服务端发送 ACK 信号后,还有可能数据传输没有完成
数据传输完成才会发送 FIN=1 的信号
在四次握手中,客户端为什么在 TIME_WAIT 后必须等待 2MSL 时间呢
为了保证客户端发送的最后一个 ACK 报文段能够到达服务器
为什么要三次握手,而不是二次或者三次
两次握手会可能导致已失效的连接请求报文段突然又传送到了服务端产生错误,四次握手又太浪费资源
PUT 和 POST,POST 与 GET 的区别
PUT VS POST
push 和 post 都有更改指定 URL 的语义,但 PUT 被定义为 idempotent 的方法,post 则不是。idempotent 多个请求产生的效果是一样的
PUT 请求,多个请求,后面的请求会覆盖掉前面的请求
POST 请求,后一个请求不会覆盖掉前一个请求
GET VS POST
GET 参数通过 URL 传递,POST 放在 Request body 中
GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置
GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留
Get 请求中有非 ASCII 字符,会在请求之前进行转码,POST 不用,因为 POST 在 Request body 中,通过 MIME,也就可以传输非 ASCII 字符
一般我们在浏览器输入一个网址访问网站都是 GET 请求
HTTP 的底层是 TCP/IP。HTTP 只是个行为准则,而 TCP 才是 GET 和 POST 怎么实现的基本。GET/POST 都是 TCP 链接。GET 和 POST 能做的事情是一样一样的。但是请求的数据量太大对浏览器和服务器都是很大负担。所以业界有了不成文规定,(大多数)浏览器通常都会限制 url 长度在 2K 个字节,而(大多数)服务器最多处理 64K 大小的 url
GET 产生一个 TCP 数据包;POST 产生两个 TCP 数据包。对于 GET 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应 200(返回数据);而对于 POST,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据)
在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的 TCP 在验证数据包完整性上,有非常大的优点。但并不是所有浏览器都会在 POST 中发送两次包,Firefox 就只发送一次。
HTTP 的请求方式有哪些
GET
POST
PUT
DELET
HEAD
OPTIONS
cookie 和 session 的区别
存放位置不同
cookie 存放在客户的浏览器
session 存在在服务器上
安全程度不同
cookie 不是很安全,其他人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗,考虑到安全应当使用 session
性能使用程度不同
session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用 cookie。
数据存储大小不同
单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie,而 session 则存储与服务端,浏览器对其没有限制
会话机制不同
session 会话机制:session 会话机制是一种服务器端机制,它使用类似于哈希表(可能还有哈希表)的结构来保存信息
cookies 会话机制:cookie 是服务器存储在本地计算机上的小块文本,并随每个请求发送到同一服务器。 Web 服务器使用 HTTP 标头将 cookie 发送到客户端。在客户端终端,浏览器解析 cookie 并将其保存为本地文件,该文件自动将来自同一服务器的任何请求绑定到这些 cookie。
Swift
swift 类与结构体的区别
关键字
class
struct
class 定义的属性必须初始化, struct 不用初始化
定义函数
class
可以使用关键字static
修饰,struct
只能使用static
修饰
扩展下标
class 和 struct 都可以使用扩展下标
初始化
结构体有默认的初始化方法
结构体不能继承
类是引用类型,结构体是值类型
类有 deinit 方法,结构体没有
什么时候使用结构体
用于封装简单的数据结构类型
结构在传递的时候是被赋值,而不是被引用
不需要继承或者方法
swift 怎么防止父类方法在子类被重写
final
关键字
作者:丸疯链接:https://juejin.cn/post/6971808317282730015
评论