2024年iOS面试题记录

813 阅读7分钟

1、了解离屏渲染吗?有遇到过什么情况下出现离屏渲染?为什么给图片切圆角会出现离屏渲染?

离屏渲染(Offscreen Rendering)是指在屏幕外的缓冲区中进行图像绘制,然后再将其显示到屏幕上。离屏渲染通常会在以下情况下出现:

  • 使用了 layer.mask
  • 设置了 layer.cornerRadiusclipsToBoundsYES
  • 使用了 layer.shadow 并设置了 shadowPath

给图片切圆角会出现离屏渲染的原因是,系统需要创建一个新的缓冲区来绘制带有圆角的图片。这会增加性能开销,影响滚动和动画的流畅性。

2、ARC的全称是什么?什么情况下对象的引用计数会-1?

ARC 的全称是 Automatic Reference Counting(自动引用计数)。对象的引用计数会在以下情况下减 1:

  • 对象的强引用被释放时
  • 对象所在的作用域结束时(例如局部变量离开作用域)
  • 对象从集合(如数组、字典)中被移除时

3、SQLite,FMDB,如何做数据迁移?

在使用 SQLite 和 FMDB 做数据迁移时,可以按照以下步骤进行:

  1. 创建一个新的数据库版本,包括新的表和字段。
  2. 编写迁移脚本,将旧版本的数据迁移到新版本的数据库。
  3. 在应用启动时检查当前数据库版本,如果旧版本存在,执行迁移脚本。
  4. 更新数据库版本号,以确保下次启动时不再执行迁移

4、计时器都用哪种?NSTimer准吗?如果不准的话,应该用什么?

在 iOS 开发中,常用的计时器有 NSTimerCADisplayLinkGCD 的计时器(DispatchSourceTimer)。NSTimer 可能会因为主线程繁忙而不够准确,如果需要高精度的计时,可以使用 CADisplayLinkDispatchSourceTimerCADisplayLink 适用于与屏幕刷新率同步的计时任务,而 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,例如使用 NSOperationQueueNSOperation,或者使用 Promise 类库(如 PromiseKit),甚至可以通过手动维护一个计数器来实现。

以下是使用 NSOperationQueueNSOperation 的示例:

使用 NSOperationQueueNSOperation

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: 的调用次数,使用 CAShapeLayerCoreGraphics 进行绘图。

7、在一个列表中,有多个item的播放音频的按钮,发送给外部设备,所以播放有延迟,如何做到每个按钮的播放状态不出错?

为了确保每个按钮的播放状态不出错,可以采用以下策略:

  1. 使用唯一标识符跟踪每个播放请求: 为每个音频播放请求分配一个唯一标识符,并在播放完成或发生错误时与该标识符进行对比。这样可以确保当前按钮的状态始终与最新的请求一致。
  2. 在播放状态中添加一个状态管理器: 使用状态管理器来跟踪每个音频按钮的播放状态(例如:播放中、已停止、出错等)。在按钮点击时更新状态,并在播放完成或发生错误时根据标识符更新状态。

8、有没有自定义过系统的组件?比如要实现一个半圆弧形的Slider,应该如何实现?

自定义系统组件是常见的需求。要实现一个半圆弧形的 Slider,可以使用 CAShapeLayer 来绘制弧形轨道,并结合 UIPanGestureRecognizer 来处理用户的滑动手势。具体步骤如下:

  1. 创建一个自定义的 UIView 继承类。
  2. 使用 CAShapeLayer 绘制半圆弧形轨道。
  3. 添加滑块(thumb)视图。
  4. 使用 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),当没有强引用时,实例会被释放。结构体没有引用计数机制。
  • 灵活性:类可以在运行时通过类型检查和类型转换实现更多灵活性。结构体由于是值类型,在类型检查和转换上有限制。

选择使用场景:

  • :适用于需要继承、需要共享状态、复杂对象管理的场景。
  • 结构体:适用于轻量级的数据封装、不需要共享状态、不需要继承的场景,例如坐标点、矩形区域、配置参数等。

11、从事这么久的iOS工作,有什么项目或者什么功能让你印象深刻,可以分享一下?