面试题

2022面试题

1: 怎么保证自己的类一定能调用到自己写的方法?

Category 并不会覆盖主类的同名方法,只是Category的方法排在主类方法的前面,OC的消息发送机制是根据方法名在method_ list 中查找方法,找到第一个名字匹配的方法之后就不继续往下找 了,每次调用的都是method. _list 中最前面的同名方法。所以我们可以根据selector查找到这个类的所有同名method,然后倒序调用,因为主类的同名方法在最后面。

2: isa指针里面储存了哪些信息?

nonpointer:(0)。为0表示这个isa只存储了地址值,为1表示这是一个优化过的isa。

has_assoc:(1)。记录这个对象是否是关联对象,没有的话,释放更快。

has_cxx_dtor:(2)。记录是否有c++的析构函数,没有的话,释放更快。

shiftcls:(isa的第3-35位,共占33位)。记录类对象或元类对象的地址值。

magic:(isa的第36-41位,共占6位),用于在调试时分辨对象是否完成初始化。

weakly_referenced:(42),用于记录该对象是否被弱引用或曾经被弱引用过,没有被弱引用过的对象可以更快释放。

deallocating:(43),标志对象是否正在释放内存。

has_sidetable_rc:(44),用于标记是否有扩展的引用计数。当一个对象的引用计数比较少时,其引用计数就记录在isa中,当引用计数大于某个值时就会采用sideTable来协助存储引用计数。

extra_rc:(isa的第45-63位,共占19位),用来记录该对象的引用计数值-1(比如引用计数是5的话这里记录的就是4)。这里总共是19位,如果引用计数很大,19位存不下的话就会采用sideTable来协助存储,规则如下:当19位存满时,会将19位的一半(也就是上面定义的RC_HALF)存入sideTable中,如果此时引用计数又+1,那么是加在extra_rc上,当extra_rc又存满时,继续拿出RC_HALF的大小放入sideTable。当引用计数减少时,如果extra_rc的值减少到了0,那就从sideTable中取出RC_HALF大小放入extra_rc中。综上所述,引用计数不管是增加还是减少都是在extra_rc上进行的,而不会直接去操作sideTable,这是因为sideTable中有个自旋锁,而引用计数的增加和减少操作是非常频繁的,如果直接去操作sideTable会非常影响性能,所以这样设计来尽量减少对sideTable的访问。

3: 为什么block要用copy修饰?

1.NSConcreteGlobalBlock 全局的静态block,不会访问外部的变量。就是说如果你的block没有调用其他的外部变量,那你的block类型就是这种。例如:你仅仅在你的block里面写一个NSLog(“hello world”);

2.NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。这个block就是你声明的时候不用copy修饰,并且你的block访问了外部变量。

3.NSConcreteMallocBlock 保存在堆中的block,当引用计数为 0 时会被销毁。好了,这个就是今天的主角 ,用copy修饰的block。

我们知道,函数的声明周期是随着函数调用的结束就终止了。我们的block是写在函数中的。

如果是全局静态block的话,他直到程序结束的时候,才会被被释放。但是我们实际操作中基本上不会使用到不访问外部变量的block。

如果是保存在栈中的block,他会随着函数调用结束被销毁。从而导致我们在执行一个包含block的函数之后,就无法再访问这个block。因为(函数结束,函数栈就销毁了,存在函数里面的block也就没有了),我们再使用block时,就会产生空指针异常。

如果是堆中的block,也就是copy修饰的block。他的生命周期就是随着对象的销毁而结束的。只要对象不销毁,我们就可以调用的到在堆中的block。

这就是为什么我们要用copy来修饰block。因为不用copy修饰的访问外部变量的block,只在他所在的函数被调用的那一瞬间可以使用。之后就消失了。

使用retain也可以,但是block的retain行为默认是用copy的行为实现的,

因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。

4: 什么是离屏渲染以及离屏渲染的影响?如何手动触发离屏渲染及离屏渲染使用建议

见https://www.jianshu.com/p/64cdcd52369e

离屏渲染:实现一些特殊效果例如圆角、阴影和遮罩、抗锯齿、高斯模糊、半透明图层混合等会触发离屏渲染。,图层属性的混合体被指定为在未预合成之前(下一个 VSync 信号开始前)不能直接在屏幕中绘制,所以就需要屏幕外渲染被唤起。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论 CPU 还是 GPU)。

影响:

1、如上图所示相比于正常的渲染流程,离屏渲染需要额外创建一个离屏缓冲区,需要 多耗费一些空间;

2、触发离屏渲染后,需要先从 Frame Buffer 切换到 Off-Screen Buffer ,渲染完毕后再切换回 Frame Buffer,这一过程需是比较 耗费性能 的,因为要来回切换上下文;

3、数据由 Off-Screen Buffer 取出,再存入 Frame Buffer 也需要 耗费时间 ,这样增加了 掉帧 的可能性;由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。

4、 离屏缓冲区存在 空间限制 ,即屏幕像素的 2.5倍,当大于这一值时便不会触发离屏渲染。(有待扩展相关知识面,当需要离屏渲染时又超出离屏渲染的空间限制后,对应超出部分会产生什么问题?)

如何手动触发离屏渲染及离屏渲染使用建议:

重点说说光栅化,当设置view.layer.shouldRasterize 为 true时,也会触发离屏渲染。

光栅化概念:将图转化为一个个栅格组成的图象。光栅化特点:每个元素对应帧缓冲区中的一像素。shouldRasterize = YES 在其他属性触发离屏渲染的同时,会将光栅化后的内容缓存起来,如果对应的 layer 及其 sublayers 没有发生改变,在下一帧的时候可以直接复用。shouldRasterize = YES 这将隐式的创建一个位图,各种阴影遮罩等效果也会保存到位图中并缓存起来,从而减少渲染的频度。相当于光栅化是把 GPU的操作转到 CPU 上了,生成位图缓存,直接读取复用。

如果缓存的图像在之后用不到或很少用到( 100ms内用不到 ),则不需要开启shouldRasterize

如果缓存的图像会经常发生变动,比如本身处于动画中,或者像tabeleView的cell的上图片可能经常改变,则不要开启shouldRasterize

缓存的图像过大,超过屏幕像素的 2.5 倍,不会触发离屏渲染,所以开启shouldRasterize也没有效果.

总结

1、iOS图形渲染流程分为 正常渲染流程 和 离屏渲染流程 ;

2、离屏渲染是在帧缓冲区之外开辟了一个临时的缓冲区,用于保存一些暂时没有用到的数据,之后会从离屏缓冲区取出,渲染后再放入帧缓冲区;

3、离屏渲染会有一定的性能问题,但是我们依然会有使用到的地方;

4、离屏缓冲区最大为 屏幕像素的2.5倍 ,超出不会触发离屏渲染;

5、设置圆角不一定会触发离屏渲染,但是如果有 多个图层 ,则会触发离屏渲染。

iOS9之前UIButton、UIImageView圆角触发离屏渲染,iOS9之后UIButton圆角触发离屏渲染

5: 说说你对ro、rw和rwe的理解?

clean memory:是指加载后不会发生改变的内存。class_ro_t 就属于clean memory, 因为它是只读的(ro 代表 readonly)。

dirty memory:是指在进程运行时会发生更改的内存,class_rw_t 是 dirty memory(rw 代表 read write)。

而dirty memory要比clean memory更加昂贵,前者一经使用就必须存在,可以通过runtime往类里写入新的数据,所以dirty memory是动态的,而clean memory是经过编译就不可以再写入的,大小是固定的,是可移除的,可以节省更多的内存空间,并且iOS是不会使用swap进行转换,所以dirty memory在iOS的开销更大一些,也就是所谓的更加"昂贵"。

ro:数据是只读的,它属于clean Memory。它是从沙盒读取,ro的数据在编译的时候就已经确定了。

rw:数据是可读可写的,它属于dirty Memory。rw的数据存放的是运行时动态修改的数据。

rwe:是苹果为rw做的优化,从rw拆出来那些平时不常用的部分,以减少rw的开销,大约 90% 的类从来不需要这些扩展数据,这在系统范围内可节省大约14MB 的内存。

6: 苹果为什么要设计元类?

7: 原子属性能保障线程安全吗?为什么?

属性声明为atomic时,

在该属性在调用getter和setter方法时,会加上同步锁,

即在属性在调用getter和setter方法时,保证同一时刻只能有一个线程调用属性的读/写方法。

保证了读和写的过程是可靠的。

但并不能保证数据一定是可靠的。

线程1setterAAA

线程2setterAAA

线程1aaa

8: 如何手动关闭 KVO?如何手动触发 KVO?KVO的实现原理?

KVO原理:当观察一个对象时,runtime会动态创建继承自该对象的类NSKVONotifying_类名,并重写被观察对象的setter方法,重写的setter方法,会负责在调用原setter方法前后通知所有观察对象值的更改,最后会把该对象的isa指针指向这个创建的子类,对象就变成子类的实例。

automaticallyNotifiesObserversForKey方法可以控制是否关闭属性KVO

如何手动触发KVO:在setter方法里,手动实现NSObject两个方法:willChangeValueForKey、didChangeValueForKey

setValueForKey会导致KVO触发

_属性名=值;不会触发

9: 用一句话描述GCD发生的死锁现象?

主要的死锁就是当前串行队列里面同步执行当前串行队列。解决的方法就是将同步的串行队列放到另外一个线程执行。

最常用的例子,同步主线程,同步串行队列嵌套自己

主队列dispatch_get_main_queue串行队列主线中执行

全局队列dispatch_get_global_queue并发队列子线程中执行

用户队列dispatch_queue_create串并都可以子线程中执行

dispatch_queue_t queue = dispatch_queue_create("aaa", DISPATCH_QUEUE_CONCURRENT);

//DISPATCH_QUEUE_CONCURRENT是并行队列

//DISPATCH_QUEUE_SERIAL是串行队列

dispatch_sync(queue, ^{//此处无所谓同步或者异步

NSLog(@"-------->%@", [NSThread currentThread]);

dispatch_sync(queue, ^{//此处必须异步才可以,同步会导致崩溃

                 NSLog(@"-------->%@", [NSThread currentThread]);

});

});

10: 单例的弊端?

优点:

1:一个类只被实例化一次,提供了对唯一实例的受控访问。

2:节省系统资源

3:允许可变数目的实例。

缺点:

1:一个类只有一个对象,可能造成责任过重,在一定程度上违背了“单一职责原则”。

2:由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

3:滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

11: load()和initialize()的区别?

父类load

本类load

父类扩展-load

本类扩展-load

父类扩展-initialize

本类扩展-initialize

load->父类->本类->父类扩展->本类扩展

initialize->父类扩展或父类->本类扩展或本类

12: 简述APP main()函数执行前的启动流程?(pass)

     

13: runtime 如何通过 selector 找到对应的 IMP 地址?

每一个类对象中都一个对象方法列表(对象方法缓存)

类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)

方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.

当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找

当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找

14: 简述你对self和super的理解?

self是什么答

  1> self 是 OC 提供保留字;

  2> self 代表着当前方法的调用者;

  3> 在 - 方法中,self代表着"对象";

  4> 在 + 方法中,self代表着"类";

  5> self 是方法的隐藏的参数变量,指向当前调用方法的对象,另一个隐藏参数是 _cmd,代表当前类方法的

      selector;

super是什么答

  1> super 是 OC 提供保留字;

  2> super 不是隐藏的参数,它只是一个"编译器指示符"。查找方法时,指定方法查找的位置在父类;

[super init]到底做了什么答

  1> 递归初始化父类对象,直到root对象;

为什么把[super init]的地址赋值给self答

     1> 整个对象过程中只有一个对象 self ,不存在父类对象的指针;

     2> 对象内部不管是 self 还是 super 其消息主体只有一个就是 self ,也就是说 self 和 super 指向的是同一个

     对象;

     3> 在父类初始化失败的时候,返回nil,终止操作。

为什么输出都是子类答:

  1> 参见 答4 ,结果显而易见,self 和 super是同一个实体。

15: 简述一下dealloc的实现机制?

Dealloc的实现机制是内容管理部分的重点,把这个知识点弄明白,对于全方位的理解内存管理的知识很有必要。

1.Dealloc 调用流程

首先调用_objc_rootDealloc()

接下来调用rootDealloc()

这时候会判断是否可以被释放,判断的依据主要有 5 个,判断是否有以上五种情况

NONPointer_ISA

weakly_reference

has_assoc

has_cxx_dtor

has_sidetable_rc

如果有以上五中任意一种,将会调用object_dispose()方法,做下一步的处理

如果没有之前五种情况的任意一种,则可以执行释放操作,C 函数的 free()

执行完毕

2.object_dispose() 调用流程

直接调用objc_destructInstance()

之后调用 C 函数的 free()

3. objc_destructInstance()调用流程

先判断hasCxxDtor,如果有 C++ 的相关内容,要调用object_cxxDestruct(),销毁 C++ 相关的内容

再判断hasAssocitatedObjects,如果有的话,要调用object_remove_associations(), 销毁关联对象的一系列操作

然后调用clearDeallocating()

执行完毕

4.clearDeallocating()调用流程

先执行sideTable_clearDellocating()

再执行weak_clear_no_lock,在这一步骤中,会将指向该对象的弱引用指针置为nil

接下来执行table.refcnts.eraser(),从引用计数表中擦除该对象的引用计数

16: 类簇的优缺点

类簇是Foundation框架中广泛使用的设计模式。类簇在公共抽象超类下对多个私有的具体子类进行分组。以这种方式对类进行分组简化了面向对象框架的公共可见体系结构,而不会降低其功能丰富度。类簇是基于抽象工厂设计模式的。

优点:

可以将抽象基类背后的复杂细节隐藏起来。

程序员不会需要记住各种创建对象的具体类实现,简化了开发成本,提高了开发效率。

便于进行封装和组件化。

减少了 if-else 这样缺乏扩展性的代码。

增加新功能支持不影响其他代码。

缺点:

已有的类簇非常不好扩展。

17: NSOperation 与 GCD 的主要区别?

1、GCD是一种轻量级的方法来实现多线程。控制起来比较麻烦,比如取消和暂停一个线程。

2、NSOperation和NSOperationQueue相对于GCD效率上要低一点,他们是面向对象的方式,NSOperation底层也是用的GCD来实现的。可以在多个操作中添加附属,也可以重用操作,取消或者暂停。NSOperation和KVO是兼容,也就是说,可以在NSOperation中使用KVO,例如,你可以通过NSNotificationCenter去让一个操作开始执行。

使用:

项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用,封装),使得实现是多线程支持,而接口简单,建议在复杂的项目中使用。

项目中使用GCD的优点是GCD本身比NSOperation简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会使代码更为易读,建议在简单的项目中使用

18: 介绍下App启动的完整过程?

(1) 系统为程序启动做好准备。

(2) 系统将控制权交给 Dyld,Dyld 会负责后续的⼯作。

(3) Dyld 加载程序所需的动态库。

(3) Dyld 对程序进⾏ rebase 以及 bind 操作。

(4) Objc SetUp。

(5) 运⾏初始化函数。

(6) 执⾏程序的 main 函数。

19: 线程是如何切换的

20: 三次握手与四次挥手

(1)首先客户端向服务器端发送一段TCP报文,其中:

标记位为SYN,表示“请求建立新连接”;

序号为Seq=X(X一般为1);

随后客户端进入SYN-SENT阶段。

(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文,其中:

标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);

序号为Seq=y;

确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。

(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中:

标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);

序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;

确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;

随后客户端进入ESTABLISHED阶段。

服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。

在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。

此后客户端和服务器端进行正常的数据传输。这就是“三次握手”的过程。

(1)首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:

标记位为FIN,表示“请求释放连接“;

序号为Seq=U;

随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。

注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。

(2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:

标记位为ACK,表示“接收到客户端发送的释放连接的请求”;

序号为Seq=V;

确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;

随后服务器端开始准备释放服务器端到客户端方向上的连接。

客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段

前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了

(3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:

标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。

序号为Seq=W;

确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。

随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。

(4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:

标记位为ACK,表示“接收到服务器准备好释放连接的信号”。

序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。

确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。

随后客户端开始在TIME-WAIT阶段等待2MSL

21: 怎么防止反编译?

1、内购破解

iOS应用需防反编译风险之一:插件法(仅越狱)、iTools工具替换文件法(常见为存档破解)、八门神器修改

2、网络安全风险

iOS应用需防反编译风险之二:截获网络请求,破解通信协议并模拟客户端登录,伪造用户行为,对用户数据造成危害

3、应用程序函数PATCH破解

iOS应用需防反编译风险之三:利用FLEX 补丁软件通过派遣返回值来对应用进行patch破解

4、源代码安全风险

iOS应用需防反编译风险之四:通过使用ida等反汇编工具对ipa进行逆向汇编代码,导致核心代码逻辑泄漏与被修改,影响应用安全

1、本地数据加密

iOS应用防反编译加密技术之一:对NSUserDefaults,sqlite存储文件数据加密,保护帐号和关键信息

2、URL编码加密

iOS应用防反编译加密技术之二:对程序中出现的URL进行编码加密,防止URL被静态分析

3、网络传输数据加密

iOS应用防反编译加密技术之三:对客户端传输数据提供加密方案,有效防止通过网络接口的拦截获取数据

4、方法体,方法名高级混淆

iOS应用防反编译加密技术之四:对应用程序的方法名和方法体进行混淆,保证源码被逆向后无法解析代码

5、程序结构混排加密

iOS应用防反编译加密技术之五:对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低

22: 为什么CTMediator方案优于基于Router的方案?


23: 断点续传如何实现的?

1.断点续传需要在下载过程中记录每条线程的下载进度;

2.每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库;

3.在每次向文件中写入数据之后,在数据库中更新下载进度;

4.下载完成之后删除数据库中下载记录。

24: JS有没有用过,原理是什么?


25: 简述组件化实现过程

https://www.jianshu.com/p/012a9d2f98bb

组件化的几种方法

第一种url-block方式:

页面间调用的方式,通过在启动时注册组件提供的服务,把调用组件使用的url和组件提供的服务block对应起来,保存到内存中。在使用组件的服务时,通过url找到对应的block,然后获取服务。

需要在内存中维护url-block的表,组件多了可能会有内存问题

url的参数传递受到限制,只能传递常规的字符串参数,无法传递非常规参数,如UIImage、NSData等类型

没有区分本地调用和远程调用的情况,尤其是远程调用,会因为url参数受限,导致一些功能受限

组件本身依赖了中间件,且分散注册使的耦合较多

第二种protocol-class方式:

第三种url-controller方式:

这部分我将其分为两类:一类是公共基础库,用于跨产品使用;一类是产品基础库,在某个产品中强相关依赖使用。这里以我们自己产品划分为例,概述一下这两类库都包括哪些基础组件:

公共库包括:组件化中间件、网络诊断、第三方SDK管理封装、长连接相关、Patch相关、网络和页面监控相关、用户行为统计库、第三方分享库、JSBridge相关、关于Device+file+crypt+http的基础方法等。

产品基础库包括:通用的WebViewContainer组件(封装了JSBridge)、自定义数字键盘、表情键盘、自定义下拉列表、循环滚动页面、AFNeworking封装库(对上层业务隐藏AF的直接引用)、以及其他自定义的UI基础组件库。

第四种target-action方式

26: 分类的底层是怎么实现的?

通过Runtime加载某个类的所有Category数据;

把所有Category的方法、属性、协议数据,合并到一个大数组中,后面参与编译的Category数据,会在数组的前面。

将合并后的Category分类数据(方法、属性、协议),插入到存放类(类对象、元类对象)原来数据的数组前面。

由于Category分类的数据(方法、属性、协议)在类数据数组的最前面,所以当调用原类、Category(分类)中同名的方法时,会优先调用Category(分类)中的方法,从而实现覆盖了类原本的方法。

分类添加属性原理:

setter方法objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);

getter方法objc_getAssociatedObject(self, _cmd);

27: 如何监控线上版本APP启动耗时(包含动态库的加载时间)

1、main() 函数执行前;

在 main() 函数执行前,系统主要会做下面几件事情:

加载可执行文件(App 的.o 文件的集合);

加载动态链接库,进行 rebase 指针调整和 bind 符号绑定;

Objc 运行时的初始处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等;

初始化,包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量。

2、main() 函数执行后;

很多时候,开发者会把各种初始化工作都放到这个阶段执行,导致渲染完成滞后。更加优化的开发方式,应该是从功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是 App 启动必要的初始化功能,而哪些是只需要在对应功能开始使用时才需要初始化的。梳理完之后,将这些初始化功能分别放到合适的阶段进行。

3、首屏渲染完成后。

28: 如何优化 App 的启动耗时?

pre-main()

1. 动态库加载越多,启动越慢

2. ObjC类,方法越多,启动越慢

3. ObjC的+load越多,启动越慢

4. C的constructor函数越多,启动越慢

5. C++静态对象越多,启动越慢

main()之后

appdelegate的didFinishLaunchingWithOptions初始化可以异步加载尽量使用异步避免阻塞主线程

首屏初始化所需配置文件的读写操作;

首屏列表大数据的读取;

首屏渲染的大量计算等。

29: 动态库和静态库的区别?为什么动态库会影响启动速度?

     静态库编译加载到可执行文件程序启动时直接加载,动态库是在启动时动态链接.

     静态库冷启动速度快,动态库冷启动速度慢(启动时动态链接)优化方式冷加载。


30: 使用drawRect有什么影响?

缺点:它处理touch事件时每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例,那就会很糟糕了

这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为dirty,但还是会显示原来的内容,直到下一次的视图渲染周期,才会将标记为 dirty 的图层重新建立Core Graphics上下文,然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制

31: 自动释放池的原理?

自动释放池(autorelease pool)是一种内存自动回收机制。

引用计数:

-当我们创建一个实例对象,它的引用计数为1;

-当我们向一个对象发送retain消息,它的引用计数+1;

-当我们向一个对象发送release消息,它的引用计数-1;

-当我们向一个对象发送autorelease消息,它的引用计数会在当前自动释放池的末尾-1;

-当一个对象的引用计数减到0,它的内存会被回收。

哨兵模式,只要调用autorelease方法,就会把该对象放到离自己最近的自动释放池中(栈顶的释放池)。在引用计数变成0的时候,runloop会把对象销毁。

release (不考虑其他情况),引用计数变成0,runloop会把对象销毁。

32: UITableView重用机制原理?

UITableView 有缓存池,在创建 UITableViewCell 的时候,会根据 cellId 先去缓存池里面去查有没有这个 cell, 有的话直接拿出来用,没有再创建。

当 cell 离开屏幕的可视范围后,就会被父视图 remove 掉,进入缓存池,长时间没被使用的话,会被销毁。

33: 线程保活有几种方式?

1、[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];

2、

34: 怎样监测卡顿问题?监测到之后怎样优化?


36: 实际开发MVC存在什么问题?面向协议编程的MVP架构思想、双向绑定MVVM的架构思想


37: loadView

作用:

loadView方法就是用来创建UIViewController的View的

什么时候被调用:

首次访问UIViewController的view(如:viewController.view或self.view),而且view为nil,loadView方法就会被调用

实现机制:

如果当前控制器不实现loadView方法,会默认调用父类的loadView方法。默认系统加载:先去storyboard里找,若没有找到,再去与控制器名称相同的xib里找,如果还没找到,那么系统就会默认创建一个空白的view当作是控制器的view,颜色是clearColor

如果当前控制器实现了loadView方法,那么上面的都不会做,所以重写loadView方法不需要调用super,节省不必要的开销。

38:copy和strong的区别

1> 当源字符串是NSString时,由于是不可变字符串,所以不管是实用strong还是copy修饰,都是指向原来的对象,copy只是做了一次浅拷贝。

2> 当源字符串是NSMutableString时,strong只是将源字符串的引用计数+1,而copy则是对源字符串做了次深拷贝,从而生成了一个新的对象,并且copy的对象指向这个新对象。

所以,如果源字符串是NSMutableString时,实用strong只会增加引用计数,但copy会执行一次深拷贝,会造成不必要的内存浪费。而如果源字符串是NSString时,strong和copy效果一样,就不会有这个问题。

我们一般声明NSString时,也不希望它改变,所以一般情况下,建议使用copy,这样可以避免NSMutableString带来的错误。

39:『iOS』引用计数是存放在哪里?

ARM64之前,对象的引用计数都存储在一个叫SideTable结构体的RefCountMap(引用计数表)散列表中;

ARM64之后是保存在ISA中。

详见:https://www.jianshu.com/p/43571ab79821

40:组件化的方案,有什么异同点?

目前业界有三大通用方案:面向接口进行解耦、使用URL路由的方式以及使用runtime进行解耦。各自的代表为:serviceCenter注册的BeeHive、URL注册的Router、使用runtime+Category的CTMediator (https://blog.csdn.net/songzhuo1991/article/details/115977726)

BeeHive: Protcol-impclass 的方式,service层为各个模块对外提供的接口采用protocol的方式,而implclass 为具体实现的类,所有组件会依赖中间层,由中间层统一对外暴露模块的服务;(https://www.jianshu.com/p/4de4c7cb8ad9)

Router: URLRouter方式,采用字符串硬编码方式,通过路由协议来服务;一般现在多用于页面跳转的路由。

CTMediator: target-Action方式,充分利用runtime特性,分本地和远程调用。外部调用:约定好URL规则之后,将远程调度在内部转为本地调度。本地调度:需要按照规则创建Target-Action提供模块的服务。利用CTMediator分类方式明确了对外提供接口,使用runtime主动发现服务方后在利用invocation进行方法的调用。(https://blog.csdn.net/songzhuo1991/article/details/115977726)

41、@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的

@property = ivar + getter + setter;

“属性” (property)有两大概念:ivar(实例变量)、getter+setter(存取方法)

“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。

42、@synthesize 和 @dynamic 分别有什么作用?

@property有两个对应的词,一个是@synthesize(合成实例变量),一个是@dynamic。

如果@synthesize和@dynamic都没有写,那么默认的就是 @synthesize var = _var;

// 在类的实现代码里通过 @synthesize 语法可以来指定实例变量的名字。(@synthesize var = _newVar;)

1. @synthesize 的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

2. @dynamic 告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成(如,@dynamic var)。

43、KVC的底层实现?

setValue: forKey: valueForKey:

当一个对象调用setValue方法时,方法内部会做以下操作:

1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。

2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。

3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。

4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。

这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。

44、KVO的底层实现?

当某个类的属性对象第一次被观察时,系统就会在运行期间动态地创建该类的一个派生类,在这个派生类中重写基类的任何被 观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制

如果原类为Person,那么生成的派生类名为NSKVONotifying_Person

我们知道,每一个类中都有一个isa指针指向当前类,所有系统就是在当一个类的对象第一次被观察的时候,系统就会偷偷将 isa指针指向动态生成的派生类,从而在被监听属性赋值时被执行的是派生类的setter方法

willChangeValueForKey: 和 didChangevalueForKey:

automaticallyNotifiesObserversForKey可以返回NO设置某些属性不被监听

45、如何访问并修改一个类的私有属性?

1). 一种是通过KVC获取。

setvalueforkey/valueforkey

2). 通过runtime访问并修改私有属性。

46、delegate 和 notification 的区别

1). 二者都用于传递消息,不同之处主要在于一个是一对一的,另一个是一对多的。

2). notification通过维护一个array,实现一对多消息的转发。

3). delegate需要两者之间必须建立联系,不然没法调用代理的方法;notification不需要两者之间有联系。

47、什么是block

block是将函数及其执行上下文封装起来的对象。

block内部有isa指针,所以说其本质也是OC对象

参考链接https://www.jianshu.com/p/08235d740480

struct __block_impl {

  void *isa;//isa指针,所以说Block是对象

  int Flags;

  int Reserved;

  void *FuncPtr;//函数指针

};

48、你一般是怎么用Instruments的?

Instruments里面工具很多,常用:

1). Time Profiler: 性能分析

2). Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能。

3). Allocations:用来检查内存,写算法的那批人也用这个来检查。

4). Leaks:检查内存,看是否有内存泄露。

49、iOS中常用的数据存储方式有哪些?

数据存储有四种方案:

NSUserDefault、KeyChain、file、DB。 

其中File有三种方式:plist、Archive(归档) 

DB包括:SQLite、FMDB、CoreData、WCDB(微信)

WCDB 是腾讯开源的一个高效、完整、易用的移动数据库框架,基于SQLCipher,支持 iOS、macOS 和 Android。

基本功能

WINQ(WCDB语言集成查询): 通过WINQ,开发者无须为了拼接SQL的字符串而写一大坨胶水代码。

ORM(Object Relational Mapping): WCDB支持灵活、易用的ORM。开发者可以很便捷地定义表、索引、约束,并进行增删改查操作。

多线程高并发: WCDB支持多线程读与读、读与写并发执行,写与写串行执行。

加密:WCDB提供基于SQLCipher的数据库加密。

损坏修复: WCDB内建了Repair Kit用于修复损坏的数据库。

反注入: WCDB内建了对SQL注入的保护。

50、怎么处理等待几个任务执行完成才开始下一步任务

1、使用GCD的dispatch_group,enter和leave,然后使用group的notify执行

2、使用GCD的信号量,注意在子线程中进行signal,wait-1和signal+1搭配,大于等于0执行

3、使用栅栏函数barrier,注意这里指定给barrier添加的并发队列应该是自己通过dispatch_queue_create函数创建的,如果传的是一个串行队列或者全局并发队列,那此时dispatch_barrier等同于Dispatch_async。

51、如何高性能的给 UIImageView 加个圆角?

不好的解决方案:使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现。

self.view.layer.cornerRadius =5.0f;

self.view.layer.masksToBounds =YES;

正确的解决方案:使用画布core graphics的AddEllipseInRect

还有一种方案:使用了贝塞尔曲线"切割"这个图片addClip

52、HTTP协议中 POST 方法和 GET 方法有那些区别?

1. GET用于向服务器请求数据,POST用于提交数据

2. GET请求,请求参数拼接形式暴露在地址栏,而POST请求参数则放在请求体里面

3. GET请求的URL有长度限制,POST请求不会有长度限制

53、native和hybrid理解

native即是原生APP开发

hybrid混合开发

web就是网页 pc端的网页 移动端的网页 输入域名就可以看见界面

native app 原生态app 就是我们常见的app 代码都是用java(安卓) swift/object-c(苹果)写的

hybrid 混合的 上面两种混合起来 比如安卓,你做好web界面 直接用 webview直接加载web界面,就不用 用native app一个个界面布局 简单快速方便

54、点击屏幕后发生了什么?

首先,以下三种情况下UIView不接受触摸事件:

1.userInteractionEnabled = NO

2.hidden = YES

3.alpha = 0-0.01

55、有哪类锁,锁的原理?

有两类锁:互斥锁和自旋锁;还有一种特殊的锁,叫读写锁,可以用栅栏实现。

锁的种类有很多,其中互斥锁包括递归锁和非递归锁,iOS中的锁大部分是互斥锁.

互斥锁:NSLock,pthread_mutex,@ synchronized,os_unfair_lock

自旋锁:OSSpinLock,spinlock_t(atomic中使用)

读写锁:pthread_rwlock,可以用dispatch_asyn_banner实现

递归锁(特殊的互斥锁): NSRecursiveLock,pthread_mutex

条件锁(互斥锁):NSCondition,NSConditionLock(底层是NSCondition,可以通过swift源码库或者汇编查看)

信号量:dispatch_semaphore

锁:OSSpinLock、NSLock、NSCondition、semaphore(信号量)、@synchronized

所以判断是否发生死锁的最好方法就是看有没有在串行队列(当然也包括主队列)中向这个队列添加任务。又因为我们知道每个串行队列对应一个线程,所以只要不在某个线程中调用会阻塞这个线程的方法即可。

事实上,我们使用同步的方法编程,往往是要求保证任务之间的执行顺序是完全确定的。且不说GCD提供了很多强大的功能来满足这个需求,向串行队列中同步的添加任务本身就是不合理的,毕竟队列已经是串行的了,直接异步添加就可以了啊。所以,解决文章开头那个死锁例子的最简单的方法就是在合适的位置添加一个字母a。

56、性能优化(比如启动优化,编译优化,feed流优化,包体积)

启动优化有:

(1)启动广告加载上次缓存数据,并异步请求下次数据;

(2)减少动态库数量,合并动态库

(3)清理无用类,无用方法,减少分类创建,删减无用静态变量,图片等,使用APPCode工具扫描未使用的代码

(4)非必要,不在+load中执行代码

(5)减少启动时候创建对象的数量,尽量异步创建

(6)首屏数据缓存,进行异步刷新

(7)二进制重排,减少缺页中断

编译优化有:

(1)非当前业务使用打包好的库

(2)dysm不需要生成,若不需要调试的话,也可以把本地符号表的生成去掉

(3)设置只编译当前架构的包

(4)关闭编译器优化

(5)无用资源文件代码等

(6)pch文件的适当使用,掘金上有人真么说

(7)去掉不必要的文件引用

(8) 头文件中尽量使用@class来标识类,减少#import,主要是减少二次引用

(9)使用cccache,但是ccache 不支持 Swift 的问题

(10)使用美团的hmap

feed流优化:

(1)离屏渲染

(2)缓存高度

(3)数据预加载

(4)预排版,计算宽高

(5)图片圆角使用UIGraphics绘制

(6)图层合并为一个图

(7)CALayer替换UIView

(8)避免过多线程,增加线程调度时间

包体积优化:

(1)无用图片,代码删除

(2)图片压缩

(3)图片放到云端,再不行放到xasset

(4)开启Bitcode,减少架构

(5)重复方法抽离

57、二进制重排

1、添加other c flags,-fsanitize-coverage=func,trace-pc-guard

2、使用createLinkOrder方法,生成

3、Order File设置${SRCROOT}/link.order

4、Write Link Map File设置为YES

5、clear一下command+shift+K,再重新编译command+B后,查看Link Map File

58、iOS常用库有哪些?原理是什么?

AFNetworking是封装的NSURLSession的网络请求,由五个模块组成:分别由NSURLSession,Security,Reachability,Serialization,UIKit五部分组成。

Masonry使用链式变成,返回值是block,block的返回值是对象。

SDWebImage

含SDWebImageManager、SDWebImageCache、SDWebImageDownloader以及UIView+WebCache扩展类

SDWebImageManager 主要是对创建任务、判断是否包含下载任务、处理图片在本地还是需要网络请求逻辑(在loadImageWithURL中进行实现)

SDWebImageCache 主要是对图片生成的缓存进行处理,包括保存图片数据、清理磁盘及图片缓存、查找相关路径、确定所占的最大内存等

SDWebImageDownloader 主要负责对公共信息的处理(请求头的拼接、block设置、参数初始化等)、对图片下载进行处理,包含下载的最大并发数、对下载任务的相关操作以及反馈下载的进度及数据等

UIView+WebCache是一个扩展类,主要是通过它将SDWebImageManager中开放的相关方法进行调用然后反馈到UI界面上

MJExtension原理是使用KVC

MJRefresh

59、编译流程是什么?

预处理,执行宏替换,删除注释,条件编译被解开;词法分析和语法分析为AST抽象语法树;静态分析,生成IR,会有类型不匹配的警告;中间代码生成和代码优化;最后生成汇编码;链接器把编译产生的.o.a.dyld等生成macho文件。macho就是可执行文件

60、iOS有什么崩溃?如何捕获和修改?

iOS APP系统crash主要分两类,一类是Objective-C Exception,一类是Unix Signal.Exception

oc exception常见有:数组越界,字典以nil作为key,方法未实现,未设置-objc导致分类未加载,遍历中修改数据,内存不足,kvo监听移除问题

Signal.Exception:调用abort函数生成的信号;除0溢出等;访问自己不允许访问的内存;启动时间过长;用户强制退出,没电;过烫;看门狗干掉。

主要是signal:

信号类主要通过分析是否是系统crash,还是内存泄露、多线程问题等等.

内存泄露可以通过instrument定位,也可以在xcode 开启zombie选择定位. retain-cycle 可以使用第三方工具检测.

关于crash的定位方面,实际项目也会接入crash 上报工具,如腾讯bugly.

这些上报原理是注册对应的处理handleUncaughtException 和信号监听,比如signal(SIGBUS, handleSignalException);

http://wzxing55.com/2018/11/30/ios-app-crash类型总结/

61、hash和iseqal?

重写hash方法和isequal方法;

isequal 先判断nil和类型;再判断地址相等,再判断具体的值;

Hash是用来当对象添加到字典或者set中用的,当hash相同时,再去判断isequal,因此hash尽可能简单又更不容易重复;

62、app有哪些状态?

Not Running:未运行。

Inactive:前台非活动状态。处于前台,但是不能接受事件处理。

Active:前台活动状态。处于前台,能接受事件处理。

Background:后台状态。进入后台,如果又可执行代码,会执行代码,代码执行完毕,程序进行挂起。

Suspended:挂起状态。进入后台,不能执行代码,如果内存不足,程序会被杀死。

63、UIWebView & WKWebView 区别?

1.运行速度更快,占用内存更少。WKWebView 在独立于app进程之外的进程中执行网络请求,请求数据不经过主进程。

2.WKWebView 更快(占用内存可能只有 UIWebView 的1/3~1/4),没有缓存,更为细致地拆分了 UIWebViewDelegate 中的方法。

3.支持了更多的HTML5特性;

4.高达60fps的滚动刷新率以及内置手势;

5.允许JavaScript的Nitro库加载并使用(UIWebView中限制);

64、UIView和CALayer?

首先UIView可以响应事件,Layer不可以.

UIView是CALayer的delegate

UIView主要处理事件,CALayer负责绘制就更好

每个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示,并且 UIView 的尺寸样式都由内部的 Layer 所提供。两者都有树状层级结构,layer 内部有 SubLayers,View 内部有 SubViews.但是 Layer 比 View 多了个AnchorPoint

65、设计模式六大原则?设计模式有哪些

六大原则

单一职责(类的功能要单一),开闭原则(可扩展不可修改),里式替换(子类代替父类),依赖倒置(依赖的单向性,模块不要互相依赖,实现(细节)依赖接口(抽象)),接口隔离(协议的功能要单一),迪米特法则(一个对象对其他对象要有最小的了解)

开闭原则

对模块,类,函数修改是关闭的,扩展是开放的。即软件实体尽量在不修改原有代码的基础上进行扩展

依赖倒置

模块间的依赖通过抽象来发生,实现类之间不产生直接依赖关系,其依赖关系通过接口或者抽象类生成。接口和抽象类不依赖实现类,实现类依赖接口或抽象类。

单一职责

每个类应该实现单一职责,否则应该把类拆分,避免类的臃肿和复用性差。

迪米特原则

最少知识原则,一个类尽量不要和其他类发生关系,对外界知道越少,耦合性越低,当修改时对外界的影响也就越小。

里氏替换原则

里氏替换原则通常拿来和继承对比,它的定义:使用父类的地方能够使用子类来替换,子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。

继承的优缺点:

优点:提高代码复用性,提高代码的可扩展性,子类形似父类,但又保留自己的特性;

缺点:侵入性,不够灵活,耦合度高

里氏替换原则规则为解决继承关系的侵入性和高耦合使用方法:

1.子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法

2.子类中可以增加自己特有的方法

3.当子类重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松

4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格

接口隔离法则

一个类对另一个类的依赖应该建立在最小的接口上。一个类不应该依赖他不需要的接口,接口不应该存在子类用不到但是必须实现的方法,否则需要进行拆分。接口粒度需要尽可能小。

https://www.jianshu.com/p/1f56168fe5b5

设计模式:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

66、自动布局与frame?

autoLayout需要将约束之类的计算成frame的形态,比较消耗cpu资源,特别是如何视图比较复杂页面渲染的时候不够流程,可能要考虑将autoLayout改成用frame直接布局

67、webview的加载流程?

初始化webview -> 请求页面 -> 下载数据 -> 解析HTML -> 请求 js/css 资源 -> dom 渲染 -> 解析 JS 执行 -> JS 请求数据 -> 解析渲染 -> 下载渲染图片

68、udp和tcp的区别?

udptcp

是否连接无连接面向连接

是否可靠不可靠传输,不使用流量控制和拥塞控制可靠传输,使用流量控制和拥塞控制

连接对象个数支持一对一,一对多,多对一和多对多交互通信只能是一对一通信

传输方式面向报文面向字节流

首部开销首部开销小,仅8字节 首部最小20字节,最大60字节

适用场景适用于实时应用(IP电话、视频会议、直播等)适用于要求可靠传输的应用,例如文件传输

69、iOS 图像渲染原理?

70、什么是长连接?心跳跟轮询的区别?

长连接:连接->传输数据->保持连接 -> 传输数据-> ....->直到一方关闭连接,客户端关闭连接。

长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。

短连接:连接->传输数据->关闭连接。

比如HTTP是无状态的的短链接,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。因为连接后接收了数据就断开了,所以每次数据接受处理不会有联系。这也是HTTP协议无状态的原因之一。

71、说一下什么是runloop?卡顿监听为什么是那两个状态?

RunLoop 就是一个跟线程一一对应的事件处理的循环,用来不断的循环处理一些事件。使用 RunLoop 的目的是让你的线程在有工作的时候忙于工作,而没工作的时候休息。 runloop 的设计是为了减少 cpu 无谓的空转。可以是一个状态机的概念。

使用场景:

1、需要使用 Port 其他线程进行通讯;

2、定时器;

3、performSelector的延时操作;

4、子线程保活;

另外,只有主线程在开始的时候开启了runloop

这跟runloop的执行顺序有关,BeforeSources之后,主要是响应UIEvent,也就是处理Source0。如果这个状态太久,说明这个app没办法响应点击事件了。

AfterWaiting之后,说明当前线程刚被唤醒,准备执行被唤醒的事件了。但又卡在这个状态,没有去执行。也能说明当前App卡顿。

nstimer强引用,displaylink 帧率,可以自己设置;autorelease监听的两个通知;runloop监听卡顿,aftersource和afterwait之间。

72、说一下什么是runtime?

运行时,方法替换,分类,关联对象。fishhook原理,innerhook。

把很多别的语言编译时候干的事情放到了运行期间

OC这门语言的的动态性是有RunTime来支撑和实现的,Runtime是一套基于C的API接口,封装了很多跟动态性相关的函数

方法的调用,方法的新增,转换等都依赖于runtime

73、函数式编程和链式编程

如果想再去调用别的方法,那么就需要返回一个对象;

如果想用()去执行,那么需要返回一个block;

如果想让返回的block再调用对象的方法,那么这个block就需要返回一个对象(即返回值为一个对象的block)。

74、埋点方案

①代码埋点就是在代码里需要上报的地方添加上报的代码。

优点

开发简单。

可以精确的控制上报的事件和时机。

方便地把自定义的事件和详细的参数上报。

缺点

对代码侵入比较大。

埋点工作量大,必须是技术开发人员完成。

更新或新增埋点成本较高,如果遇到漏埋或者需要新埋的点,需要重新发版。

②可视化埋点是用可视化的方式,提前把需要采集的控件事件进行圈选,并下发给客户端,用户进行操作时,埋点SDK会自动根据下发的配置进行上报,不需要进行代码埋点。

Mixpanel 提供了可视化埋点的方案,并且把代码进行了开源。SensorsData 参考了 Mixpanel,也进行了开源。本文介绍的技术方案方案参考了这两个开源框架。

优点

解决了代码埋点的侵入性、工作量和发版成本问题。

缺点

不是所有的事件都可以圈选埋点,比如一些和业务逻辑耦合比较紧的事件埋点。

无法在埋点时增加自定义的字段。

③无埋点

优点

在可视化埋点的基础上,可以分析已经上报的数据。也就是如果哪天突然想对某个用户操作进行分析,历史数据也可以分析。

缺点

不是所有的事件都可以圈选埋点,比如一些和业务逻辑耦合比较紧的事件埋点。

无法在埋点时增加自定义的字段。

此外因为上报了所有的用户的操作,相比可视化埋点网络负担稍微增大。

下面列出部分第三方埋点框架:

Mixpanel(开源)

SensorsData(神策)(开源)

Heap

GrowingIO

MTA(腾讯移动分析)

友盟

TalkingData

MTJ(百度移动统计)

GoogleAnalytics(谷歌统计)

HubbleData(网易内部使用)

74、SEL 和 IMP 是什么

SEL : 类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。

IMP:一个函数指针,保存了方法的地址

IMP和SEL关系

每一个继承于NSObject的类都能自动获得runtime的支持。在这样的一个类中,有一个isa指针,指向该类定义的数据结构体,这个结构体是由编译器编译时为类(需继承于NSObject)创建的.在这个结构体中有包括了指向其父类类定义的指针以及 Dispatch table. Dispatch table是一张SEL和IMP的对应表。

也就是说方法编号SEL最后还是要通过Dispatch table表寻找到对应的IMP,IMP就是一个函数指针,然后执行这个方法。

75、HTTPS加解密流程

用户在浏览器发起HTTPS请求,默认使用服务端的443端口进行连接;

HTTPS需要使用一套CA数字证书,证书内会附带一个公钥,而与之对应的私钥保留在服务端不公开;

服务端收到请求,返回配置好的包含公钥的证书给客户端;

客户端收到证书,校验合法性,主要包括是否在有效期内、证书的域名与请求的域名是否匹配,上一级证书是否有效(递归判断,直到判断到系统内置或浏览器配置好的根证书),如果不通过,则显示HTTPS警告信息,如果通过则继续;

客户端生成一个用于对称加密的随机Key,并用证书内的公钥进行加密,发送给服务端;

服务端收到随机Key的密文,使用与公钥配对的私钥进行解密,得到客户端真正想发送的随机Key;

服务端使用客户端发送过来的随机Key对要传输的HTTP数据进行对称加密,将密文返回客户端;

客户端使用随机Key对称解密密文,得到HTTP数据明文;

后续HTTPS请求使用之前交换好的随机Key进行对称加解密。

76、网络七层概述

77、堆栈

栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量等值。其操作方式类似于数据结构中的栈。

存放的局部变量、先进后出、一旦出了作用域就会被销毁;函数跳转地址,现场保护等;

程序猿不需要管理栈区变量的内存; 栈区地址从高到低分配;

堆区(heap):一般由程序员分配释放,若程序员不释放,则可能会引起内存泄漏。注堆和数据结构中的堆栈不一样,其类是与链表。

堆区的内存分配使用的是alloc;需要程序猿管理内存;ARC的内存的管理,是编译器再编译的时候自动添加 retain、release、autorelease;

堆区的地址是从低到高分配)

包括两个部分:未初始化过 、初始化过; 也就是说,(全局区/静态区)在内存中是放在一起的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域; eg:int a;未初始化的。int a = 10;已初始化的。

78、串行队列和并行队列的同步、异步顺序

https://blog.csdn.net/feisongfeiqin/article/details/50282273

①串行队列 异步任务,会创建子线程,且只创建一个子线程,异步任务执行是有序的。

②串行队列 同步任务,不创建新线程,同步任务执行是有序的。 

③并行队列 异步任务,会创建子线程,且多个子线程,异步任务打印结果无序。 

④并行队列 同步任务,不创建新线程,同步任务执行是有序的。 

⑤串行队列 先异步再同步,打印1,添加2任务挂起,打印3,添加同步4阻塞挂起,执行2任务,执行4任务,执行5任务

⑥串行队列 先同步再异步,打印1,添加同步2执行2,打印3,添加4挂起,打印5,执行4任务

⑦并行队列 先异步再同步,打印1,添加2任务挂起,打印3,添加同步4执行4,执行5,2在3之后随机

⑧并行队列 先同步再异步,打印1,添加同步2执行2,打印3,添加任务4挂起,打印5,打印4

同步、异步决定是否创建子线程,同步任务不创建子线程,都是在主线程中执行,异步任务创建子线程。 

串行、并行决定创建子线程的个数,串行创建一个子线程,并行创建多个子线程(具体几个由系统决定)。 

你可能感兴趣的:(面试题)