iOS 常用面试题

2024-05-06  本文已影响0人  Edviin_2de8

1,对于OC的动态性你有什么理解

2,有那些与动画卡帧相关的问题

1.cpu 过载

复杂的计算,大量的数据处理,同步任务的阻塞主线程造成,cpu处理过多的计算任务,导致动画无法按照预期帧率进行

2.GPU 过载

高复杂的图形操作,纹理资源过多,过度绘制等

3.内存压力

过多的内存分配,内存的释放不及时,以及资源的泄漏等

4.主线程的阻塞

长时间的同步任务,网络请求,文件的I/O 等

5.过度绘制 以及一些错误的动画参数

复杂的视图层级,冗余的UI元素等,一次错误的参数导致的冲突等

使用instruments 进行性能分析
选择合适的模板

3.你知道线程保活的方式吗

线程保活是指保持线程在一段时间内持续运行,以便能够快速响应任务或请求。在 iOS 中,线程的创建和销毁是具有一定成本的。如果应用频繁创建和销毁线程,可能会导致性能下降。因此,使用线程保活技术,可以提高应用的性能和响应速度。

iOS 中线程保活的方式

在使用中需要注意

4.你使用过哪些锁,能具体介绍一下吗

首先锁是在多线程环境下,防止数据竞争确保线程同步的一种方式。
常用的锁包括

#import <Foundation/Foundation.h>

NSCondition *condition = [[NSCondition alloc] init];
BOOL ready = NO;

// 生产者线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [condition lock];
    ready = YES;  // 生产者准备好了
    [condition signal];  // 通知等待的线程
    [condition unlock];
});

// 消费者线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [condition lock];
    while (!ready) {  // 等待条件满足
        [condition wait];
    }
    // 执行消费者任务
    // ...
    [condition unlock];
});

NSconditionlock适合更复杂的条件控制
lockWhenCondition
unlockWithCondition

#import <Foundation/Foundation.h>

NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:0];

// 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [conditionLock lockWhenCondition:0];  // 等待条件为 0
    // 执行操作
    [conditionLock unlockWithCondition:1];  // 设置条件为 1
});

// 线程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [conditionLock lockWhenCondition:1];  // 等待条件为 1
    // 执行操作
    [conditionLock unlock];  // 释放锁
});

#import <os/lock.h>

os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;

os_unfair_lock_lock(&lock);  // 获取锁
// 执行操作
os_unfair_lock_unlock(&lock);  // 释放锁

#import <Foundation/Foundation.h>

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);  // 初始信号量为 1

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);  // 等待信号量
    // 执行操作
    dispatch_semaphore_signal(semaphore);  // 释放信号量
});

- (void)threadSafeMethod {
    @synchronized(self) {  // 加锁
        // 执行线程安全的操作
        // ...
    }  // 解锁
}

5.多种计时器的使用有哪些问题

ios开发中最常用的计时器nstimer 需要注意的问题,

立即运行:如果需要计时器立即开始运行,使用 scheduledTimerWithTimeInterval(:target:selector:userInfo:repeats:) 或 scheduledTimerWithTimeInterval(:repeats:block:)。
会立即将计时器添加到当前的runloop 中,并开始执行

稍后运行:如果需要稍后启动计时器或添加到特定的 RunLoop,使用 init(timeInterval:target:selector:userInfo:repeats:) 或 init(fire:interval:target:selector:userInfo:repeats:)

dispatchsouretimer 是基于GCD的定时器,用于更精确的定时任务,不受到runloop的影响,尤其在多线程环境中使用,需要注意手动管理生命周期,以及注意UI刷新任务需要回调到主线程执行

let queue = DispatchQueue(label: "com.myapp.timerQueue")
let dispatchTimer = DispatchSource.makeTimerSource(queue: queue)

// 配置定时器
dispatchTimer.schedule(deadline: .now() + 2.0, repeating: 2.0) // 2秒后开始,每2秒执行

// 添加回调
dispatchTimer.setEventHandler { [weak self] in
    self?.doSomething()
}

// 启动定时器
dispatchTimer.resume() // 默认处于暂停状态,必须 resume 才会开始
//DispatchSourceTimer 需要手动取消,确保计时器不会泄漏。
dispatchTimer.cancel() // 取消计时器

CADisplayLink 用户屏幕刷新通的任务常于动画或者视图渲染,由于与屏幕刷新同步资源消耗高,不适用于延迟执行

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self
                                                         selector:@selector(updateFrame)];

// 将 DisplayLink 添加到 RunLoop
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

[displayLink invalidate]; // 停止 DisplayLink
displayLink = nil; // 确保清除引用

6.如何检测bug和崩溃,包括线上的问题

1.bugly分析 符号表分析 在xcode编译过程中。编译器会生成一个符号表,包含了所有代码中的符号名称和地址映射,在使用时可以讲崩溃日志中的地址信息转换为人类刻度的信息,例如函数名称 文件名等 能都快速定位
2.日志分析
3.异常捕获
4.功能限制开关
5.xcode 调试,debug instrments

7.你有哪些使用KVO 和 KVC的经验?

kvo key-value observing

是一种观察属性变化的机制,能够在对象属性变化时接收到通知,分为3个步骤
1.注册观察者

[self addObserver:self forKeyPath:@"propertyName" options:NSKeyValueObservingOptionNew context:nil];

2.观察属性变化

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"propertyName"]) {
        NSLog(@"Property changed: %@", change[NSKeyValueChangeNewKey]);
    }
}

3.取消注册

[self removeObserver:self forKeyPath:@"propertyName"];

使用的是oc 的运行时动态特性,主要实现步骤为,当你为某个对象添加观察者时,系统会创建对应的子类,并且重写其setter方法,在调用原setter 方法之前发出通知

kvc key-value-codeing

是一种通过键访问属性的方法,允许使用字符串键来获取和设置对象的属性
通过valueforkey setvalue forkey 来实现
还支持嵌套路径

NSString *city = [myObject valueForKeyPath:@"address.city"];

底层实现
1,动态方法查找,查找是否有常规的getter( get<Key>、is<Key>、<Key>) 和 setter 方法
2,访问实例变量,如果类允许的话
3,valueForUndefinedKey setValue:forUndefinedKey:
accessInstanceVariablesDirectly 决定是否能直接访问实例变量
如果键是以 countof enumertorof memberof 等前缀命名,kvc 会假设这是一个集合,并调用相关方法

8,除了加锁,多线程的安全问题还有哪些

1,多个线程同时访问修改共享数据,可能导致数据不一致的问题,解决办法可以通过加锁
2,死锁,多个线程相互等待导致的无法继续,大概是因为锁的使用不当
3,资源竞争,多个线程竞争有限的系统资源(如内存、文件句柄等)可能导致性能下降或者死锁。
4,内存管理问题,多个线程访问数据,一个线程可能释放了另一个线程仍在使用的内存,导致内存访问错误或崩溃。
5,频繁的线程切换可能会带来性能开销
考虑使用合适的同步机制,锁定策略,原子操作(原子操作是一种不可分分割的操作,一单开始就会完成执行,在多线程中其他线程无法终端或者干预这个操作),确保线程的正确和资源的管理

9.什么是runloop ,你怎么理解他,他有什么用处

runloop 还是一个不断循环的机制,负责处理应用程序中的各种事件,每个线程有一个runloop.
runloop 循环包括三个阶段

应用

1.kCFRunLoopEntry 这个表示Runloop 刚刚开始运行,还没处理时间准备好进入循环了
可以进行一些初始化操作,或者设置一些环境
2.kCFRunLoopBeforeTimers 这个状态表示即将检查计时器,timers,可以在这里检查或者调整计时器确保正确的时间间隔
3.kCFRunloopBeforeSources  这个表示即将处理输入源,包括用户 系统时间,在这里可以设置或者修改输入源
4.kCFRunloopBeforeWaitng 这个表示即将进去休眠,这里可以执行一些非紧急任务,
5.kCFRunLoopAfterWaiting 这个表示刚刚从休眠中唤醒,可以在这里执行一些准备操作
6.KCFRunLoopExit 这个表示已经完成循环,即将推出或者开始新的循环,可以在这里做一些清理操作

使用 RunLoop 观察者来监控 RunLoop 的状态。当 RunLoop 在 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 之间的时间超过超时时间时,表明应用可能出现卡顿。

NSTimeInterval timeoutInterval = 0.5; // 超时时间
CFRunLoopObserverRef observer;
CFRunLoopActivity activities = kCFRunLoopBeforeSources | kCFRunLoopAfterWaiting;

observer = CFRunLoopObserverCreateWithHandler(NULL, activities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    static NSDate *entryTime = nil;

    if (activity == kCFRunLoopBeforeSources) {
        entryTime = [NSDate date]; // 记录 RunLoop 进入状态的时间
    } else if (activity == kCFRunLoopAfterWaiting) {
        if (entryTime && -[entryTime timeIntervalSinceNow] > timeoutInterval) {
            NSLog(@"Application is stuck"); // 如果超过超时间,表示可能卡顿
        }
    }
});

CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);

CFRunLoopObserverRef observer;

observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    // 在 RunLoop 即将休眠前执行一些任务
    [self performBackgroundTasks];
});

CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);

10,你有使用Coredata的经验吗,可以分享一下你的使用体验和注意事项吗

ios开发中的数据持久包括NSUserDefault 文件存储,SQlite ,Core Data,Keychain等,

需要注意的是在多线程环境中,每个线程需要使用独立的NSManagerObjectContext,不要共享以防止错误
在设置实体的关联的时候,需要注意避免循环引用

11,你知道哪些性能优化的方法,能具体介绍实现原理吗

12,如何检测卡顿,能介绍具体实现原理吗

14,内存占用过高的检测方法是什么,你能介绍具体实现原理吗,根据你的项目经验,可以分享一下你项目中的难点和优点,以及架构设计的优缺点吗

instruments中的内存分析工具,包括memory leaks (主要是通过引用计数和生命周期,判断其引用计数为0的时候,是否被释放,),allocaction等工具
Xcode debug模式下可以直接查看内存使用
使用建议,正确的使用weak 避免循环引用和内存泄漏,在执行大量的临时对象创建时,使用autoreleasepool 来及时释放内存,在试图页面消失时,检测内存变化,对应的对象dealloc 方法是否执行等

15,多线程的实现方式,GCD和NSOperation的优缺点

常见的就是gcd NSOperation 还有NSThread
GCD是通过队列和任务来实现的并发操作,他的优点包括高性能,简单易用,使用灵活
缺点是缺乏高级功能调试难度较高
GCD的是建立才GCD之上的,提供了更加丰富的功能入任务的依赖关系,优先级,任务的取消等,比GCD相对复杂,由于支持了更多特性,性能略低于GCD

16,你知道哪些第三方库,你有看过哪些源码,他们使用了哪些技术,他们有什么优缺点,他们的架构设计师是怎么样的

  1. AFNetworking
    AFNetworking 是最流行的 Objective-C 网络库之一,简化了 HTTP 网络请求和响应处理。

使用的技术

  1. SDWebImage
    SDWebImage 是一个流行的图片加载和缓存库,常用于优化图片处理。

使用的技术

上一篇 下一篇

猜你喜欢

热点阅读