1、TCP连接的三次握手过程?
第一次握手:客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SEND 状态,等待服务器确认;
第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开 TCP 连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)
TCP的四次挥手都做些什么?
拆除两条通道和释放资源
因为TCP的连接是全双工的,所以连接的拆除需要单独将两个通道分别拆除
TCP的四次挥手过程?
第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1, Server进入CLOSED状态,完成四次挥手
3、Scoket连接和HTTP连接的区别?
HTTP连接:短连接,客户端向服务器发送一次请求,服务器响应后连接断开,节省资源。
Socket连接:长连接,客户端跟服务器端直接使用Socket进行连接,没有规定连接后断开,
因此客户端和服务器段保持连接通道,双方可以主动发送数据。
4、HTTPS的加密原理?
服务器端用非对称加密(RSA)生成公钥和私钥
然后把公钥发给客户端, 服务器则保存私钥
客户端拿到公钥后, 会生成一个密钥, 这个密钥就是将来客户端和服务器用来通信的钥匙
然后客户端用公钥对密钥进行加密, 再发给服务器
服务器拿到客户端发来的加密后的密钥后, 再使用私钥解密密钥, 到此双方都获得通信的钥匙
5、对程序性能的优化你有什么建议?
使用复用机制
尽可能设置 View 为不透明
避免臃肿的 XIB 文件
不要阻塞主线程
View 的复用和懒加载机制
图片尺寸匹配 UIImageView
6、介绍下App启动的完成过程?
解析Info.plist
加载相关信息,例如闪屏
沙盒建立、权限检查
Mach-O加载
main函数
执行UIApplicationMain函数
7、编程中的六大设计原则?
1.单一职责原则
通俗地讲就是一个类只做一件事
CALayer:动画和视图的显示。
UIView:只负责事件传递、事件响应。
2.开闭原则
对修改关闭,对扩展开放。 要考虑到后续的扩展性,而不是在原有的基础上来回修改
3.接口隔离原则
使用多个专门的协议、而不是一个庞大臃肿的协议
UITableviewDelegate
UITableViewDataSource
4.依赖倒置原则
抽象不应该依赖于具体实现、具体实现可以依赖于抽象。 调用接口感觉不到内部是如何操作的
5.里氏替换原则
父类可以被子类无缝替换,且原有的功能不受任何影响
例如 KVO
6.迪米特法则
一个对象应当对其他对象尽可能少的了解,实现高聚合、低耦合
8、Runtime:运行时特性
常见的作用:
动态交换两个方法的实现
动态添加属性
实现字典转模型的自动转换
动态添加方法
拦截并替换方法
9、堆和栈的区别?
内存模型分为5个区:栈区、堆区、静态区、常量区、代码区等
一般说的内存泄漏就是堆区的内存
静态区:用static修饰的局部变量或全局变量
常量区:存储的常量,不允许修改
代码区:存放函数体的二进制代码
10、手写单例
+ (instancetype)sharedInstance {
static Singleton *_sharedInstance =nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[super allocWithZone:NULL] init];
});
return_sharedInstance;
}
11、一个上线的项目,知道这个方法可能会出问题,在不破坏改方法前,怎么搞?
利用Runtime,交换方法,通过改变我们原始方法的IMP指向,指向我们要处理正确逻辑的函数实现
12、HTTPS的请求传输过程?
HTTPS在传输的过程中会涉及到三个密钥:
服务器端的公钥和私钥,用来进行非对称加密
客户端生成的随机密钥,用来进行对称加密
一个HTTPS请求实际上包含了两次HTTP传输,可以细分为8步。
1.客户端向服务器发起HTTP请求,连接到服务器的443端口
2.服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。
3.服务器将自己的公钥发送给客户端。
4.客户端收到服务器端的公钥之后,会对公钥进行检查,验证其合法性,如果发现公钥有问题,那么HTTPS传输就无法继续。严格的说,这里应该是验证服务器发送的数字证书的合法性,如果公钥合格,那么客户端会生成一个随机值,这个随机值就是用于进行对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密,这样客户端密钥就变成密文了,至此,HTTPS中的第一次HTTP请求结束。
5.客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。
6.服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
7.然后服务器将加密后的密文发送给客户端。
8.客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。
13、能说下红黑树的原理吗?
红黑树是一种自平衡二叉查找树,每个结点是黑色或红色,根结点是黑色,每个叶子结点(nil)是黑色,如果一个结点是红色的,则它的子结点必须是黑色的
左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,其左子结点保持不变。
右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,其右子结点保持不变。
14、TCP为什么是三次握手而不是两次握手?
为了实现可靠数据传输。
TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。
三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤
如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认
15、NSTimer和CADisplay的区别?
iOS设备屏幕的刷新频率是固定的,CADisplay正常情况下会在每次刷新结束时被调用,精确度相当高
NSTimer的精确度就低一些,比如NSTimer的触发时间到的时候,runloop如果在阻塞状态,触发时间就会推迟到下一个runloop周期
16、不用第三者,怎么交换两个数?
a = a + b;
b = a - b;
a = a - b;
17、算法常见时间复杂度如下图:
18、NSObject的底层实现?
NSObject对象实际占用16个字节(malloc_size函数获得),但NSObject对象内部只使用了8个字节(64位环境下,通过class_getInstanceSize函数获得)
NSObject的本质就是一个结构体,有一个isa成员变量
struct NSObject_IMPL
{
Class isa; // 64位占用8个字节 32位占用4个字节
};
@interface Person : NSObject
{
int_age;
}
@end
以上的一个Person对象占用16字节的内存(按照结构体内存大小对齐原则,结构体的大小是最大成员大小的倍数,最大成员是8而不是int类型的4)
@interface Student : Person
{
int_no;
}
@end
以上的一个Student对象占用16字节的内存(按照结构体内存大小对齐原则,结构体的大小是最大成员大小的倍数)
19、操作系统内存布局,有什么区?
总共5个区
1、栈区(stack): 由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。
2、堆区(heap) :由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在ios 中 alloc 都是存放在堆中。(堆空间分配内存的空间都是16的倍数,最大是256个字节)
3、全局区(静态区) (static): 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后有系统释放。
4、文字常量区: 存放常量字符串,程序结束后由系统释放
5、程序代码区: 存放函数的二进制代码
20、Runloop有几种mode?
系统默认注册了5个mode,
(1)kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
(2)UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
(3)UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
(4)GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
(5)kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
注意iOS 对以上5中model进行了封装
NSDefaultRunLoopMode
NSRunLoopCommonModes
21、所有OC对象都继承NSObject吗?
不是,NSProxy是一个虚类,可以被继承
22、SDWebImage的清除缓存策略?
缓存清理,第一种是内存缓存,第二种是硬盘缓存
[[[SDWebImageManager sharedManager] imageCache] clearMemory];
[[[SDWebImageManager sharedManager] imageCache] clearDisk];
23、MD5和base64的区别?
MD5: 不可逆、消息完整性保护。用于:1、一次性验证。2.安全访问认证。3、数字证书
Base64: 可逆、是一种编码方式,主要作用不是加密,是用来避免‘字节’中不能转换成可显示字符的数值,将二进制数据转换成文本数据,方便使用HTTP协议等。使用场合:表示、传输、储存一些二进制数据。
24、描述下tableview cell的重用机制?
当cell滚出屏幕的时候,会将滚出屏幕的单元格放入重用的queue中,当某个未在屏幕上显示的单元格要显示的时候,就会从这个queue中取出进行重新,通过重用cell可以达到节省内存给的目的
25、UIView的frame和bounds的区别是什么?
frame是参照父视图的坐标系统
bounds是本身的坐标系统。原点是(0,0)
26、如何提高一个应用程序的性能?
1.ARC进行内存管理
2.适当情况下使用reuseIdentity 重用
3.尽可能将view设置为不透明(Opaque)
4.避免臃肿的xib
5.不要阻塞主线程
6.让图片的大小UIimageview一样
7.选择正确的合集-使用适合的类和对象编写代码
8.使用GZip压缩-使用GZip对网络传输中的数据进行压缩,减小文件的大小,并加快下载的速度,压缩对文本的数据特别有用,文本具有很高的压缩比,NSURLConnection默认已经支持GZip压缩
9.重用和延迟加载view
10.缓存
11.考虑绘制
12.处理内存警告
13.重用花销很大的对象
14.使用Sprite Sheets-游戏
15.避免重新处理数据
16.选择正确的数据格式
17.设置适当的背景图片
18.降低web内容的影响
19.设置阴影路径
20.优化tableview
21.选择正确的数据储存方式
22.加速启动时间
23.使用Autorelease pool-临时对象不使用的时候,自动释放
24.缓存图片或不缓存-imageNamed加载图片的时候这个图片是有缓存下来,使用imageWithContentOfFile是没有缓存,如果经常使用到的则缓存下来比较合适25.尽量避免Date格式化
25、不同版本的APP,数据库结构变化了,如何处理?
新建一个新的数据库,旧的数据库数据导出来,按新的数据库的结构存入新的数据库中。
26、苹果推送机制APNs?
苹果利用自己专门的推送服务器(APNs)接收来自我们自己应用服务器的需要被推送的信息,然后推送到指定的iOS设备上,然后由设备通知到我们的应用程序
27、HTTP 2.0介绍下?
HTTP 2.0性能提升的核心是二进制分帧层
HTTP 2.0是二进制协议,它采用二进制格式传输数据而不是1.x的文本格式
HTTP 2.0让所有的通信都在一个TCP连接上完成,真正实现了请求的并发
28、HTTP是哪一层的协议?
应用层协议
29、_objc_msgForward函数是做什么的?
_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发
30、为什么block要使用copy而不是strong?
因为block在创建的时候,它的内存是分配在栈上,而不是堆上,为了在block的声明域外使用,就要把block拷贝的堆,所以为了block的属性声明和实际的操作一致,最好声明为copy。
32、离屏渲染消耗性能的原因?
需要创建新的缓冲区
离屏渲染的过程,需要多次切换上下文环境
33、DNS解析过程?
以www.163.com为例:
客户端打开浏览器,输入一个域名。比如输入www.163.com,这时,客户端会发出一个DNS请求到本地DNS服务器。本地DNS服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动。
查询www.163.com的DNS请求到达本地DNS服务器之后,本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果。如果没有,本地DNS服务器还要向DNS根服务器进行查询。
根DNS服务器没有记录具体的域名和IP地址的对应关系,而是告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址。
本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。
最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。
34、C语言中strlen和sizeof的区别?
sizeof求的是数组整体的大小
strlen求的是字符串的长度
35、分类可以添加:
实例方法,类方法、协议、属性
但不能添加:
成员变量
36、单例可以被销毁吗?
可以
37、Runloop什么时候创建,什么时候销毁?
创建发生在第一次获取时,销毁发生在线程结束时
38、OC的对象、类主要是基于C\C++的什么数据结构实现的?
结构体
39、对象方法放在类对象的方法列表里面,没有放在实例对象里面,实例对象只放特有的一些东西,例如:成员变量等。
40、创建一个实例对象,至少需要多少内存?
导入:#import<objc/runtime.h>
调用的方法:class_getInstanceSize([NSObject class])
41、创建一个实例对象,实际上分配了多少内存?
导入:#import<malloc/malloc.h>
调用的方法:malloc_size((_bridge const void *)obj)
注意:以下占用内存是由系统分配的,系统分配的内存是16的倍数,最大为256个字节,所以,以下应为32个字节
42、KVO的本质是什么?
利用runtime动态生成一个子类,并且让实例对象的isa指向子类,当修改实例对象的属性时,会调用Foundation的NSSetXXXValueAndNotify函数,这个函数里面会实现以下内容:
willChangeValueForKey:
父类原来的setter方法
didChangeValueForKey:(内部会触发observer的监听方法:observerValueForKeyPath:ofObject:change:context)
43、直接修改成员变量会触发KVO吗?
不会。
因为只有重写了set方法才会去触发KVO,而直接修改成员变量不会重写set方法,就不会触发KVO。
44、通过KVC修改属性会触发KVO吗?
会触发
45、KVC的赋值和取值过程是怎样的?原理是什么?
赋值过程:先按照setKey:和_setKey:的顺序去查找方法,如果找到就直接赋值;如果没找到,就去查找accessInstanceVariablesDirectly方法的返回值,如果返回YES,就按照_key、_isKey、key、isKey的顺序去查找成员变量,如果找到了就直接赋值;如果成员变量都没找到就报错:setValue:forUnderfinedKey。
取值过程:先按照getKey、key、isKey、_key的顺序去查找方法,如果找到了就直接调用方法,如果没有找到,就去查找accessInstanceVariablesDirectly方法的返回值,如果返回YES,就按照_key、_isKey、key、isKey的顺序去查找成员变量,如果找到了就直接赋值;如果成员变量都没找到就报错:setValue:forUnderfinedKey。
分类的方法是通过runtime动态合并到类对象和元类对象中的。
所有分类在编译的时候,和类是分开的,都会生成如下的_category_t结构体,内部结构一样,但里面存储的数据不一样:
分类运行时的操作如下:
多个不同的Category中有相同的方法,那么最终执行的方法取决于编译的先后顺序,最后编译Category的方法会被执行。
Category的加载处理过程?
1、通过Runtime加载某个类的所有Category数据
2、把所有Category的方法、属性、协议数据,合并到一个大数组中,后面参与编译的Category数据,会在数组的前面
3、将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
Category的实现原理是什么?
Category编译之后的底层结构是struct category_t结构体,里面存储着分类的对象方法、类方法、属性、协议信息;在运行的时候,runtime会将Category的数据,合并到类信息中(类对象和元类对象中)
分类和类扩展的区别?
类扩展在编译的时候,它的数据就已经包含在类信息中。
分类是在运行时,才会将数据合并到类信息中。
+load方法会在runtime加载类、分类时调用。
每个类、分类的+load方法,在程序运行过程中只调用一次。
+load调用顺序:
1、先调用父类的+load方法,再调用子类的+load方法,最后调用分类的+load方法
2、分类的+load方法按照编译的先后顺序调用,先编译的先调用
分类中有load方法吗?load方法是什么时候调用的?load方法能继承吗?
有。
load方法在Runtime加载类、分类时调用。
load方法能继承。
子类调用父类的load方法过程?
先去父类的分类中查找,如果父类的分类没有;再去父类中查找。
initialize在类第一次接收到消息的时候调用,先调用父类的initialize(前提是父类之前没被初始化,如果初始化过,就不会被调用),再调用子类的initialize。
+load方法和+initialize方法的区别?
调用方式:
1、load是根据函数地址直接调用
2、initialize是通过objc_msgSend调用
调用时刻:
1、load是runtime加载类、分类的时候调用(只会调用一次)
2、initialize是第一次接收到消息的时候调用,每一个类只会调用一次(但是父类的initialize方法可能会调用多次)
+load方法和+initialize方法的调用顺序?
load:
1、先调用类的load:
a、先编译的类,优先调用load
b、调用子类的load之前,会先调用父类的load
2、再调用分类的load:
先编译的分类,优先调用load
initialize:
1、先初始化父类
2、再初始化子类
如果子类没有实现+initialize,会调用父类的+initialize,所以父类的+initialize可能会被调用多次。
如果分类实现了+initialize,就会覆盖类本身的+initialize调用。
类中的属性会帮助做三件事情?
1、生成成员变量
2、生成set和get方法的声明
3、生成set和get方法的实现
分类中的属性会帮助做一件事情?
只会生成set和get方法的声明
分类能否添加成员变量?如果可以,如何给分类添加成员变量?
不能。
如果可以的话,通过如下关联对象的方法来实现:
关联对象的底层原理图如下:
关联对象并不是存储在被关联对象本身内存中,存储在全局的统一的一个AssociationsManager中。
block捕获?
局部变量会捕获
全局变量不会捕获
变量默认都是auto类型的
以下block会对self进行捕获,因为oc中的test方法实际上是转成c语言的代码,self实际上是当做入参,而入参相当于局部变量,所以也会被block捕获。
以下访问name也会被捕获,因为self被当做局部变量捕获了,所以self里面的属性自然也会被捕获。
block本质?
block是将函数及其执行上下文封装起来的对象,内部也有个isa指针。
block有以下三种类型,最终都继承自NSBlock:
_NSGlobalBlock_
_NSStackBlock_
_NSMallocBlock_
数据区:存放全局变量、类对象
栈区:存放局部变量
堆区:存放实例对象
注意:定义的宏不是全局变量。
以下Person对象将在触发点击事件三秒后才销毁,因为block对象在堆上,会对Person进行强引用,只有当block销毁后,Person对象才会销毁
以下Person对象将在触发点击事件后立即销毁,因为Person对象为弱引用
如果要在block中修改变量,可以将变量变为static类型或全局变量,但是这样都不太好,会一直占用内存
可以在变量前面加上__block进行修饰
__block修饰符
对象类型的auto变量、__block变量
以下为被__block修饰的对象类型:
__weak和__unsafe_unretained的区别?
__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
ARC下解决循环引用的问题,有如下三种方案:
但第三种方案的不好之处是,需要在用到的时候才调用self.block(),如果不调用的话,就会发生内存泄漏。
MRC下解决循环引用的问题,有如下两种方案:
Runtime:
[super message]的底层实现:
消息接收者仍然是子类对象,只不过是从父类的方法列表去查找方法。
class的底层实现,是返回消息接收者
- (Class)class{
return object_getClass(self);
}
superclass的底层实现,是返回消息接收者的父类
- (Class)superclass {
return class_getSuperClass(object_getClass(self));
}
NSObject的元类对象调用superClass,就是[NSObject class],所以打印结果如下:
以下代码能执行成功:
查找内存对应地址存放的数据类型
利用runtime动态交换方法:
利用runtime动态交换方法的实现:
什么是Runtime?项目中有用到过Runtime吗?
Runtime是一套C语言的API,封装了很多动态性相关的函数,OC的动态性就是由Runtime来支撑和实现的。
具体应用:
1、利用关联对象(AssociatedObject)给分类添加成员变量
2、遍历所有成员变量(修改textField的占位文字颜色、字典转模型、自动归档、解档)
3、交换方法实现(交换系统的方法)
4、利用消息转发机制解决方法找不到的问题
assign、retain、copy、strong
assign:生成的set方法直接赋值
retain:生成的set方法里面会将之前的值release掉,新传进来的值进行一次retain操作
copy:生成的set方法里面会将之前的值release掉,新传进来的值进行一次copy操作
引用计数怎么存储?
直接存储在isa指针里面,如果不够存储的话,会存储在SideTable里面的refcnts
weak主要存在散列表的弱引用表里面-》entry-》数组-》弱引用对象里面
1、autoreleasepool最终是由autoreleasePoolPage来管理的
autoreleasePoolPage对象占用4096个字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址。
2、所有的autoreleasePoolPage都是通过双向链表的形式连接在一起
3、POOL_Boundary:哨兵对象
执行AutoreleasePoolPush方法时,会往栈中插入一个POOL_Boundary对象,
再添加autorelease对象地址
执行AutoreleasePoolPop方法时,会移除autorelease对象,直到前一个POOL_Boundary对象停止
objc4源码下载地址:open.source.apple
GCD源码源码:https://github.com/apple/swift-corelibs-libdispatch
GNUstep是GNU计划的项目之一,它将Cocoa的OC库重新开源实现了一遍
源码地址:http://www.gnustep.org/resources/downloads.php
下载好后,打开base项目
objc4
1、
2、
3、dispatch_sync:立马在当前线程执行任务,执行完毕才能继续往下执行
下图会产生死锁,原因是:
53行代码的dispatch_sync往主队列中添加了一个任务,需要马上执行,而主队列中正在执行viewDidLoad方法这个任务,viewDidLoad不执行完的话,就没法执行dispatch_sync中的任务;但是dispatch_sync中的任务不执行完,又没法执行完viewDidLoad的任务,这样就发生了死锁。
4、dispatch_async:不要求在当前线程立马执行任务
下面不会产生死锁
5、下图会产生死锁
原因是:
54行dispatch_async中的任务和56行dispatch_sync中的任务发生了死锁。
6、下图不会产生死锁
7、下图不会产生死锁
8、下图不会产生死锁
9、使用dispatch_sync往当前的串行队列中添加任务会产生死锁。
10、OSSpinLock:自旋锁(os_unfair_lock用于取代不安全的OSSpinLock,iOS10开始才支持),忙等锁,会出现优先级反转的问题,导致锁无法释放。
os_unfair_lock:会处于休眠状态,并非忙等
pthread_mutex_t:互斥锁,等待锁的线程会处于休眠状态
NSRecursiveLock:递归锁,是对mutex递归锁的封装
NSLock:对象锁,是对mutex普通锁的封装
NSConditionLock:条件锁
dispatch_semaphore_t:信号量,信号量的初始值,用来控制线程并发访问的最大数量
@synchronized:是对mutex递归锁的封装
自旋锁和互斥锁的比较:
什么情况下使用自旋锁比较划算?
预计线程等待锁的时间比较短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器
什么情况下使用互斥锁比较划算?
预计线程等待锁的时间比较长
单核处理器
加锁的代码(临界区)有IO(文件的读写)操作
临界区代码复杂或者循环量大
atomic:用于保证setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁(可以参考objc4的objc_accessors.mm),比较耗性能,一般用在Mac开发上
如何实现文件的多读单写操作?
同一时间,只能有一个线程进行写的操作
同一时间,允许有多个线程进行读的操作
同一时间,不允许既有读的操作,又有写的操作
实现方案:
pthread_rwlock_t:读写锁,等待锁的线程会进入休眠
dispatch_barrier_async:异步栅栏调用
pthread_rwlock_t实现的多读单写方案:
dispatch_barrier_async实现的多读单写方案:
RunLoop相关
什么是Runloop?
即运行循环,是在程序运行中循环做一些事情。
1、NSRunLoop是基于CFRunLoopRef的一层OC包装
CFRunLoopRef是开源的:https://opensource.apple.com/tarballs/CF/
2、NSRunLoop常见的模式和状态?
两种Mode:
kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,主线程是在这个Mode下运行
UITrackingRunLoopMode:界面追踪Mode,用于ScrollerView追踪触摸滑动,保证接麦你滑动时不受其他Mode影响。
状态:
kCFRunLoopEntry //即将进入loop
kCFRunLoopBeforeTimers //即将处理Timer
kCFRunLoopBeforeSources //即将处理Source
kCFRunLoopBeforeWaiting //即将进入休眠
kCFRunLoopAfterWaiting //刚从休眠中唤醒
kCFRunLoopExit //即将退出loop
3、如果切换Mode,只能退出当前runloop,再重新选择一个Mode进入,不会导致程序退出。
4、Source0事件:
触摸事件处理
performSelector:onThread
5、Source1事件:
基于Port的线程间通信
系统事件捕捉(事件通过Source1捕捉,Source0来处理)
6、Timers:
NSTimer
performSelector: withObject: afterDelay:
7、Observers:
用于监听RunLoop的状态
UI刷新(beforeWaiting):在线程即将睡眠之前来刷新UI(例如 :self.view.backgroundColor = [UIColor redColor])
Autorelease pool(beforeWaiting)
8、kCFRunLoopCommonModes
kCFRunLoopCommonModes默认包括:kCFRunLoopDefaultMode和UITrackingRunLoopMode
9、监听RunLoop的状态
10、runloop的运行逻辑?
1、通知监听者即将进入loop
2、通知监听者即将处理Timers
3、通知监听者即将处理Sources
4、处理Blocks
5、处理Source0(可能再次处理Blocks)
6、如果存在Source1,跳转到第8步
7、没有Source1,通知监听者开始休眠,等待消息唤醒
8、通知监听者,结束休眠(被某个消息唤醒)
11、runloop休眠的本质?
用户态到内核态的切换(通过调用内核态的API:mach_msg())
12、runloop响应事件的流程?
先由Source1对事件进行捕捉,包装到事件队列里面,事件队列再由Source0进行处理。
13、Timer与Runloop的关系?
Runloop里面放着一堆模式,模式里面可能会含有timers
14、程序中添加每一秒响应一次的NSTimer,当拖动UITableView时NSTimer可能无法响应的解决方案?
创建一个NSTimer,将它添加到NSRunLoopCommonModes模式下即可,因为NSRunLoopCommonModes包含了NSDefaultRunLoopMode和UITrackingRunLoopMode两种模式。
15、Runloop在实际开发中的应用?
控制线程的生命周期(线程保活)
解决NSTimer在滑动时停止工作的问题
监控应用卡顿
性能优化
16、Runloop中mode的作用是什么?
为了把不同模式下的Timer、Sources、Obsrvers隔离开来,保证相互之间互不干扰。
17、
18、
19、主线程的runloop注册了两个observer:
第一个observer监听kCFRunLoopEntry事件,会调用objc_autoReleasePoolPagePush()
第二个observer监听kCFRunLoopBeforeWaiting事件,会调用objc_autoReleasePoolPagePush()和objc_autoReleasePoolPagePop()
20、什么时候会调用release?
在当次runloop休眠之前调用。
1、
实例对象的isa指针指向类对象
类对象的isa指针指向元类对象
元类对象的isa指针指向基类的元类对象
类对象的superclass指针指向父类,如果没有父类,则superclass指针为nil(NSObject没有父类,所以他的superclass指针为nil)
元类对象的superclass指针指向父类,基类元类对象的superclass指针指向基类的类对象
下面虚线为isa指针,实线为superclass指针:
2、实例对象如何调用对象方法的轨迹?
实例对象通过isa指针找到类对象,如果类对象有就直接调用类对象中的对象方法;如果没有,再通过superclass指针去父类的类对象中查找,如果一直到基类的类对象还没找到的话,就提示找不到方法的异常。
3、类对象如何调用类方法的轨迹?
类对象通过isa指针找到元类对象,如果元类对象有就直接调用元类对象的类方法;如果没有,再通过superclass指针去父类的元类对象中查找,如果一直到基类的元类对象还没找到的话,就去基类的类对象中查找,如果还是没找到的话,就提示找不到方法的异常。
4、窥探struct objc_class的结构:
一开始类中所有东西最初是放在class_ro_t里面,当程序运行起来,会将分类的东西和类原来的东西一起再放到了class_rw_t里面,相当于class_rw_t里面有一部分东西来源于class_ro_t。
cache_t是通过散列表来缓存方法的
方法缓存cache_t是采用空间换时间,牺牲内存空间来快速获取方法:
交换方法如果调用的是class_rw_t的话,实质上是交换class_rw_t里面methods的method_t的imp。
交换方法如果调用的是cache_t的话,实质上是清空缓存,重新再来一遍。
1、使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
无论在MRC下还是ARC下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放。
2、
cocoapods的dummy.m文件的作用?
为了解决如果当前 Pod 库中只有 Category 导致链接不上的原因
以main为分界,load方法在main函数之前执行,initialize在main函数之后执行
AFN的加密策略?
iOS中动画的类型:
1、基本动画
2、关键帧动画
3、组动画
4、转场动画
atomic 的底层实现,老版本是自旋锁,新版本是互斥锁。
atomic并不是绝对线程安全,它能保证代码进入getter和setter方法的时候是安全的,但是并不能保证多线程的访问情况下是安全的,一旦出了getter和setter方法,其线程安全就要由程序员自己来把握,所以atomic属性和线程安全并没有必然联系。
__weak 修饰的变量在地址被释放后,为何被置为 nil?
在Runtime中专门维护了一个用于存储weak指针变量的weak表,这实际上是一个Hash表。这个表key是weak指针所指向的内存地址,value是指向这个内存地址的所有weak指针,实际上是一个数组。
过程可以总结为3步
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用objc_storeWeak()函数,objc_storeWeak()的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
GCD异步回到主队列也是由Runloop处理的
DNS劫持是啥?如何解决DNS劫持?
DNS劫持是篡改解析结果,返回一个更改后的IP地址
栈和堆是公有的还是私有的?
堆在一起的东西,肯定是公用(公有)的,你占(栈)有的东西,肯定是你自己私有的。
使用KVO会出现什么问题?
没有添加监听的情况下,移除观察者的话,会导致崩溃
或者是重复移除观察者也会导致崩溃
如何解决KVO的这种问题:通过Runtime的方法交换实现
TCP/IP中的IP是啥?
互联网传输协议
APNS中客户端和苹果服务器之间是长链接
1、如何copy一个类?
遵守NSCopying协议,实现copyWithZone方法
2、子类是否可以直接调用父类的分类方法?
可以
3、NSUserdefaults和SQLite的优缺点?
NSUserdefaults一般用于属性的存储,轻量级
SQLite一般用于大量数据测存储
5、KVO,NSNotification,delegate及block区别?
delegate是代理,一对一的关系
block是delegate的另一种形式,是函数式编程的一种形式,相比delegate更灵活
NSNotification和KVO都是一对多,KVO一般监听属性的变化
NSNotification不局限于属性的变化,可以对多种多样的状态变化进行监控,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应。
6、Flutter原理?
Flutter框架分三层
Framework,Engine, Embedder(嵌入层)
Framework使用dart语言实现,包括UI,文本,图片,按钮等Widgets,渲染,动画,手势等。此部分的核心代码是flutter仓库下的flutter package,以及sky_engine仓库下的 io, async, ui(dart:ui库提供了Flutter框架和引擎之间的接口)等package。
Engine使用C++实现,主要包括:Skia, Dart 和 Text。
Embedder是一个嵌入层,通过该层把Flutter嵌入到各个平台上去
UI线程使用Dart来构建视图结构,视图结构在GPU线程进行图层合成,然后再由Skia引擎渲染为GPU数据,最终经由OpenGL提供给GPU。
Skia是一个跨平台的2D绘图引擎库,可以被嵌入到Flutter的iOS SDK库中,Android自带了Skia,所以 Flutter Android SDK要比 iOS SDK小很多。
7、swift相对于OC的优点
swift更简洁
swift具备函数式编程、泛型等新特性
8、swift相对于OC的缺点
swift的包体积比OC大
9、类和结构体的区别
类可以被继承,结构体不可以
类有引用计数,允许对象被多次引用
10、KVO原理?
利用Runtime生成一个NSKVONotify_A的子类,实例对象的isa指针指向这个子类,当修改实例对象的属性时,会去调用NSKVONotify_A的set方法,里面会调用NSSetIntValueAndNotify方法,然后再调用willChangeValueForKey,父类的set方法,didChangeValueForKey,didChangeValueForKey方法里面再调用observeValueForKeyPath方法,通知监听器属性发生了改变。
分类是后编译的,先调用
load和initialize的区别?
调用方式:
load是根据函数地址直接调用
initialize是通过objc_msgSend调用
调用时刻:
load是Runtime加载类和分类的时候调用(只会调用一次)
initialize是第一次接收到消息时调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
load和initialize的调用顺序?
load:
A、先调用类的load:
先编译的类,先调用
先调用父类的,再调用子类的
B、再调用分类的load:
先编译的分类,先调用
initialize:
先初始化父类
再初始化子类(可能最终调用的是父类的initialize方法)
UILabel要想显示在界面上至少需要几个约束?
两个
UIView要想显示在界面上至少需要几个约束?
四个
让UIView只用两个约束就能显示在屏幕上,如何做?
自定义UIView,重写intrinsicContentSize方法即可
如何画一个三角形的UIView?
用Core Graphics或者UIBezierPath