1.导致Crash的原因有哪些?
1、找不到方法的实现unrecognized selector sent to instance 2、KVC造成的crash 3、EXC_BAD_ACCESS 4、KVO引起的崩溃 5、集合类相关崩溃 6、多线程中的崩溃 7、Socket长连接,进入后台没有关闭 8、Watch Dog超时造成的crash 9、后台返回NSNull导致的崩溃,多见于Java做后台服务器开发语言
2.不使用第三方,如何知道已经上线的App崩溃问题, 具体到哪一个类的哪一个方法的?
大致实现方式如下。
使用NSSetUncaughtExceptionHandler可以统计闪退的信息。
将统计到的信息以data的形式 利用网络请求发给后台
在后台收集信息,进行排查
-
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. NSSetUncaughtExceptionHandler(&my_uncaught_exception_handler); return YES; } static void my_uncaught_exception_handler (NSException *exception) { //这里可以取到 NSException 信息 NSLog(@"***********************************************"); NSLog(@"%@",exception); NSLog(@"%@",exception.callStackReturnAddresses); NSLog(@"%@",exception.callStackSymbols); NSLog(@"***********************************************"); }
3.NSString类型为什么要用copy修饰 ?
主要是防止NSString被修改,如果没有修改的说法用Strong也行。
当NSString的赋值来源是NSString时,strong和copy作用相同。
当NSString的赋值来源是NSMutableString,copy会做深拷贝,重新生成一个新的对象,修改赋值来源不会影响NSString的值。
4.单例是怎么销毁的?
//必须把static dispatch_once_t onceToken; 这个拿到函数体外,成为全局的.
+ (void)attempDealloc {
onceToken = 0; // 只有置成0,GCD才会认为它从未执行过.它默认为0,这样才能保证下次再次调用shareInstance的时候,再次创建对象.
_sharedInstance = nil;
}
dispatch_once_t 的工作原理是,static修饰会默认将其初始化为0, 当且仅当其为0的时候dispatch_once(&onceToken, ^{})这个函数才能被调用, 如果执行了这个函数 这个dispatch_once_t 静态变成- 1了 就永远不会被调用
不使用dispatch_once 如何 实现单例
1.第一种方式,重写+allocWithZone:方法;
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static id instance = nil;
@synchronized (self) { // 互斥锁
if (instance == nil) {
instance = [super allocWithZone:zone];
}
}
return instance;
}
2.第二种方式,不用重写+allocWithZone:方法,而是直接用@synchronized 来保证线程安全,其它与上面这个方法一样;
+ (instancetype)sharedSingleton {
static id instance = nil;
@synchronized (self) {
if (!instance) {
instance = [[self alloc] init];
}
}
return instance;
}
项目开发中,你用单例都做了什么?
答 :整个程序公用一份资源的时候 例如 :
设置单例类访问应用的配置信息
用户的个人信息登录后用的NSUserDefaults存储,对登录类进一步采用单例封装方便全局访问
防止一个单例对 应用 多处 对同意本地数据库存进行操作
5.APNS的基本原理
第一阶段:应用程序的服务器端把要发送的消息、目的iPhone的标识打包,发给APNS。
第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发送到iPhone。
第三阶段:iPhone把发来的消息传递给相应的应用程序,并且按照设定弹出Push通知。
6.ViewController生命周期
按照执行顺序排列:
1. initWithCoder:通过nib文件初始化时触发。
2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
3. loadView:开始加载视图控制器自带的view。
4. viewDidLoad:视图控制器的view被加载完成。
5. viewWillAppear:视图控制器的view将要显示在window上。
6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9. viewDidAppear:视图控制器的view已经展示到window上。
10. viewWillDisappear:视图控制器的view将要从window上消失。
11. viewDidDisappear:视图控制器的view已经从window上消失。
7为什么说NSTimer不准确?
// 在子线程中开启NStimer,或者更改当前Runloop的Mode 为NSRunLoopCommonModes
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
// 利用CADisplayLink (iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高)
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(logInfo)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// 利用GCD
NSTimeInterval interval = 1.0;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"GCD timer test");
});
dispatch_resume(_timer);
8.为什么AFN3.0中需要设置self.operationQueue.maxConcurrentOperationCount = 1;而AF2.0却不需要?
功能不一样, 2.x是基于NSURLConnection的,其内部实现要在异步并发,所以不能设置1。 3.0 是基于NSURLSession其内部是需要串行的鉴于一些多线程数据访问的安全性考虑, 设置这个达到串行回调的效果。
AFNetworking 2.0 和3.0 的区别?
AFN3.0剔除了所有的NSURLConnection请求的API
AFN3.0使用NSOperationQueue代替AFN2.0的常驻线程
9.autoreleasePool 在何时被释放?
一个被autoreleasepool包裹生成得对象,都会在其创建生成之后自动添加autorelease, 然后被autorelease对象得释放时机 就是在当前runloop循环结束的时候自动释放的
子线程中的autorelease变量什么时候释放?
子线程中会默认包裹一个autoreleasepool的, 释放时机是当前线程退出的时候。
10.简单解释一下渲染机制
首先iOS渲染视图的核心是Core Animation,其渲染层次依次为:图层树->呈现树->渲染树
一共三个阶段
CPU阶段(进行Frame布局,准备视图和图层之间的层级关系)
OpenGL ES阶段(iOS8以后改成Metal), (渲染服务把上面提供的图层上色,生成各种帧)
GPU阶段 (把上面操作的东西进行一些列的操作,最终展示到屏幕上面)
稍微详细说明
首先一个视图由CPU进行Frame布局,准备视图和图层的层及关系。
CPU会将处理视图和图层的层级关系打包,通过IPC(进程间的通信)通道提交给渲染服务(OpenGL和GPU)
渲染服务首先将图层交给OpenGL进行纹理生成和着色,生成前后帧缓存,再根据硬件的刷新帧率,一般以设备的VSync信号和CADisplayLink(类似一个刷新UI专用的定时器)为标准,进行前后帧缓存的切换
最后,将最终 要显示在画面上的后帧缓存交给GPU,进行采集图片和形状,运行变换, 应用纹理混合,最终显示在屏幕上。
从第一次打开App到完全开始展现出UI,中间发生了什么? 或者App是怎么渲染某一个View的?
Core Animation 在 RunLoop 中注册了一个 Observer,监听了 BeforeWaiting 和 Exit 事件。这个 Observer 的优先级是 2000000,低于常见的其他 Observer。当一个触摸事件到来时,RunLoop 被唤醒,App 中的代码会执行一些操作,比如创建和调整视图层级、设置 UIView 的 frame、修改 CALayer 的透明度、为视图添加一个动画;这些操作最终都会被 CALayer 捕获,并通过 CATransaction 提交到一个中间状态去(CATransaction 的文档略有提到这些内容,但并不完整)。当上面所有操作结束后,RunLoop 即将进入休眠(或者退出)时,关注该事件的 Observer 都会得到通知。这时 CA 注册的那个 Observer 就会在回调中,把所有的中间状态合并提交到 GPU 去显示;如果此处有动画,CA 会通过 DisplayLink 等机制多次触发相关流程。
CPU渲染职能
布局计算:如果视图层级过于复杂,当试图呈现或者修改的时候,计算图层帧率就会消耗一部分时间,
视图懒加载: iOS只会当视图控制器的视图显示到屏幕上才会加载它,这对内存使用和程序启动时间很有好处,但是当呈现到屏幕之前,按下按钮导致的许多工作都不会被及时响应。比如,控制器从数据局中获取数据, 或者视图从一个xib加载,或者涉及iO图片显示都会比CPU正常操作慢得多。
解压图片:PNG或者JPEG压缩之后的图片文件会比同质量的位图小得多。但是在图片绘制到屏幕上之前,必须把它扩展成完整的未解压的尺寸(通常等同于图片宽 x 长 x 4个字节)。为了节省内存,iOS通常直到真正绘制的时候才去解码图片。根据你加载图片的方式,第一次对 图层内容赋值的时候(直接或者间接使用 UIImageView )或者把它绘制到 Core Graphics中,都需要对它解压,这样的话,对于一个较大的图片,都会占用一定的时间。
Core Graphics绘制:如果对视图实现了drawRect:或drawLayer:inContext:方法,或者 CALayerDelegate 的 方法,那么在绘制任何东 西之前都会产生一个巨大的性能开销。为了支持对图层内容的任意绘制,Core Animation必须创建一个内存中等大小的寄宿图片。然后一旦绘制结束之后, 必须把图片数据通过IPC传到渲染服务器。在此基础上,Core Graphics绘制就会变得十分缓慢,所以在一个对性能十分挑剔的场景下这样做十分不好。
图层打包:当图层被成功打包,发送到渲染服务器之后,CPU仍然要做如下工作:为了显示 屏幕上的图层,Core Animation必须对渲染树种的每个可见图层通过OpenGL循环 转换成纹理三角板。由于GPU并不知晓Core Animation图层的任何结构,所以必须 要由CPU做这些事情。这里CPU涉及的工作和图层个数成正比,所以如果在你的层 级关系中有太多的图层,就会导致CPU没一帧的渲染,即使这些事情不是你的应用 程序可控的。
GPU渲染职能
GPU会根据生成的前后帧缓存数据,根据实际情况进行合成,其中造成GPU渲染负担的一般是:离屏渲染,图层混合,延迟加载。
这里又会出现一个面试题!!! 一个UIImageView添加到视图上以后,内部如何渲染到手机上的?
图片显示分为三个步骤: 加载、解码、渲染、 通常,我们程序员的操作只是加载,至于解码和渲染是由UIKit内部进行的。 例如:UIImageView显示在屏幕上的时候需要UIImage对象进行数据源的赋值。而UIImage持有的数据是未解码的压缩数据,当赋值的时候,图像数据会被解码变成RGB颜色数据,最终渲染到屏幕上。
看完上面的又来问题了! 关于UITableView优化的问题?(真他妈子子孙孙无穷尽也~) 先说造成UITableView滚动时候卡顿的的原因有哪些?
- 隐式绘制 CGContext
- 文本CATextLayer 和 UILabel
- 光栅化 shouldRasterize
- 离屏渲染
- 可伸缩图片
- shadowPath
- 混合和过度绘制
- 减少图层数量
- 裁切
- 对象回收
- Core Graphics绘制
- -renderInContext: 方法
在说关于UITableView的优化问题!
基础的
- 重用机制(缓存池)
- 少用有透明度的View
- 尽量避免使用xib
- 尽量避免过多的层级结构
- iOS8以后出的预估高度
- 减少离屏渲染操作(圆角、阴影啥的)
**** 解释一下为什么减少离屏渲染操作?****
需要创建新的缓冲区
整个过程需要多次切换上下文环境, 显示从当前的屏幕切换到离屏,等待离屏渲染结束后,将离屏缓冲区的渲染结果 显示到屏幕有上, 又要将上下文环境从离屏切换到当前屏幕,
****那些操作会触发离屏渲染?****
光栅化 layer.shouldRasterize = YES
遮罩layer.mask
圆角layer.maskToBounds = Yes,Layer.cornerRadis 大于0
阴影layer.shadowXXX
进阶的
缓存cell的高度(提前计算好cell的高度,缓存进当前的模型里面)
异步绘制
滑动的时候,按需加载
高阶的
你想不到 竟然不推荐用UILabel。哈哈哈~ 至于为什么 看下面的链接吧
至于上面的那些基础的,涉及到渲染级别的自己说的时候悠着点,面试官如果想搞你的话,考一考你最上面的那些,CUP和GUP,以及openGL相关, 在考一下你进程通信IPC,以及VSync信号啥的, 这些东西太鸡儿高深了,没点匠心 这东西还真搞不了,要想研究可以看看YYKit的作者写的一篇关于页面流畅的文章:blog.ibireme.com/2015/11/12/…
卡顿检测的方法
卡顿就是主线程阻塞的时间问题,可以添加Observer到主线程Runloop中,通过监听Runloop状态切换的耗时,以达到监听卡顿的目的
tableView 加一个tap的手势, 点击当前cell的位置 哪个事件被响应 为什么?
tap事件被响应, 因为tap事件添加之后,默认是取消当前tap以外的所有事件的, 也就是说, tap事件处于当前响应者链的最顶端, 解决的办法执行tap的delagete