面试相关(二)

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

调用的方法:class_getInstanceSize([NSObject class])


41、创建一个实例对象,实际上分配了多少内存?

导入:#import

调用的方法: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

你可能感兴趣的:(面试相关(二))