面试题零碎问题

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值