整理一下面试题:

阿里p5

1.MVC具有什么样的优势,各个模块之间怎么通信,比如点击 Button 后 怎么通知 Model?

参考答案:

MVC 是一种设计思想,一种框架模式,是一种把应用中的所有类组织起来的策略,它把你的程序分为三块,分别是:

M : 实际上考虑的是“什么”问题,你的程序本质上是什么,独立于UI工作,是程序重处理应用程序逻辑的部分,通常负责存取数据。

C:  控制你的Model 如何呈现在屏幕上,当它需要数据的时候就告诉Model,你帮我获取某某某数据,当它需要UI展示和更新的时候就告诉View,你帮我生成一个UI显示某某数据,是Model 和View 的沟通桥梁。

MVC 通信规则

Controller to Model

可以直接单向通讯。Controller 需要将Model 呈现给用户,因此需要知道模型的一切,还需要有通Model 完全通信的能力,并且能任意使用Model 的公共API.

Controller to View

可以直接通信,Controller 通过View 来布局用户界面。

Model to View

永远不要直接通信,Model 是独立于UI的,并不需要和View 直接通信,View 通过Controller 获取数据。

View to Controller

View 不能对Controller 知道的太多,因此要通过间接的方式通信。

Target action.首先Contrller 会留给自己一个Target ,再把配套的action 交给view 作为联系方式。那么view 接收到某些变化时,View 就会发送action 给target 从而达到通知的目的。这里View 只需要发送action ,并不需要知道Controller 如何取执行方法。

代理。有时候View 没有足够的逻辑去判断用户操作是否符合规范,他会把判断这些问题的权利委托给其他对象。他只需要获得答案就行了,并不管是谁给的答案。

DataSource  View 没有拥有他们所显示数据的权利,View 只能向controller 请求数据进行显示,controller 则获取Model 的数据整理排版后提供给View.

Model 访问Controller

同样的Model 是独立于UI存在的。因此无法直接与Controller 通信,但是当Moel 本身信息发生改变的时候,会通过下面的方式进行间接通信。

Notification & KVO 一种类似电台的方法,Model 信息的改变时会广播消息给感兴趣的人,只要Controller 收到了这个广播的时候就会主动联系Model,获取新的数据并提供给View.

MVC的优点: 低耦合性。有利于开发分工。有利于组件重用。可维护性。

2.两个无限长度链表(也就是可能有环) 判断有没有交点

(单链表是否存在环?环的入口是什么?)

是否存在环

1) 判断是否存在环: 设置快慢指针fast 和slow,fast 步速为2,slow 为1,若最终fast==slow,那么就证明单链表中一定有环,如果没有环的话,fast 一定先达到尾节点。

2)简单证明: 利用相对运动的概念,以slow 为参考点(静止不动),那么fast的步速实际为1,当fast 超过slow之后,fast 以每步一个节点的速度追赶slow,如果链表有环的话,fast 一定会追赶slow,即fast==slow.

如何找到环的入口

第一次相遇

字母代表的量:

a.链表头节点到环入口的距离

r:环长

蓝色线:fast 指针所走的距离为2s

黑色线:slow指针所走的距离1s

假设链表的总长度为L,且fast 与slow 相遇时fast 已经绕环走了n圈,则有如下关系:

2s = s + nr

将s 移到左边得:

s = nr

转换:

s = (n-1)r + r = (n-1)r + L -a

a + x = (n-1)r + L -a

得:

a = (n-1)r+L-a-x

由图可知,(L-a-x)为相遇点到环入口的距离。由上式可知:

从链表头到环入口的距离 = (n-1)圈内环循环 + 相遇点到环入口的距离,将r视为周期的话,a 与L-a-x 在某种意义上是相等的(实际并不相等)。那么由此我们便找到了突破点,为了找到环的入口点,在fast 与slow 相遇时,将slow 指针重新指向单链表的头节点,fast 仍留在相遇点,只不过步速降为与slow 相同的1,每次循环只经过一个节点,如此,当fast 与slow 再次相遇,那个新的相遇点便是我们苦苦寻找的入口点了。

如何知道环的长度

记录下相遇点,让slow 与fast 从该点开始,再次碰撞所走过的操作数就是环的长度r.

带环的链表长度是多少

链表的长度=环入口+环的长度 L = a+ r

分析问题之前我们先搞清楚链表相交的一些基本概念

明确概念: 两个单向链表相交,只能是y型相交,不可能是x型相交

分析: 有两个链表,La,Lb,设他们的交点为p,假设La 中,p的前驱为pre_a,,后继为next_a,在Lb 中,前驱为pre_b,后继为next_b,则pre_a->next=p,pre_b->next=p,接下来看后继,p->next = next_a,p->next=next_b,那么问题来了,一个单链表的next指针只有一个,怎么跑两个呢,所以必有next_a==next_b,于是我们能得出两个链表相交只能是y型相交。

情况一: 两个链表都是无环

1)问题简化。将链表B接到链表A的后面,如果A,B有交点,则构成一有环的单链表,而我们上面讨论了一个如何判断一个单链表有环。

2) 若两个链表相交则必为Y型,由此可知两个链表从相交点到尾节点是相同的,我们并不知道他们的相交点位置。但是我们可以遍历的出AB链表的尾节点,如此,比较他们的尾节点是否相等便可以求证A。B是否相交了。

情况二:链表有环

1)其中一个链表有环,另一个链表无环。则两个链表不可能相交。

2)那么有环相交的情况只有当两个链表都有环时才会出现,如果两个有环链表相交,则他们拥有共通的换,即环上任意一个节点都存在于两个链表上,因此,通过判断A链表上的快慢指针相遇点是否也在B链表上遍可以得出两个链表是否相交了。

如果两个无环相交的单向链表,怎么求出他们的第一个节点呢? 分析:采用对齐的思想,计算两个链表的长度L1,L2,分别用两个指针P1,P2指向两个链表的头,然后将较长的表的p1向后移动L2-L1个节点,然后再同时向后移动P1,P2,知道P1=p2,相遇的点就是相交的第一个节点。

3.UITableView的相关优化

1)正确的使用UITableViewCell的重用机制

2)提前计算好cell的高度和布局

3)避免阻塞主线程

很多时候我们需要从网络加载图片,把这些操作放在后台执行,并且缓存起来,现在大部分使用sdwebImage进行网络图片处理,正常的使用是没有问题的,但是如果对性能要求较高,或者要处理gif图片,推荐YYWebImage

4)按需加载

5)减少SubViews的数量

6)尽可能重用开销比较大的对象。

如NSDateFormatter 和NSCalendar等对象初始化比较慢,我们可以把它加入类的属性中,或创建单例来使用。

7)尽量减少计算的复杂度。

在高分屏尽量用ceil 或floor 或round 取整,不要出现1.007这样的小数。

8)不要动态add 或者remove 子控件

最好在初始化时就添加完,然后通过hidden来控制是否显示。

9)cell 上的颜色尽量不要用透明颜色

因为在渲染这些view 时,如果是透明的颜色,需要将该View 和下层view 混合后才计算出该像素点的实际颜色

10)使用异步绘制

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       CGRect rect = CGRectMake(0, 0, 100, 100);
       UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
       CGContextRef context = UIGraphicsGetCurrentContext();
       [[UIColor lightGrayColor] set];
       CGContextFillRect(context, rect);
       
       //将绘制的内容以图片的形式返回,并调用主线程显示
       UIImage * tempImg = UIGraphicsGetImageFromCurrentImageContext();
       UIGraphicsEndImageContext();
       //回到主线程
       dispatch_async(dispatch_get_main_queue(), ^{
           //code
       });
   });

另外绘制cell 不建议使用UIView,建议使用CALayer

从形式来说;UIView 的绘制是建立在CoreGraphic 上的,使用的是CPU,CALayer 使用的是CoreAnimation,CPU,GPU通吃,由系统决定使用哪个,View 的绘制使用的是自下而上的一层一层的绘制,然后渲染,Layer处理的是Texture,利用GPU的Texture Chache 和独立的浮点数计算单元加速纹理的处理。

从事件响应上来说,UIView 是CALayer 的代理,layer 本身并不能响应事件,因为layer 是直接继承NSObject,不具备处理事件的能力。而UIView 继承了UIResponder 的,这也是事件转发的事件角度说明,view 要比单纯的layer 复杂的多。在滑动的列表上,多层次的view 再加上各种手势的处理势必会导致帧数的下降。

在这一块还有个问题就是当tableView 快速滑动的时候,会有大量的异步绘制任务提交到后台线程去执行,线程并不是越多越好,太多了只会增加cpu的负担,所以我们需要在适当的时候取消不重要的线程。

目前两种做法:

YY的做法是:

尽量快速、提前判断当前绘制的任务是否取消,在绘制每一行文本前,都不会调用isCancelled()来进行判断,保证被取消的任务能及时退出,不至于影响后续操作。

另一种做法是:

当滑动时,松开手指后,立刻计算出滑动停止时cell 的位置,并预先绘制那个位置附近的几个cell,而忽略当前滑动中的cell,忽略的代价就是快速滑动中会出现大量空白的内容。

11)尽量避免使用带来离屏渲染的效果

4.KVO、Notification、delegate各自的优缺点,效率还有使用场景

delegate 的优势:

1)很严格的语法,所有能响应的事件必须在协议中有清晰的定义

2)因为有很严格的语法,所以编译器能帮你检查是否实现了所有应该实现的方法,不容易遗忘和出错

3)使用delegate的时候,逻辑很清楚,控制流程可跟踪和识别

4)在一个controller 中可以定义多个协议,每个协议有不同的delegate

5) 没有第三方的要求保持/监视通讯过程,假如出现问题我们可以很容易比较方便的定位错误代码。

6)能够接受调用的协议方法的返回值,意味着delegate 能够提供反馈信息给controller

delegate的缺点:

 需要写的代码很多

NotificationCenter : 单例,允许当前事件发生的时候通知一些对象,满足控制器与一个任意的对象进行通信的目的,这种模式的基本特征就是接收到在该controller中发生的某种事件而产生的消息,controller 用一个key 通知的名称,这样对于controller 是匿名的,其他的使用同样的key 来注册了该通知的对象能对通知的事件作出反应。

notification的优势:

1、不需要写多少代码,实现比较简单

2、一个对象发出的通知,多个对象能进行反应,一对多的方式实现很简单

缺点:

1、编译期不会知道通知是否能被正确处理

2、释放注册的对象时候,需要在通知中心取消注册。

3、调试的时候程序的工作以及控制流程难跟踪

4、需要第三方来管理controller 和观察者的联系

5、controller 和观察者需要提前知道通知的名称、UseInfo dictionary keys,如果没有这些在工作区间会出现不同步的现象

6、通知发出后,发出通知的对象不能从观察者获得任何反馈

KVO

KVO 是一个对象能观察另一个对象属性的值,前两种模式更适合一个controller 和其它对象通信,而kvo 适合任何对象监听另一个对象的改变,这是一个对象与另一个对象保持同步的方法,kvo 只能对属性做出反应,不会用来对方法或者事件作出反应。

优点:

1、提供一个简单的方法来实现两个对象同步

2、能够对非我们创建的对象作出反应

3、能够提供观察的属性的最新值和先前值

4、用keypaths 来观察属性,因此也可以观察嵌套对象

缺点:

1、观察的属性值必须用string 定义,因此编译器不会出现警告和检查

2、对属性重构将导致观察不可用

3、复杂的if 语句要求对象正在观察多个值,这是因为所有的观察都通过一个方法来指向kvo 有显著的使用场景,当你希望监视一个属性的时候,我们选用KVO

5.如何手动通知KVO

想要知道如何手动触发,必须知道自动触发KVO的原理:

willChangeValueForKey: 和didChangeValueForKey

一个是在被观察属性发生改变之前,willChangeValueForKey 一定会被调用,这就会记录旧的值

当改变发生之后,didChangeValueForKey:会被调用

继而objectServerValueForKey: OfObject:change:content: 也会被调用

如果可以手动实现这些调用就可以实现手动触发了

6.Objective-C 中的copy方法

对象的复制就是复制一个对象作为副本,它会开辟一块新的内存来存储副本对象。必须实现NSCopying 或NSMutableCopying 协议

copy:产生的副本是不可变的

mutableCopy:产生的副本是可变的

自定义copy 还必须实现copywithzone 或mutableCopywithzone

7.runtime 中,SEL和IMP的区别

IMP 指向方法实现的指针

SEL 类成员方法的指针

8.autoreleasepool的使用场景和原理

当某一块代码中产生大量变量,容易造成僵尸对象的地方。autoreleasepool 可以自动管理对象,当对象引用计数器为0时会在autoreleasepool pop时自动销毁,原理是延迟调用release

9.RunLoop的实现原理和数据结构,什么时候会用到

 此处省略一千字

10.block为什么会有循环引用

 block 在oc 中是一个独立的代码块,当block 中持有block 所在控制器时,而控制器又持有block时,两者的引用计数器都是1,只有一方的引用计数器为0,才可以释放,否则,相互引用就不会释放,可以使用__weak 来修饰变量来解除循环引用。

12.NSOperation和GCD的区别

NSOperation 是对GCD的封装的一个OC的api.GCD是基于c的api,是苹果对C中Pthread 的一个封装,其内部做了很多性能方面的优化。GCD使用block,代码高效简洁,可以实现比较复杂的多线程应用。

NSOperation 很显著的特点是可以设置最大并发数和依赖关系,可以使用KVO监听不同时段的状态。

13.CoreData的使用,如何处理多线程问题


14.如何设计图片缓存?

1、提高相应速度

2、减少网络流量

3、提高用户体验


15.有没有自己设计过网络控件?