美文网首页iOS
面试相关(二)

面试相关(二)

作者: 异乡人_4f2a | 来源:发表于2020-10-12 10:09 被阅读0次

    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

    相关文章

      网友评论

        本文标题:面试相关(二)

        本文链接:https://www.haomeiwen.com/subject/skztxhtx.html