1.说说你了解的设计模式。说说你了解的设计原则。
设计模式的六大原则
1、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5、迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
设计模式
装饰模式:在不改变原封装的前提下,为对象动态添加新功能的模式。在 Objective-C 中,它的实现形式为 Category 和 Delegation;在 Swift 中,它的实现形式为 Extension 和 Delegation。
观察者模式:通知、KVO
备忘录模式:保存对象当前的状态,并在日后可以恢复的模式。用 UserDefaults 来读写,同时配合栈可以存储一系列状态。它经常用于初始化、重启、App 前后台状态改变等场景。
工厂模式
单例模式
2.什么是队列?同步异步是什么?GCD与NSOperation的相同与不同,使用场景。
GCD的队列可以分为2大类型
并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效
串行队列(Serial Dispatch Queue)
- 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
同步和异步主要影响:能不能开启新的线程
- 同步:在当前线程中执行任务,不具备开启新线程的能力
- 异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行主要影响:任务的执行方式
- 并发:多个任务并发(同时)执行
- 串行:一个任务执行完毕后,再执行下一个任务
并发队列 | 手动创建的串行队列 | 主队列 | |
---|---|---|---|
同步(sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 |
异步(async) | 有开启新线程,并发执行任务 | 有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 |
使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)
3.Block,本质,指针引用,值引用,变量捕获(static和auto),__weak
__block
1 | NSMutableArray *array = nil; |
有什么问题?
局部变量是值捕获,不能在block内部修改。
1 | NSMutableArray *array = [NSMutableArray array]; |
有什么问题?
没问题,可以添加元素
4.交叉链表找交点
1 | function FindFirstCommonNode(pHead1, pHead2) |
5.性能优化,启动优化
性能优化
- cell复用,header、footer复用。
- 尽量把view设置不透明。
- 减少视图的层级。
- 尽量避免调整视图层次、添加和移除视图。
- 合理选择加载图片的方式,UIImage imageNamed: ImageAssets 用于多个地方重复使用,UIImage imageWithContentsOfFile 一般用在图片数据很大,一般不需要多次使用的情况。
- 图片大小最好和UIImageView的大小相同。
- 不要阻塞主线程,耗时操作放在子线程进行。
- 懒加载。
- 缓存,缓存图片SDWebImage,缓存行高,NSCache缓存。
- 复用高开销对象NSDateFormatter和NSCalendar。
- 减少离屏渲染,优化圆角、阴影。
- 使用正确的数据存储。
启动优化
- 将一些耗时操作延迟执行。比如SDK的初始化,界面的创建。
- 不能延迟执行的,尽量放到后台执行。比如数据读取,原始 JSON 数据转对象,日志发送。
- 减少动态库、合并动态库,定期清理不必要的动态库。
- 减少类、分类的数量,合并Category和功能类似的类。删除不必要的方法和类、分类。
- 将不必须在+load中做的事延时到+initialize中。
6.谈谈https
一个HTTPS请求实际上包含了两次HTTP传输:
1)客户端发起一个http请求,连接到服务器的443端口。
2)服务端把自己的信息以数字证书的形式返回给客户端(证书内容有密钥公钥,网站地址,证书颁发机构,失效日期等)。证书中有一个公钥来加密信息,私钥由服务器持有。
3)验证证书的合法性
客户端收到服务器的响应后会先验证证书的合法性(证书中包含的地址与正在访问的地址是否一致,证书是否过期)。
4)生成随机密码(RSA签名)
如果验证通过,或用户接受了不受信任的证书,浏览器就会生成一个随机的对称密钥(session key)并用公钥加密,让服务端用私钥解密,解密后就用这个对称密钥进行传输了,并且能够说明服务端确实是私钥的持有者。
5)生成对称加密算法
验证完服务端身份后,客户端生成一个对称加密的算法和对应密钥,以公钥加密之后发送给服务端。此时被黑客截获也没用,因为只有服务端的私钥才可以对其进行解密。之后客户端与服务端可以用这个对称加密算法来加密和解密通信内容了。
6)客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。
7)服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
8)然后服务器将加密后的密文发送给客户端。
9)客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。
7.说说响应链
能够响应触摸事件的例如UIView,UIButton,UIViewController,UIApplication,Appdelegate等都继承自UIResponder类,一个页面上通常会有许许多多个这种类型的对象,都可以对点击事件作出响应。为了避免冲突,这就需要有一个先后顺序,也就是响应的优先级。Hit-Testing的目的就是找到具有最高优先级的响应对象。
寻找的具体流程如下:
- UIApplication首先将事件队列中的事件取出,传递给窗口对象。如果有多个窗口,则优先询问windows数组的最后一个窗口。
- 如果窗口不能响应事件,则将事件传递给倒数第二个窗口,以此类推。如果窗口能够响应事件,则再依次询问该窗口的子视图。
- 重复步骤2。
- 若视图的所有子视图均不是最佳响应者,则自身就是最合适的响应者。
另外需要注意的是,一下几种状态的视图无法响应事件:
- 不允许交互的视图:userInteractionEnabled = NO
- 隐藏的视图:hidden = YES
- 透明度alpha<0.01的视图
怎么样验证一下上面所说的Hit-Testing的顺序呢,看一下UIView的API,里面会有一个hitTest:withEvent:方法,这个方法的主要作用就是查询并返回事件在当前视图中的响应者,每个被询问到的视图对象都会调用这个方法来返回当前视图层的响应者。
- 如果当前视图无法响应事件,则返回nil。
- 如果当前视图可以响应事件,但子视图不能响应事件,则返回自身作为当前视图的响应者。
- 如果当前视图可以响应事件,同时有子视图可以响应事件,则返回该子视图作为当前视图的响应者。
8.你知道哪些锁?(自旋锁,互斥锁)使用场景都有哪些?
os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
线程同步