OC高阶面试题

序号 面试题
1 UI视图的事件传递机制是如何实现的?
2 UI绘制原理是怎样的?
3 请利用TableView的重用机制实现一个字母索引条?
4 什么是离屏渲染?
5 什么是ARC?
6 AutoReleasePool的实现机制。(总结一句话:是以栈为结点构成的双向链表结构。)
7 循环引用相关的考察,NSTimer如果重复调用怎样解除循环引用?
8 __block关键字是否可以解决循环引用?
9 Block的本质是什么?
10 Block的截获变量的特性应该怎样解释,Block是怎样产生循环引用的?
11 怎样利用iOS的多线程技术对共享变量实现多读单写操作呢?
12 怎样理解自旋锁? 递归锁应该怎样使用?
13 常见的线程同步问题该怎样解决?
14 怎样解决DNS劫持?
15 TCP的慢启动特点是怎样的。
16 你对HTTPS是怎样理解的?
17 给你一个实际场景,让大家现场提出利用哪个设计模式解决实际问题。
18 怎样设计一个时长统计框架?
19 怎样设计一个图片缓存框架?
20 客户端的整体架构实现是怎样的,解耦方式都有哪些?
21 UIView和CALayer之间的关系是怎样的?请从设计原则的角度回答系统为何这样设计?
22 UI卡顿、掉帧的原理是怎样的?
23 请解释一下你对isa指针的理解。
24 你是怎样理解引用计数机制的?(很多人会说什么retain\release\dealloc,完全没有Get到面试官的考察意图)
25 结构体和类的区别?
26 分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?
27 讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?
28 被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?
29 关联对象有什么应用,系统如何管理关联对象?其被释放的时候需要手动将其指针置空么?
30 KVO的底层实现?如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?
31 Autoreleasepool 所使用的数据结构是什么? AutoreleasePoolPage 结构体了解么?
32 讲一下对象,类对象,元类,跟元类结构体的组成以及他们是如何相关联的?为什么对象方法没有保存的对象结构体里,而是保存在类对象的结构体里?
33 class_ro_t 和 class_rw_t 的区别?
34 iOS 中内省的几个方法? class 方法和 objc_getClass 方法有什么区别?
35 在运行时创建类的方法 objc_allocateClassPair 的方法名尾部为什么是pair(成对的意思)?
36 一个int变量被 __block 修饰与否的区别?
37 为什么在block外部使用 __weak 修饰的同时需要在内部使用 __strong 修饰?
38 RunLoop的作用是什么?它的内部工作机制了解么?(最好结合线程和内存管理来说)
39 哪些场景可以触发离屏渲染?(知道多少说多少)

1、UI视图的事件传递机制是如何实现的?

事件的产生

  • 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中为什么是队列而不是栈?因为队列的特定是先进先出,先产生的事件先处理才符合常理,所以把事件添加到队列。
  • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)。
  • 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步。
    找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理。

事件的传递

  • 触摸事件的传递是从父控件传递到子控件
  • 也就是UIApplication->window->寻找处理事件最合适的view

注 意: 如果父控件不能接受触摸事件,那么子控件就不可能接收到触摸事件

应用如何找到最合适的控件来处理事件?

  • 1.首先判断主窗口(keyWindow)自己是否能接受触摸事件
  • 2.判断触摸点是否在自己身上
  • 3.子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤)
  • 4.view,比如叫做fitView,那么会把这个事件交给这个fitView,再遍历这个fitView的子控件,直至没有更合适的view为止。
  • 5.如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的view。

UIView不能接收触摸事件的三种情况:

    1. 不允许交互:userInteractionEnabled = NO
    1. 隐藏:如果把父控件隐藏,那么子控件也会隐藏,隐藏的控件不能接受事件
    1. 透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明。

注 意:默认UIImageView不能接受触摸事件,因为不允许交互,即userInteractionEnabled = NO,所以如果希望UIImageView可以交互,需要userInteractionEnabled = YES。

2、UI绘制原理是怎样的?

  • iOS系统主要提供两种途径去创建高质量的图形:OpenGL或者使用原生Quarts、Core Animation和UIKit。本文会展开讲一下后者。
  • Quartz是主要的绘制途径,它提供了基于路径绘制、抗锯齿绘制、渐变色、图形绘制、颜色、变形和PDF文档的创建展示和解析能力。UIKit是对Quartz的线条、图片和颜色操作的封装。Core Animation提供了对在动画中修改UIView属性的的支持,同时还可以实现自定义动画。

3、请利用TableView的重用机制实现一个字母索引条?

传送门

4、什么是离屏渲染?

GPU渲染机制:
CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
GPU屏幕渲染有以下两种方式:
On-Screen Rendering意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。

Off-Screen Rendering意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
离屏渲染的触发方式
设置了以下属性时,都会触发离屏绘制:
shouldRasterize(光栅化)
masks(遮罩)
shadows(阴影)
edge antialiasing(抗锯齿)
group opacity(不透明)
复杂形状设置圆角等
渐变

其中shouldRasterize(光栅化)是比较特别的一种:光栅化概念:将图转化为一个个栅格组成的图象。光栅化特点:每个元素对应帧缓冲区中的一像素。
shouldRasterize = YES在其他属性触发离屏渲染的同时,会将光栅化后的内容缓存起来,如果对应的layer及其sublayers没有发生改变,在下一帧的时候可以直接复用。shouldRasterize = YES,这将隐式的创建一个位图,各种阴影遮罩等效果也会保存到位图中并缓存起来,从而减少渲染的频度(不是矢量图)。
相当于光栅化是把GPU的操作转到CPU上了,生成位图缓存,直接读取复用。
当你使用光栅化时,你可以开启“Color Hits Green and Misses Red”来检查该场景下光栅化操作是否是一个好的选择。绿色表示缓存被复用,红色表示缓存在被重复创建。
如果光栅化的层变红得太频繁那么光栅化对优化可能没有多少用处。位图缓存从内存中删除又重新创建得太过频繁,红色表明缓存重建得太迟。可以针对性的选择某个较小而较深的层结构进行光栅化,来尝试减少渲染时间。
注意:对于经常变动的内容,这个时候不要开启,否则会造成性能的浪费
例如我们日程经常打交道的TableViewCell,因为TableViewCell的重绘是很频繁的(因为Cell的复用),如果Cell的内容不断变化,则Cell需要不断重绘,如果此时设置了cell.layer可光栅化。则会造成大量的离屏渲染,降低图形性能。

为什么会使用离屏渲染
当使用圆角,阴影,遮罩的时候,图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制,所以就需要屏幕外渲染被唤起。
屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。
所以当使用离屏渲染的时候会很容易造成性能消耗,因为在OPENGL里离屏渲染会单独在内存中创建一个屏幕外缓冲区并进行渲染,而屏幕外缓冲区跟当前屏幕缓冲区上下文切换是很耗性能的。
Instruments监测离屏渲染
Instruments的Core Animation工具中有几个和离屏渲染相关的检查选项:
Color Offscreen-Rendered Yellow开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。

Color Hits Green and Misses Red如果shouldRasterize被设置成YES,对应的渲染结果会被缓存,如果图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。

iOS版本上的优化
iOS 9.0 之前UIimageView跟UIButton设置圆角都会触发离屏渲染
iOS 9.0 之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。
这可能是苹果也意识到离屏渲染会产生性能问题,所以能不产生离屏渲染的地方苹果也就不用离屏渲染了。

5、什么是ARC?

Automatic Reference Counting,自动引用计数,即ARC,WWDC2011和iOS5所引入的。ARC是新的LLVM 3.0编译器的一项特性,使用ARC,解决了手动内存管理的麻烦。永远不写retain,release和autorelease三个关键字。
当ARC开启时,编译器将自动在代码合适的地方插入retain,release和autorelease。
关于效率:ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做的只不过是在代码编译时为你自动在合适的位置插入release或autorelease,就如同之前MRC时你所做的那样。因此,至少在效率上ARC机制是不会比MRC弱的,而因为可以在最合适的地方完成引用计数的维护,以及部分优化,使用ARC甚至能比MRC取得更高的运行效率。

ARC的基本规则:只要某个对象被任一strong指针指向,那么它将不会被销毁。如果对象没有被任何strong指针指向,那么就将被销毁。weak类型的指针也可以指向对象,但是并不会持有该对象。

使用ARC以后,不论是strong还是weak类型的指针,都不再会指向一个dealloced的对象,从根源上解决了意外释放导致的crash。

6、AutoReleasePool的实现机制

其实是一种类似栈结构的进栈出栈操作,以栈为结点构成的双向链表结构,每一个线程的 autoreleasepool 其实就是一个指针的堆栈
单个自动释放池的执行过程就是

objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)

7、 循环引用相关的考察,NSTimer如果重复调用怎样解除循环引用?

使用NSTimer时,由于被加入到了Runloop中,会被Runloop强引用,且NSTimer的target如果是self的话,会导致无法释放,也就走不到dealloc。以下是解决方案:

封装一个NSTimer的工具类,暴露出类方法和相应的回调,这样NSTimer的target只会是工具类的self,不是使用类的self,就不会导致强引用。同时在调用invalidate方法之后,要把定时器nil掉,这样就能保证NSTimer的及时释放。

好,坑来了:假设你在一个类中声明了一个NSTimer属性,如果一个“不小心”初始化了两次,一定一定要记得,invalidate两次,换句话说:初始化几次,invalidate几次。具体原因是因为,NSTimer被初始化之后就加入到了Runloop中,初始化几次,就加入了几个,如果不一个一个invalidate的话,定时器就会一直存在,页面也就无法释放了。

8、 __block关键字是否可以解决循环引用?

block不会强引用 block内部的局部变量和 弱指针,只会强引用 block 外部strong指针,并不是 block结束之后就会释放掉局部变量,所以不会引起循环,因为如果像那样说的话,假如block不执行,那局部变量岂不是就释放不掉了。
__block的作用:用其修饰的外部变量,我们可以在blcok中才能修改该变量。

那为什么用__block修饰的变量才能在block中修改呢?我们知道,局部变量是存放在栈中的,所在的函数退出后栈也被清空。有被清空的危险,而__block的作用就是将该变量复制到堆上去,自己管理声明周期!这样就不存在清空的危险,block页知道改外部变量是存在的,修改是安全的。

先看看__block是怎么解决循环引用的:

__block MyObject *myobject = self;

self.block = ^{

NSLog(@"%@", myobject.ob1);

myobject = nil;

};

原因其实也是将self复制一份到堆上,然后在block中用完后手动释放掉!(myobject = nil)

9、Block的本质是什么?

block对象就是一个结构体,里面有isa指针指向自己的类(global malloc stack),有desc结构体描述block的信息,__forwarding指向自己或堆上自己的地址,如果block对象截获变量,这些变量也会出现在block结构体中。最重要的block结构体有一个函数指针,指向block代码块。block结构体的构造函数的参数,包括函数指针,描述block的结构体,自动截获的变量(全局变量不用截获),引用到的__block变量。(__block对象也会转变成结构体)

block代码块在编译的时候会生成一个函数,函数第一个参数是前面说到的block对象结构体指针。执行block,相当于执行block里面__forwarding里面的函数指针。

10、Block的截获变量的特性应该怎样解释,Block是怎样产生循环引用的?

因为block的特性,编译器不允许在block内直接修改所捕获的变量,但是我们可以修改__block修饰的自动变量,因为用__block修饰过之后,原先存储在栈中的变量就变成了存在堆中了,查看用clang过后的cpp文件你会发现在block中多了一个与该变量同名的__Block_byref_i_0结构体的指针变量,其中包含了存储在堆中的那个变量,可以通过结构体指针变量来直接更改变量的值,而没有用__block修饰的变量,block会把截获的变量copy为自己的一个变量。
由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,则会造成循环引用。

11、怎样利用iOS的多线程技术对共享变量实现多读单写操作呢?

关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法,或者某一个代码块。

12、怎样理解自旋锁? 递归锁应该怎样使用?

传送门

13、常见的线程同步问题该怎样解决?

1、加锁
2、串行队列思想

14、怎样解决DNS劫持?

DNS劫持

15、TCP的慢启动特点是怎样的。

通常在刚开始发送报文段时,先把拥塞窗口cwnd设置为一个最大报文段的数值。而在每收到一个对新报文段的确认后,把拥塞窗口增加至多一个MSS数值。
也就是说,第一次时发送1个报文,在收到接收端确认之后,第二次时发送2个报文,同样都确认后,第三次时发送4个报文,2倍指数增长。
它的名字虽然叫慢启动,但实际上一点不慢,因为指数增长是很快的,所以它需要一个上限值,默认为64k。
慢启动的作用就是最大限度使用网络资源。

16、你对HTTPS是怎样理解的?

HTTPS使用理解

17、给你一个实际场景,让大家现场提出利用哪个设计模式解决实际问题。

设计模式

18、怎样设计一个时长统计框架?

时长统计框架

19、怎样设计一个图片缓存框架?

传送门

20、客户端的整体架构实现是怎样的,解耦方式都有哪些?

客户端整体架构

21、UIView和CALayer之间的关系是怎样的?请从设计原则的角度回答系统为何这样设计?

UIView和CALayer之间的关系

22、UI卡顿、掉帧的原理是怎样的?

UI卡顿、掉帧的原理
优化

23、请解释一下你对isa指针的理解

isa指针

24、你是怎样理解引用计数机制的?(很多人会说什么retain\release\dealloc,完全没有Get到面试官的考察意图)

Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数器。如果想使某个对象继续存活,那就递增其引用计数;用完了之后,就递减其引用计数。计数变为0,就表示没人关注此对象了,于是,就可以把它销毁。

25、结构体和类的区别?

1、结构体只能封装属性,类却不仅可以封装属性也可以封装方法。如果一个封装的数据有属性也有行为,就只能用类了。
2、结构体变量分配在栈,而OC对象分配在堆,栈的空间相对于堆来说是比较小的,但是存储在栈的数据访问效率相对于堆而言是比较高的。
3、堆的存储空间比较大,存储在堆中的数据访问效率相对于栈而言是比较低的。
4、如果定义一个结构体,这个结构体有很多属性,那么这个时候结构体变量在栈中会占据很多空间,这样的话就会降低效率
5、我们使用结构体的时候最好是属性比较少的结构体对象,如果属性较多的话就要用类了。
6、结构体赋值的话是直接赋值,对象的指针,赋值的对象地址。

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