iOS 常见面试题总结及答案(1)
一. Runloop和线程的关系?
1.一一对应的关系,主线程的runloop已经创建,默认开启,子线程的runloop需要手动创建
2.runloop在第一次获取时创建,在线程结束时销毁.
runloop 的运行逻辑就是 do-while 循环下运用观察者模式(或者说是消息发送),根据7种状态的变化,处理事件输入源和定时器。如下图
runloop的应用:
1.NSTimer在子线程开启一个定时器;控制定时器在特定模式下执行
2.imageView的显示
3.performSelector
4.常驻线程(让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他事件)
5.自动释放池
二.自动释放池什么时候释放?
第一次创建:启动runloop时候
最后一次销毁:runloop退出的时候
其他时候的创建和销毁:当runloop即将睡眠时销毁之前的释放池,重建一个新的
三.什么时候使用weak关键字,和assign的区别?
1.arc中有可能出现循环引用的地方,比如delegate属性
2.自定义IBOutlet空间属性一般也是使用weak
区别:weak表明一种非持有关系,必须用于oc对象;assign用于基本数据类型
weak修饰的指针默认是nil,如果用assign修饰对象,在对象被销毁时,会产生野指针,容易发生崩溃
四.objc中向一个nil对象发送消息将会发生什么?
在oc中向nil发送消息是完全有效的,只是在运行时不会有任何作用,如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil),如果向一个nil对象发送消息,首先寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误.
五.runtime如何实现weak变量的自动置nil
runtime对注册的类,会进行布局,对于weak对象会放入一个hash表中,用weak指向的对象内存地址作为key,当此对象的引用计数为0的时候会dealloc,假如weak指向的对象内存地址是a,那么就会以a为键,在这个weak表中搜索,找到所有以a为键的weak对象,从而设置为nil
六.runtime如何通过selector找到对应的IMP地址?
每一个类对象都有一个方法列表,方法列表中记录着方法名称.方法实现.参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现
七.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
1.不能向编译后得到的类中增加实例变量,可以向运行时创建的类中添加实例变量
原因: (1)因为编译后的类已经注册在runtime中,类结构中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同事runtime会调用class_setIvarLayout 或者class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量
(2)运行时创建的的类可以添加实例变量,调用class_addIvar函数,但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上
八.kvo的实现原理?
当你观察一个对象时,一个新的类会被动态创建,这个类继承自该对象的原本的类,并重写了被观察属性的setter方法,重写setter方法会负责在调用原setter方法之前和之后,通知所有观察对象:值得更改,最后通过isa混写,把这个对象的isa指针(isa指针告诉runtime系统这个对象的类是什么)指向这个新创建的子类,对象就神奇的变成了新创建子类的实例
实现原理如下图:
九.谈谈你对kvc的理解
kvc可以通过key直接访问对象属性,或者给对象的属性赋值,这样可以在运行时动态访问或者改变对象的属性值
当调用setValue:属性值forKey:@"name"的代码时,底层执行机制如下:
1.程序优先调用set:属性值方法,代码通过setter方法完成设置,注意,这里的是指成员变量名,首字母大小写要符合kvc的命名规则
2.如果没有找到setName:方法,kvc机制会检查+(Bool)accessInstanceVariablesDiretly方法,有没有返回yes,默认是返回yes,如果重写了该方法切返回NO的话,那么这一步会直接执行setValue:forUnderfindKey:方法, 如果返回Yes,那么kvc机制会搜索该类里面有没有名为的成员变量,不论该变量是在类接口处定义,还是在类实现处定义,也无论用什么样的访问修饰符只要存在以命名的变量,kvc都可以对该变量赋值
3.如果该类即没有set方法,也没有成员变量,kvc机制会搜索_is的成员变量
4.和上面一样,如果该类即没有set:方法,也没有_和_is成员变量,kvc机制会继续搜索和is的成员变量,再给它赋值
5.如果以上列出的方法和成员变量都不存在,系统将执行setValue:forUnderfindKey:方法,默认是抛出异常.
十.Notification和KVO的区别
1.KVO提供一种机制,当指定的被观察对象属性被修改后,kvo会自动通知响应的观察者
2.通知:是一种广播机制,在事件发生的时候,通过通知中心对象,一个对象能够为所关心这个事件发生的对象发送消息,两者都是观察者模式,不同在于kvo是被观察者直接发送消息给观察者,是对象间的直接交互.通知则是两者都和通知中心对象交互,对象之间不知道彼此
3.本质区别,底层原理不一样,kvo基于runtime,通知则有个通知中心来进行通知
十一.如果让你设计一个通知中心,设计思路
1.创建通知中心单例类,并在里面有一个保存全局NSDictionary
2.对于注册通知的类,将注册通知名作为key,执行的方法和类,以及一些参数作为一个数组为值
3.发送通知可以调用通知中心,通过字典key(通知名)找到对应的类和方法进行调用传值
十二.atomic和nonatomic区别,以及作用?
atomic与nonatom的主要区别就是系统自动生成的getter/setter方法不一样 ,atomic系统自动生成的getter/setter方法会进行加锁操作,nonatomic系统自动生成的getter/setter方法不会进行加锁操作
atomic不是线程安全的
系统生成的getter/setter方法会进行加锁操作,注意:这个锁仅仅保证了getter和setter存取方法的线程安全.因为getter/setter方法有加锁的缘故,故在别的线程来读写这个属性之前,会先执行完当前操作
atomic可以保证多线程访问时候,对象是未被其他线程销毁(比如:如果当一个线程正在get或set时,又有另一个线程同时在进行release操作,可能会直接crash)
十三.说一下静态库和动态库之间的区别
静态库:以.a 和 .framework为文件后缀名。链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝。
动态库:以.tbd(之前叫.dylib) 和 .framework 为文件后缀名。链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用(如系统的UIKit.framework等),节省内存
静态库.a 和 framework区别.a 主要是二进制文件,不包含资源,需要自己添加头文件.framework 可以包含头文件+资源信息
十四.遇到过BAD_ACCESS的错误吗?你是怎样调试的?
BAD_ACCESS 报错属于内存访问错误,会导致程序崩溃,错误的原因是访问了野指针(悬挂指针)。
常规操作如下:
设置全局断点快速定位问题代码所在行。
开启僵尸对象诊断
Analyze分析
重写object的respondsToSelector方法,现实出现EXEC_BAD_ACCESS前访问的最后一个object。
Xcode 7 已经集成了BAD_ACCESS捕获功能:Address Sanitizer。 用法如下:在配置中勾选✅Enable Address Sanitizer
十五.说一下iOS 中的APNS,远程推送原理?
Apple push Notification Service,简称 APNS,是苹果的远程消息推送,原理如下:
iOS 系统向APNS服务器请求手机端的deviceToken
App 接收到手机端的 deviceToken,然后传给 App 对应的服务器.
App 服务端需要发送推送消息时, 需要先通过 APNS 服务器
然后根据对应的 deviceToken 发送给对应的手机
十六.UITableView的优化
1.重用cell
2.缓存行高(在请求到数据的时候提前计算好行高,用字典缓存好高度)
3.加载网络图片,使用异步加载,并缓存,下载的图片根据显示大小切成合适大小的图,查看大图时再显示大图,服务端最好处理好大图和小图,延时加载,当滚动很快时避免频繁请求,可通过runloop设置defultMode状态下渲染请求
4.局部刷新,减少全局刷新
5.渲染,尽量不要使用透明图层,将cell的opaque值设为Yes,背景色和子View不要使用透明色,减少阴影渐变,圆角等
6.少用addSubview给cell动态添加子View,初始化时直接设置好,通过hidden控制显示隐藏,布局在初始化直接布局好,避免cell的重新布局
7.按需加载cell,滚动很快时,只加载范围内的cell,如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后定制n行加载,按需加载,提高流畅性方法如下
8.遇到复杂界面,需要异步绘制,给自定义的cell添加draw方法,在方法中利用GCD异步绘制,或者直接重写drawRect方法,此外,绘制cell不建议使用UIView,建议使用CALayer,UIView的绘制是建立在CoreGraphic上的,使用的是cpu,CALayer使用的是core Animation,CPU.GPU通吃.由系统决定使用哪一个,view的绘制使用的自下向上的一层层的绘制,然后渲染layer处理的是Texure,利用GPU的Texure Cache和独立的浮点数计算单元加速纹理的处理,GPU不喜欢透明,所以绘图一定要弄成不透明,对于圆角和阴影截一个伪透明的小图绘制上去,在layer回调里一定只做绘图,不做计算
cell被重用时,内部绘制的内容并不会自动清除,因此需要调用setNeedsDisplay或者setNeedsDisplayLayInRect:方法
十七.离屏渲染
下面的情况或操作会引发离屏渲染:
1.为图层设置遮罩(layer.mask)
2.将图层的layer.masksToBounds/view.clipsToBounds属性设置为ture
3.将图层layer,allowsGroupOpacity属性设置为Yes和layer.opacity小于1.0
4.给图层设置阴影(layer.shadow)
5.为图层设置layer.shouldRasterize=Yes(光栅化)
6.具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的图层(圆角,抗锯齿)
7.使用CGContext在drawRect:方法中绘制大部分情况会导致离屏渲染,甚至是一个空的实现
优化方案
圆角优化:使用CAShapeLayer和UIBezierPath设置圆角;直接中间覆盖一张为圆形的透明图片
shadow优化:使用shadowPath指定layer阴影效果路径,优化性能
使用异步进行layer渲染(Facebook开源异步绘制框架AsncDisplayKit)
设置layer的opaque值为Yes,减少复杂图层合成
尽量使用不包含透明(alpha)通道的图片资源
尽量设置layer的大小为整型值
十八.UIView和CALayer区别
1.UIView可以响应事件,CALayer不可以,UIView继承自UIResponder,在UIResponder中定义了处理各种事件的事件传递接口。而CALayer直接继承NSObject,并没有相应的处理事件接口。
2.一个CALayer的frame是由它的anchorPoint(锚点),position,bounds,和transform共同决定,而一个view的frame只是简单的返回layer的frame,同样view的center和bounds也是返回layer的一些属性
3..UIView主要是对显示内容的管理,而CALayer主要是侧重显示内容的绘制。UIView是CALayer的CALayerDelegate。
4.每一个view内部都有一个CALayer在背后提供内容的绘制和显示,并且UIView的尺寸样式都由内部的CALayer提供,两者都有树状层级结构,layer内部有subLayers,view内部有subviews。
5.两者最明显的区别是view可以接受并处理事件,而Layer不可以。View是Layer的代理Delegate。
十九.iOS应用程序生命周期
ios程序启动原理(过程):如下:
二十.view视图生命周期