1、了解离屏渲染吗?有遇到过什么情况下出现离屏渲染?为什么给图片切圆角会出现离屏渲染?
离屏渲染(Offscreen Rendering)是指在屏幕外的缓冲区中进行图像绘制,然后再将其显示到屏幕上。离屏渲染通常会在以下情况下出现:
- 使用了
layer.mask
- 设置了
layer.cornerRadius
且clipsToBounds
为YES
- 使用了
layer.shadow
并设置了shadowPath
给图片切圆角会出现离屏渲染的原因是,系统需要创建一个新的缓冲区来绘制带有圆角的图片。这会增加性能开销,影响滚动和动画的流畅性。
2、ARC的全称是什么?什么情况下对象的引用计数会-1?
ARC 的全称是 Automatic Reference Counting(自动引用计数)。对象的引用计数会在以下情况下减 1:
- 对象的强引用被释放时
- 对象所在的作用域结束时(例如局部变量离开作用域)
- 对象从集合(如数组、字典)中被移除时
3、SQLite,FMDB,如何做数据迁移?
在使用 SQLite 和 FMDB 做数据迁移时,可以按照以下步骤进行:
- 创建一个新的数据库版本,包括新的表和字段。
- 编写迁移脚本,将旧版本的数据迁移到新版本的数据库。
- 在应用启动时检查当前数据库版本,如果旧版本存在,执行迁移脚本。
- 更新数据库版本号,以确保下次启动时不再执行迁移
4、计时器都用哪种?NSTimer准吗?如果不准的话,应该用什么?
在 iOS 开发中,常用的计时器有 NSTimer
、CADisplayLink
和 GCD
的计时器(DispatchSourceTimer
)。NSTimer
可能会因为主线程繁忙而不够准确,如果需要高精度的计时,可以使用 CADisplayLink
或 DispatchSourceTimer
。CADisplayLink
适用于与屏幕刷新率同步的计时任务,而 DispatchSourceTimer
更加灵活和精确。
5、有三个网络请求,需要等待三个网络请求全部完成后做刷新UI的操作,要怎么设计?
可以使用 GCD 的 dispatch_group
来实现等待三个网络请求全部完成后再刷新 UI 的操作:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[networkRequest1 startWithCompletion:^(id response) { // 处理响应 dispatch_group_leave(group);}];
dispatch_group_enter(group);
[networkRequest2 startWithCompletion:^(id response) { // 处理响应 dispatch_group_leave(group);}];
dispatch_group_enter(group);
[networkRequest3 startWithCompletion:^(id response) { // 处理响应 dispatch_group_leave(group);}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 刷新UI
});
如果不使用 GCD,可以采用其他方法来等待三个网络请求全部完成后刷新 UI,例如使用 NSOperationQueue
和 NSOperation
,或者使用 Promise
类库(如 PromiseKit),甚至可以通过手动维护一个计数器来实现。
以下是使用 NSOperationQueue
和 NSOperation
的示例:
使用 NSOperationQueue
和 NSOperation
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.maxConcurrentOperationCount = 3; // 并发执行三个请求
NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
// 所有请求完成后的刷新UI操作
dispatch_async(dispatch_get_main_queue(), ^{
// 刷新UI
});
}];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
// 网络请求1
[self performNetworkRequest1WithCompletion:^{
[completionOperation addDependency:operation1];
}];
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
// 网络请求2
[self performNetworkRequest2WithCompletion:^{
[completionOperation addDependency:operation2];
}];
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
// 网络请求3
[self performNetworkRequest3WithCompletion:^{
[completionOperation addDependency:operation3];
}];
}];
[completionOperation addDependency:operation1];
[completionOperation addDependency:operation2];
[completionOperation addDependency:operation3];
[operationQueue addOperations:@[operation1, operation2, operation3, completionOperation] waitUntilFinished:NO];
使用 Promise
类库(例如 PromiseKit)
如果你使用 PromiseKit
,可以通过 when
方法来等待多个异步操作完成:
AnyPromise *promise1 = [self performNetworkRequest1];
AnyPromise *promise2 = [self performNetworkRequest2];
AnyPromise *promise3 = [self performNetworkRequest3];
PMKWhen(@[promise1, promise2, promise3]).then(^(NSArray *results) {
// 刷新UI操作
dispatch_async(dispatch_get_main_queue(), ^{
// 刷新UI
});
}).catch(^(NSError *error) {
// 处理错误
});
手动维护计数器
你也可以手动维护一个计数器,在每个请求完成时增加计数器,当计数器达到 3 时,执行刷新 UI 的操作:
__block NSInteger completedRequests = 0;
dispatch_group_t group = dispatch_group_create();
void (^completionHandler)(void) = ^{
completedRequests += 1;
if (completedRequests == 3) {
dispatch_async(dispatch_get_main_queue(), ^{
// 刷新UI
});
}
};
dispatch_group_enter(group);
[self performNetworkRequest1WithCompletion:^{
completionHandler();
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self performNetworkRequest2WithCompletion:^{
completionHandler();
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self performNetworkRequest3WithCompletion:^{
completionHandler();
dispatch_group_leave(group);
}];
这三种方法都可以达到在三个网络请求全部完成后刷新 UI 的效果,具体使用哪种方法可以根据项目需求和个人习惯来选择。
6、-drawRect:(CGRect)rect{},耗能吗?
-drawRect:(CGRect)rect
方法在进行复杂的绘图操作时可能会比较耗能,尤其是在高频率调用时。为了提高性能,可以尽量减少 -drawRect:
的调用次数,使用 CAShapeLayer
或 CoreGraphics
进行绘图。
7、在一个列表中,有多个item的播放音频的按钮,发送给外部设备,所以播放有延迟,如何做到每个按钮的播放状态不出错?
为了确保每个按钮的播放状态不出错,可以采用以下策略:
- 使用唯一标识符跟踪每个播放请求: 为每个音频播放请求分配一个唯一标识符,并在播放完成或发生错误时与该标识符进行对比。这样可以确保当前按钮的状态始终与最新的请求一致。
- 在播放状态中添加一个状态管理器: 使用状态管理器来跟踪每个音频按钮的播放状态(例如:播放中、已停止、出错等)。在按钮点击时更新状态,并在播放完成或发生错误时根据标识符更新状态。
8、有没有自定义过系统的组件?比如要实现一个半圆弧形的Slider,应该如何实现?
自定义系统组件是常见的需求。要实现一个半圆弧形的 Slider,可以使用 CAShapeLayer
来绘制弧形轨道,并结合 UIPanGestureRecognizer
来处理用户的滑动手势。具体步骤如下:
- 创建一个自定义的
UIView
继承类。 - 使用
CAShapeLayer
绘制半圆弧形轨道。 - 添加滑块(
thumb
)视图。 - 使用
UIPanGestureRecognizer
处理滑块的拖动手势,并根据手势更新滑块的位置和 Slider 的值。
9、Swift和OC的区别?有什么优缺点?
Swift 和 Objective-C 的主要区别:
- 语法风格:Swift 的语法更现代化、简洁,类似于其他现代编程语言,而 Objective-C 则保留了较多的 C 风格语法。
- 类型安全:Swift 是强类型语言,编译时会进行严格的类型检查,减少运行时错误。Objective-C 类型检查较宽松。
- 内存管理:Swift 自动管理内存(ARC),且不需要显式地声明强引用和弱引用,而 Objective-C 需要手动管理。
- 开发效率:Swift 的 Playground 提供了实时反馈,提升了开发效率。Objective-C 缺乏类似的工具。
- 社区和生态:Swift 是开源的,有广泛的社区支持,并且不断更新和改进。Objective-C 虽然稳定但更新缓慢。
优缺点:
-
Swift 优点:
- 现代化语法,提高代码可读性和维护性。
- 类型安全和错误处理机制,减少潜在的运行时错误。
- 更快的开发迭代速度和优化的性能。
-
Swift 缺点:
- 相较于 Objective-C 还较年轻,生态系统尚在完善中。
- 对于已有的大型 Objective-C 项目,迁移成本较高。
-
Objective-C 优点:
- 经过多年验证的稳定性和成熟的生态系统。
- 与 C 语言兼容,适合低层级的系统编程。
-
Objective-C 缺点:
- 语法复杂,学习曲线较陡峭。
- 类型检查宽松,可能导致更多的运行时错误。
10、class和struct区别?
类(class)和结构体(struct)的主要区别:
- 引用类型 vs 值类型:类是引用类型,实例通过引用传递。结构体是值类型,实例通过拷贝传递。
- 继承:类支持继承,一个类可以继承另一个类。结构体不支持继承。
- 引用计数:类实例有引用计数机制(ARC),当没有强引用时,实例会被释放。结构体没有引用计数机制。
- 灵活性:类可以在运行时通过类型检查和类型转换实现更多灵活性。结构体由于是值类型,在类型检查和转换上有限制。
选择使用场景:
- 类:适用于需要继承、需要共享状态、复杂对象管理的场景。
- 结构体:适用于轻量级的数据封装、不需要共享状态、不需要继承的场景,例如坐标点、矩形区域、配置参数等。