美文网首页
进阶题一

进阶题一

作者: 纳兰沫 | 来源:发表于2020-10-12 16:09 被阅读0次

1.KVC的底层实现?

当一个对象调用setValue方法时,方法内部会做以下操作:
1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。

2.KVO的底层实现?

KVO-键值观察机制,原理如下:
1.当给A类添加KVO的时候,runtime动态的生成了一个子类NSKVONotifying_A,让A类的isa指针指向NSKVONotifying_A类,重写class方法,隐藏对象真实类信息
2.重写监听属性的setter方法,在setter方法内部调用了Foundation 的 _NSSetObjectValueAndNotify 函数
3._NSSetObjectValueAndNotify函数内部
a) 首先会调用 willChangeValueForKey
b) 然后给属性赋值
c) 最后调用 didChangeValueForKey
d) 最后调用 observer 的 observeValueForKeyPath 去告诉监听器属性值发生了改变 .
4.重写了dealloc做一些 KVO 内存释放

3.说一下工作中你怎么做性能优化的

#造成tableView卡顿的原因
1.没有使用cell的重用标识符,导致一直创建新的cell
2.cell的重新布局
3.没有提前计算并缓存cell的属性及内容
4.cell中控件的数量过多
5.使用了ClearColor,无背景色,透明度为0
6.更新只使用tableView.reloadData()(如果只是更新某组的话,使用reloadSection进行局部更新)
7.加载网络数据,下载图片,没有使用异步加载,并缓存
8.使用addView 给cell动态添加view
9.没有按需加载cell(cell滚动很快时,只加载范围内的cell)
10.实现无用的代理方法(tableView只遵守两个协议)
11.没有做缓存行高(estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。
建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可)
12.做了多余的绘制工作(在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制)
13.没有预渲染图像。(当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕)
#提升tableView的流畅度
*本质上是降低 CPU、GPU 的工作,从这两个大的方面去提升性能。
  1.CPU:对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制
  2.GPU:纹理的渲染
#卡顿优化在 CPU 层面
1.尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView
2.不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的修改
3.尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
4.Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
5.图片的 size 最好刚好跟 UIImageView 的 size 保持一致
6.控制一下线程的最大并发数量
7.尽量把耗时的操作放到子线程
8.文本处理(尺寸计算、绘制)
9.图片处理(解码、绘制)
#卡顿优化在 GPU层面
1.尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
2.GPU能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸
3.尽量减少视图数量和层次
4.减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES
5.尽量避免出现离屏渲染

5.Runtime实现的机制是什么?能做什么事情呢?

runtime简称运行时。OC是运行时机制,也就是在运行时才做一些处理。例如:C语言在编译的时候就知道要调用
哪个方法函数,而OC在编译的时候并不知道要调用哪个方法函数,只有在运行的时候才知道调用的方法函数名称,
来找到对应的方法函数进行调用。
1.发送消息
【场景:方法调用】
2.交换方法实现(交换系统的方法)
【场景:当第三方框架或者系统原生方法功能不能满足我们的时候,我们可以在保持系统原有方法功能的基础上,添加额外的功能。】
3.动态添加方法
【场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。】
4.利用关联对象(AssociatedObject)给分类添加属性
【场景:分类是不能自定义属性和变量的,这时候可以使用runtime动态添加属性方法;
 原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。 】
5.遍历类的所有成员变量
【1.NSCoding自动归档解档
  场景:如果一个模型有许多个属性,实现自定义模型数据持久化时,需要对每个属性都实现一遍encodeObject 和 decodeObjectForKey方法,比较麻烦。我们可以使用Runtime来解决。
  原理:用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。
2.字典转模型
  原理:利用Runtime,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。
  3.访问私有变量
  场景:修改textfield的占位文字颜色】
6.利用消息转发机制解决方法找不到的异常问题

6.iOS图片设置圆角性能问题

1.直接使用setCornerRadius
【这样设置会触发离屏渲染,比较消耗性能。比如当一个页面上有十几头像这样设置了圆角会明显感觉到卡顿。
 注意:png图片UIImageView处理圆角是不会产生离屏渲染的。(ios9.0之后不会离屏渲染,ios9.0之前还是会离屏渲染)】
2.setCornerRadius设置圆角之后,shouldRasterize=YES光栅化
【avatarImageView.layer.shouldRasterize = YES;
 avatarImageViewUrl.layer.rasterizationScale=[UIScreen mainScreen].scale;  //UIImageView不加这句会产生一点模糊
 shouldRasterize=YES设置光栅化,可以使离屏渲染的结果缓存到内存中存为位图,
使用的时候直接使用缓存,节省了一直离屏渲染损耗的性能。
但是如果layer及sublayers常常改变的话,它就会一直不停的渲染及删除缓存重新
创建缓存,所以这种情况下建议不要使用光栅化,这样也是比较损耗性能的。】
3.直接覆盖一张中间为圆形透明的图片(推荐使用)
4.UIImage drawInRect绘制圆角
【这种方式GPU损耗低内存占用大,而且UIButton上不知道怎么绘制,可以用
UIimageView添加个点击手势当做UIButton使用。】
5.SDWebImage处理图片时Core Graphics绘制圆角(暂时感觉是最优方法)

7.什么是 RunLoop?

从字面上讲就是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。
一个线程对应一个RunLoop,基本作用就是保持程序的持续运行,处理app中的各种事件。通过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。
主线程的run loop默认是启动的。iOS的应用程序里面,程序启动后会有一个如下的main()函数
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
@RunLoop,是多线程的法宝,即一个线程一次只能执行一个任务,执行完任务后就会退出线程。主线程执行完即
时任务时会继续等待接收事件而不退出。非主线程通常来说就是为了执行某一任务的,执行完毕就需要归还资源,
因此默认是不运行RunLoop的;
@每一个线程都有其对应的RunLoop,只是默认只有主线程的RunLoop是启动的,其它子线程的RunLoop默认是不
启动的,若要启动则需要手动启动;
@在一个单独的线程中,如果需要在处理完某个任务后不退出,继续等待接收事件,则需要启用RunLoop;
@NSRunLoop提供了一个添加NSTimer的方法,可以指定Mode,如果要让任何情况下都回调,则需要设置Mode为Common模式;
实质上,对于子线程的runloop默认是不存在的,因为苹果采用了懒加载的方式。如果我们没有手动调用
[NSRunLoop currentRunLoop]的话,就不会去查询是否存在当前线程的RunLoop,也就不会去加载,更不会
创建。

8.以scheduledTimerWithTimeInterval的方式触发的timer,在滑动页面上的列表时,timer会暂停,为什么?该如何解决?

原因在于滑动时当前线程的runloop切换了mode用于列表滑动,导致timer暂停。
runloop中的mode主要用来指定事件在runloop中的优先级,有以下几种:
* Default(NSDefaultRunLoopMode):默认,一般情况下使用;
* Connection(NSConnectionReplyMode):一般系统用来处理NSConnection相关事件,开发者一般用不到;
* Modal(NSModalPanelRunLoopMode):处理modal panels事件;
* Event Tracking(NSEventTrackingRunLoopMode):用于处理拖拽和用户交互的模式。
* Common(NSRunloopCommonModes):模式合集。默认包括Default,Modal,Event Tracking三大模式,可以处理几乎所有事件。
回到题中的情境。滑动列表时,runloop的mode由原来的Default模式切换到了Event Tracking模式,timer原来好好的运行在Default模式中,被关闭后自然就停止工作了。
解决方法其一是将timer加入到NSRunloopCommonModes中。其二是将timer放到另一个线程中,然后开启另一个线程的runloop,这样可以保证与主线程互不干扰,而现在主线程正在处理页面滑动。
方法1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
方法2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] run];
});

9.进程与线程

进程:
1.进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元.
2.进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app.
3.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源

线程
1.程序执行流的最小单元,线程是进程中的一个实体.
2.一个进程要想执行任务,必须至少有一条线程.应用程序启动的时候,系统会默认开启一条线程,也就是主线程

进程和线程的关系
1.线程是进程的执行单元,进程的所有任务都在线程中执行
2.线程是 CPU 分配资源和调度的最小单位
3.一个程序可以对应多个进程(多进程),一个进程中可有多个线程,但至少要有一条线程
4.同一个进程内的线程共享进程资源

10.iOS中实现多线程的几种方案,各自有什么特点?讲一下具体使用场景/在项目什么时候选择使用 GCD,什么时候选 择 NSOperation?

pthread 是一套通用的多线程的 API,可以在Unix / Linux / Windows 等系统跨平台使用,使用 C 语言编写,
需要程序员自己管理线程的生命周期,使用难度较大,我们在 iOS 开发中几乎不使用 pthread
NSThread 面向对象的,需要程序员手动创建线程,但不需要手动销毁。子线程间通信很难。
GCD c语言,充分利用了设备的多核,自动管理线程生命周期。比NSOperation效率更高。
NSOperation 基于gcd封装,更加面向对象,比gcd多了一些功能。

场景:1.多个网络请求完成后执行下一步 
     2.多个网络请求顺序执行后执行下一步 
     3.异步操作两组数据时, 执行完第一组之后, 才能执行第二组
@项目中使用 NSOperation 的优点是 NSOperation 是对线程的高度抽象,在项目中使 用它,会使项目的程序
结构更好,子类化 NSOperation 的设计思路,是具有面向对 象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中 使用。
@项目中使用 GCD 的优点是 GCD 本身非常简单、易用,对于不复杂的多线程操 作,会节省代码量,而 Block 
参数的使用,会是代码更为易读,建议在简单项目中 使用。

相关文章

网友评论

      本文标题:进阶题一

      本文链接:https://www.haomeiwen.com/subject/hyrjpktx.html