美文网首页
iOS 常用面试题

iOS 常用面试题

作者: Edviin_2de8 | 来源:发表于2024-05-06 22:17 被阅读0次

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

  • 动态绑定
    在运行时而不是在编译时确定方法的调用和函数的应用,通过msg send 实现动态方法的绑定
  • 动态类型
    对象的类型可以在运行时确定而不是编译时固定,使用id 类型是可以在运行时决定对象的实际类型,通过 iskindof respondsToSelector 确保类型的安全执行
  • 动态加载
    可以在运势是按需加载动态库或者框架
  • 动态方法交换
    可以通过class_replaceMethod 可以在运行时替换对象的特定方法,还以为类或者对象添加方法,扩展功能
  • 支持元编程
    以在运行时检查和操作代码的结构行为和类型,主要表现为
    -- 1.反射
    类反射,可以在运行时函数检查类的信息。例如objc_getclasslist获取当前进程中的类,class_getName 获取类的名称
    方法反射,使用class_copyMethodlist 获取一个类的所有方法
    属性反射,使用class_copyproertylist获取类的属性列表
    --2.动态方法的添加和替换
    class_addMethod, 为一个类新增方法,便于扩展
    class_replaceMedhtod,对方法的实现进行替换,实现对方法的拦截或修改
    perfromSelector 等动态调用方法
    --3.动态类的创建和修改
    ocj_allocateclasspair objc_registercallpair
    --4.动态属性的操作,包括添加删除修改
    class_addProperty 添加新的属性
    objc_getAssociatedObject objc_setAssiciateObject 动态获取和设置

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

1.cpu 过载

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

  • 通过减少主线程的负载,将计算任务和数据处理移到后台线程
  • 使用异步方法处理长时间人任务,避免阻塞主线程
  • 减少不必要的计算,使用缓存和预处理等方式

2.GPU 过载

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

  • 避免过度绘制,确保UI层次结构简单
  • 减少复杂的图形操作,优化图形资源的使用
  • 使用合适的图形框架和技术,比如metal和core aniamation ,提高渲染效率

3.内存压力

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

  • 减少内存的分配,及时释放资源
  • 使用autoreleasepool 控制内存使用,避免内存泄漏
  • 确保图形资源的的有效管理,避免不必要纹理和缓存

4.主线程的阻塞

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

  • 避免在主线程进行同步操作,比如网络请求,文件的IO等
  • 将主线程任务移动到后台线程
  • 使用GCD是处理异步任务等

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

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

  • 确保参数的合理,避免动画时间过长或过短
  • 避免动画冲突,确保动画的顺序和逻辑
  • 使用合适的动画工具,如core Animaiton uikit 动画等,确保动画流畅

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

  • time profiler:用于分析CPU时间的消耗 查看方法或函数的对cpu时间的占用
  • Acitvey monitor 监控cpu和内存的总体使用情况
  • allocactions 分析内存分配,查看内存泄漏和过度分配
  • leaks 检测内存泄漏
  • core animation 分析动画的性能,查找动画卡针的原因
  • gpu Frame Capture 分析gpu 性能适合渲染和图形相关的性能分析

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

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

iOS 中线程保活的方式
  • Grand Central Dispatch(GCD)提供了全局队列,允许你将任务分派到系统级线程池。这些线程可以在需要时自动扩展和收缩,并在没有任务时保持活动状态。这是一种高效的线程保活方式。
  • OperationQueue 是一种高级的任务调度方式。可以通过设置队列的最大并发数和任务等待时间,实现线程保活。
  • 通过创建自定义线程,并让线程在特定的条件下保持活动,可以实现线程保活。这通常通过使用 Run Loop 来维持线程的活动状态。

在使用中需要注意

  • 资源的管理 避免浪费和泄漏
  • 线程的安全
  • 避免阻塞主线程

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

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

  • NSLock 适用于简单的通途,是一个互斥锁,适用于防止多个线程同时访问共享资源
  • NSRecursiueLock 如果涉及递归操作或者多次获取,使用的递归锁,允许同一线程多次获取不会应发死锁
  • NSCondition NSConditionLock,适用于基于条件进行同步时 适合生产和消费的模型
    使用wait 或者 waituntilDate 等待条件满足,使用sigal 或者boardcast通知继续执行
#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];  // 释放锁
});

  • os_unfair_lock 高性能的无偏锁,通常是替代就的OSSPinLock
#import <os/lock.h>

os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;

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

  • dispatch Semaphore 需要控制线程并发
#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);  // 释放信号量
});

  • @synchronized
    oc 的同步语法糖
- (void)threadSafeMethod {
    @synchronized(self) {  // 加锁
        // 执行线程安全的操作
        // ...
    }  // 解锁
}

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

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

  • 确保在不需要时通过 invalidate() 来停止计时器
  • 当timer 作为闭包进行回调时,注意循环引用
  • 默认情况下,time 是在起创建runloop上运行,如果对应的runloop 被阻塞或者没有运行会导致计时器不工作
  • nstimer 不是实时精确地,受到其runloop中的其他任务的影响,考虑精度可以使用 dispatchSourceTimer 或者CADisplaylink
  • 如果设置的时间间过短 可能会导致性能下降
  • 避免在后台运行计时器,可能会被系统中止

立即运行:如果需要计时器立即开始运行,使用 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 循环包括三个阶段

  • 等待事件, RL进入休眠,等待事件,不占资源
  • 处理事件,有事件发生,唤醒并处理
  • 回到等待
    runloop 和线程的关系
  • 每条线程都有唯一一个与之对应的runloop
  • runloop 保存在一个全局字典中,key是线程,value 是runloop
  • 线程创建是没有runloop 对象,只有在第一次获取时才能创建 [NSRunLoop currentRunLoop]
  • runloop 会在线程结束是销毁
  • 主线程的runloop 已经自动穿件,子线程默认没有开启

应用

  • 线程保活
  • 解决nstimer 滑动式停止的问题
    nstimer 默认绑定到 NSDefaultRunLoopMode,当用户滚动视图时,runloop 会切换到 UITrackingRunloopModel ,解决办法是将timer添加到NSRunLoopCommonModes
  • 监控应用卡顿
    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);

  • 性能优化
    RunLoop 即将休眠时执行一些非紧急任务,以确保这些任务不会阻塞主线程。这有助于提高应用的响应能力。
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等,

  • userdefault
    一般用于存储用的配置等小型,使用简单,不适用于大量数据或者复杂的数据结构
  • 文件存储
    可以直接将数据保存到文件系统中,通常用于存储比较大的数据 图片等,缺点需要手动管理文件名和路径等,不适合管理复杂的数据结构
  • SQLite
    轻量数据库,支持SQL语法,适合存储结构化数据和执行复杂的查询。需要熟悉SQL语句和数据库操作,手动管理数据库的链接和事务
  • Keychain
    用于存储铭感数据的安全方式,适合存储密码和秘钥等,有点就是数据加密且有系统级别的安全,应用删除后任可保留,可以在不同应用之间共享,使用比较复杂,不适合大量数据
  • coredata
    iOS 上的持久化框架,提供了高级的数据管理功能,适合复杂的数据结构和关系映射,还支持数据迁移和批处理,查询和过滤功能
    在使用上需要
    1.首先需要配置coredata 栈,通常会创建一个单例,来负责配置和管理core data栈,使用NSPersistentContainer 初始化,并且提供viewcontext 来操作
    2.执行操作
    创建数据,需要创建一个NSManagerObject 对象并且插入到NSManagerObjectContext中
    读取数据,使用NSFetchRequest进行查询
    更新数据,需要先查询到对象,在修改属性并保存
    删除数据,需要先查询对象,再从上下文中删除 并保存

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

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

  • 内存优化
    内存分配需要系统进行内存管理和垃圾收集,频繁的内存分配会增加系统负担
    可以通过复用例如tableview collectview ,不要频繁的创建一些变量,及时的释放资源,对数据进行一些缓存等
    适当的使用autoreleaseppol 在循环或者后台线程中需要创建大量的的零时变量时使用,避免内存的过度增长
  • cpu使用优化
    减少循环和冗余计算,尝试使用更高效的算法,降低计算复杂度,避免不必要的循环,地丢等
    将一些耗时操作放到后台线程,避免主线程的阻塞,充分发挥多核处理器的优势
    使用instruments分析方法函数的调用时间
  • ui渲染优化
    减少重绘次数,延迟加载不急需的UI元素,对图片或者其他资源按需加载
    使用instruments工具检测离屏渲染,避免过度使用 圆角 阴影等特性
    减少负责层级的试图层级机构 合并重复试图
    使用shouldResterize 进行缓存
  • 网络性能优化
    合并小的网络请求,肩上网络通信次数,使用批量请求和数据压缩来优化网络
    将频繁访问的数据缓存在本地,避免重复请求
    确保网络请求在后台线程执行,避免主线程的的阻塞
  • 文件或者数据库的优化
    避免频繁的读写文件,对SQL或者coredata 等持久化的数据,使用事务和批处理来优化性能,减少查询次数,使用索引等来优化查询
  • 使用instrument或者日志来,分析性能 具体问题具体解决

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

  • 上文提到的runloop
  • 主线程中添加timer 定期检查时间间隔是否大于预期
  • 使用instruments 分析工具分析
  • 或者一些第三方工具

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 网络请求和响应处理。

使用的技术

  • 基于 NSURLSession:AFNetworking 在 NSURLSession 之上构建,提供更高级的功能。

  • 操作队列:使用 NSOperationQueue 来管理并发网络请求。

  • 序列化:支持 JSON、XML、图片等多种数据格式的序列化和反序列化。
    优缺点

  • 优点:强大的网络功能,支持链式调用,易于使用,社区支持广泛。

  • 缺点:随着 Swift 的流行,其 Objective-C 版本的维护力度下降,但仍有许多应用依赖于此。
    架构设计
    AFNetworking 的架构包括以下主要部分:

  • AFHTTPSessionManager:处理网络请求和响应,基于 NSURLSession。

  • AFURLSessionManager:处理低级别的 URLSession 操作。

  • AFSecurityPolicy:提供 SSL/TLS 安全策略。

  • AFImageDownloader:用于异步下载图片。

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

使用的技术

  • 异步加载:支持异步下载和缓存图片。

  • 缓存机制:提供内存和磁盘缓存,减少网络请求。

  • 图像解码:优化图像解码以提高性能。
    优缺点

  • 优点:简单易用,提供丰富的功能,如图片缓存、异步加载等。

  • 缺点:可能会增加内存占用,特别是在处理大量图片时。
    架构设计
    SDWebImage 的架构包括以下主要部分:

  • SDWebImageManager:管理图片下载和缓存。

  • SDImageCache:提供内存和磁盘缓存。

  • SDWebImageDownloader:处理图片的异步下载。

  • UIImageView+WebCache:为 UIImageView 提供扩展,支持直接加载网络图片。

相关文章

网友评论

      本文标题:iOS 常用面试题

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