性能优化
1.NSTimer 滑动失效是什么原因?
定时器里面有个runoop mode,一般定时器是运行在NSDefaultRunLoopMode上。但是如果滑动了这个页面,主线程runloop会转到UITrackingRunLoopMode中,这时候就不能处理定时器了,造成定时器失效,原因就是runroop mode选错了,解决办法有2个:
1.更改mode为NSRunLoopCommonModes(无论runloop运行在哪个mode,都能运行)
2.在子线程创建NSTimer,主意回调如果刷新UI要切到主线程。
2.Runloop 和线程的关系?
一个线程对应一个 Runloop。
主线程的默认就有了 Runloop。
子线程的 Runloop 以懒加载的形式创建。
Runloop 存储在一个全局的可变字典里,线程是 key ,Runloop 是 value
3.RunLoop的运行模式
RunLoop的运行模式共有5种,RunLoop只会运行在一个模式下,要切换模式,就要暂停当前模式,重写启动一个运行模式
- kCFRunLoopDefaultMode, App的默认运行模式,通常主线程是在这个运行模式下运行
- UITrackingRunLoopMode, 跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
- kCFRunLoopCommonModes, 伪模式,不是一种真正的运行模式
- UIInitializationRunLoopMode:在刚启动App时第进入的第一个Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
4.BAD_ACCESS在什么情况下出现?
原因是访问了野指针,比如访问已经释放对象的成员变量或者发消息、死循环等。
5.单例的利弊是什么?
优点:实例化一次,节省空间,任意地方引用比较方便。 缺点:违背单一职责,滥用浪费内存。
6.UI卡顿&掉帧
【CPU&GPU渲染流程图】:
CPU进行数据的计算,GPU完成渲染工作。屏幕显示的时候,是由垂直同步信号(VSync)和水平同步信号(HSync)组成,按照每秒60帧计算,每隔16.7ms就应该有一次VSync,如果CPU和GPU的工作总耗时超过16.7ms,就会造成卡顿掉帧。
比如前面已经有任务卡住了,通过Runloop的方式只能知道卡了,但要如何把真实卡顿的线程dump出来,如果通过observer的方式,当观察到卡顿的时候其实卡顿已经过去了,要怎么去获取堆栈呢
每隔50毫秒获取一次主线程堆栈,保存最近20个堆栈,当主线程检测到卡顿时,通过对保存到循环队列中的堆栈进行回溯,获取最近最耗时堆栈:
栈顶函数相同即整个堆栈相同 堆栈重复越多可以认为耗时越多 有多个重复次数相同的堆栈,取最近的一个最耗时堆栈
7.列表滚动优化
7.1.列表卡顿的原因可能有哪些了?你平时是怎么优化的?
1.复用问题
2.避免数据过多,布局复杂
3.异步线程处理耗时问题,如 图片下载
4.主线程避免操作复杂问题
5.预排版(布局计算、文本计算、缓存高度等),预渲染(文本等异步绘制、图片解码等)操作放到子线程中处理。
6.尽量避免离屏渲染
7.预加载和缓存数据
8.您在项目中是怎么优化内存的?
1.使用工具如Xcode的Instruments工具、内存分析工具、APM上报的场景去复现,找到内存泄漏位置并修复。
2.通过使用轻量级对象(如继承问题)、减少重量级单例的使用、及时释放不再需要的对象,避免循环引用等方式,使用autorelease pool及时释放临时对象。
3.图片资源优化,无损压缩
4.对于大量数据或复杂对象,可以采用懒加载的方式
使用第三方内存优化工具,如FBMemoryProfiler、Memory Graph Debugger等来帮助定位内存问题并进行优化。
9.优化你是从哪几方面着手?
1.UI卡顿&掉帧:
CPU:
• 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView
• 不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改
• 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
• Autolayout会比直接设置frame消耗更多的CPU资源
• 图片的size最好刚好跟UIImageView的size保持一致
• 控制一下线程的最大并发数量
• 尽量把耗时的操作放到子线程
文本处理(尺寸计算、绘制)
图片处理(解码、绘制)
GPU:
• 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
• GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸
• 尽量减少视图数量和层次
• 减少透明的视图(alpha<1),不透明的就设置opaque为YES(默认就是YES)
• 尽量避免出现离屏渲染
• 离屏渲染
• 在OpenGL中,GPU有2种渲染方式
On-Screen Rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作
Off-Screen Rendering:离屏渲染,在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
• 离屏渲染消耗性能的原因
需要创建新的缓冲区
离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕
• 哪些操作会触发离屏渲染?
• 光栅化,layer.shouldRasterize = YES
• 遮罩,layer.mask
• 圆角,同时设置layer.masksToBounds = YES、layer.cornerRadius大于0,可以考虑通过CoreGraphics绘制裁剪圆角,或者叫美工提供圆角图片
• 阴影,layer.shadowXXX,如果设置了layer.shadowPath就不会产生离屏渲染
2.内存占用较高:上面内存优化
3.耗电量大:
主要来源:
1.CPU处理,Processing
2.网络,Networking
3.定位,Location
4.图像,Graphics
耗电优化:
• CPU处理,Processing
1.尽可能降低CPU、GPU功耗
• 少用定时器
• 优化I/O操作
1.尽量不要频繁写入小数据,最好批量一次性写入
2.读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的API。用dispatch_io系统会优化磁盘访问
数据量比较大的,建议使用数据库(比如SQLite、CoreData)
• 网络优化
1.减少、压缩网络数据
2.如果多次请求的结果是相同的,尽量使用缓存
3.使用断点续传,否则网络不稳定时可能多次传输相同的内容
4.网络不可用时,不要尝试执行网络请求
5.让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间
6.批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封,不要一封一封地下载
• 定位优化
1.如果只是需要快速确定用户位置,最好用CLLocationManager的requestLocation方法。定位完成后,会自动让定位硬件断电
2.如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务
3.尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest
4.需要后台定位时,尽量设置pausesLocationUpdatesAutomatically为YES,如果用户不太可能移动的时候系统会自动暂停位置更新
5.尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion:
• 硬件检测优化
1.用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件
10.iOS常见的崩溃类型
空指针异常 (Nil Pointer Exception):
- 试图访问一个为
nil的对象或变量。
- 试图访问一个为
数组越界 (Array Index Out of Bounds):
- 尝试访问数组中不存在的索引。
内存泄漏 (Memory Leaks) 和 内存不足 (Out of Memory):
- 内存泄漏导致内存使用持续增长,最终可能导致应用崩溃。
- 内存不足时,应用可能会被系统强制终止。
线程同步问题 (Thread Synchronization Issues):
- 多线程访问共享资源时未正确同步,导致数据竞争或死锁。
KVO/KVC 错误 (Key-Value Observing/Key-Value Coding Errors):
- 观察者未正确移除或访问未定义的键。
类型转换错误 (Type Casting Errors):
- 将对象强制转换为不兼容的类型。
UI 操作在非主线程 (UI Operations on Non-Main Thread):
- 在非主线程上进行 UI 更新操作。
网络请求错误 (Networking Errors):
- 处理网络请求时未正确处理错误或响应数据。
文件 I/O 错误 (File I/O Errors):
- 读取或写入文件时出现错误,例如文件不存在或权限问题。
未处理的用户输入 (Unhandled User Input):
- 例如,未正确处理用户输入导致的异常情况。
11.iOS内存泄漏的场景有哪些
循环引用 (Retain Cycles):
- 对象之间的强引用:两个或多个对象相互强引用,导致它们无法被释放。
- 自引用 (Self-Referencing):对象对自己进行强引用,例如在 block 中使用
self而未使用weak或unowned修饰符。
未释放的单例 (Singletons):
- 单例对象在整个应用生命周期中持续存在,如果单例对象持有大量资源,会导致内存泄漏。
未正确处理的通知 (Notifications):
- 注册了通知但未在对象销毁时移除观察者,导致对象无法被释放。
未正确处理的 KVO (Key-Value Observing):
- 注册了 KVO 观察但未在对象销毁时移除观察者,导致对象无法被释放。
未正确处理的 Timer (定时器):
- 定时器对目标对象的强引用导致目标对象无法被释放。应使用
weak引用或在适当时机使定时器无效。
- 定时器对目标对象的强引用导致目标对象无法被释放。应使用
未正确释放的文件句柄或套接字:
- 打开文件或网络连接后未正确关闭。
未正确处理的 Block:
- Block 捕获了对象的强引用,导致对象无法被释放。应使用
__weak或__block修饰符来避免强引用。
- Block 捕获了对象的强引用,导致对象无法被释放。应使用
12.iOS一般什么情况下会导致野指针
多线程问题:
- 在多线程环境中,一个线程释放了对象,而另一个线程试图访问该对象。
未正确处理的弱引用 (Weak References):
- 弱引用(
__weak或weak)在对象被释放后会自动置为nil,但未正确处理弱引用可能会导致访问已释放的对象。
- 弱引用(
未正确处理的通知 (Notifications):
- 对象在未移除观察者的情况下被释放,导致通知中心尝试向已释放的对象发送通知。
未正确处理的 KVO (Key-Value Observing):
- 对象在未移除观察者的情况下被释放,导致 KVO 机制尝试向已释放的对象发送通知。
未正确处理的 Timer (定时器):
- 定时器对目标对象的强引用,目标对象在未使定时器无效的情况下被释放。
如何进行多线程切换,例如网络数据如何从子线程传递到主线程
1.使用 DispatchQueue 和 GCD (Grand Central Dispatch)
// 在后台线程执行网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 模拟网络请求
NSData *data = [self fetchDataFromNetwork];
// 切换到主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUIWithData:data];
});
});2.使用 NSOperationQueue
NSOperationQueue 提供了更高层次的抽象,可以更灵活地管理并发任务。以下是一个示例:
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
[backgroundQueue addOperationWithBlock:^{
// 模拟网络请求
NSData *data = [self fetchDataFromNetwork];
// 切换到主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateUIWithData:data];
}];
}];3.使用 performSelectorOnMainThread
这是一个较为老旧的方法,但在某些情况下依然有用:
// 在后台线程执行网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 模拟网络请求
NSData *data = [self fetchDataFromNetwork];
// 切换到主线程更新UI
[self performSelectorOnMainThread:@selector(updateUIWithData:) withObject:data waitUntilDone:NO];
});13.iOS中常见的死锁案例
1. 主线程同步调用自身
这是最常见的死锁情况之一。主线程试图同步执行另一个任务,而这个任务又需要主线程的资源,导致相互等待。
dispatch_sync(dispatch_get_main_queue(), ^{
// 这段代码永远不会被执行,因为主线程已经被阻塞
});2. 多个队列间的相互等待
多个队列相互等待资源,导致死锁。
dispatch_queue_t queueA = dispatch_queue_create("com.example.queueA", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueB = dispatch_queue_create("com.example.queueB", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queueA, ^{
dispatch_sync(queueB, ^{
// 这段代码永远不会被执行,因为 queueA 和 queueB 相互等待
});
});
dispatch_sync(queueB, ^{
dispatch_sync(queueA, ^{
// 这段代码永远不会被执行,因为 queueA 和 queueB 相互等待
});
});3. GCD 的嵌套同步调用
在一个队列中进行嵌套同步调用,导致死锁。
dispatch_queue_t queue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
dispatch_sync(queue, ^{
// 这段代码永远不会被执行,因为队列自身被阻塞
});
});4. NSLock 的误用
使用 NSLock 时,如果一个线程持有锁而另一个线程试图获取锁,则会导致死锁。
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
sleep(1); // 模拟耗时操作
[lock unlock];
});
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
// 这段代码永远不会被执行,因为锁已经被另一个线程持有
[lock unlock];
});5. NSRecursiveLock 的误用
NSRecursiveLock 允许同一个线程多次获取锁,但如果多个线程相互等待,会导致死锁。
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
sleep(1); // 模拟耗时操作
[lock unlock];
});
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
// 这段代码永远不会被执行,因为锁已经被另一个线程持有
[lock unlock];
});6. NSOperationQueue 的相互依赖
NSOperationQueue 中的操作相互依赖,导致死锁。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
// Operation 1
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
// Operation 2
}];
[operation1 addDependency:operation2];
[operation2 addDependency:operation1];
[queue addOperation:operation1];
[queue addOperation:operation2];
// 这段代码永远不会被执行,因为 operation1 和 operation2 相互依赖避免死锁的建议
- 尽量避免在主线程上进行同步调用,特别是
dispatch_sync。 - 小心处理锁,确保锁的获取和释放在正确的线程和时机。
- 避免循环依赖,确保操作和任务之间没有相互依赖的关系。
- 使用适当的并发工具,如
NSRecursiveLock或NSCondition,根据实际需求选择合适的工具。 - 使用调试工具,如 Xcode 的线程调试器,检测和定位死锁问题。
