[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f2aFbheRuoODjjGfMhtsZSAzYmc83P6I8irOF7L-HF6U":3},{"code":4,"message":5,"data":6},200,"成功",{"id":7,"createdAt":8,"title":9,"content":10,"summary":11,"image":12,"uid":13,"user":14,"categoryId":21,"category":22,"subCategoryId":24,"subCategory":25,"comments":27,"status":17,"reason":28,"notice":28,"visitCount":29,"commentCount":30,"keywords":31},183,"2026-03-03T04:38:22.115Z","iOS 高频面试题（10 道）","# iOS 高频面试题（10 道）\n## 1. OC 中 Category（分类）和 Extension（扩展）的区别？Category 为什么能给类添加属性却不能直接生成成员变量？\n![面试题png](https://image.xinwei.ltd/image1772510914934.png)\n\n(2）Category 不能直接生成成员变量的原因\n- OC 的类结构（struct objc_class）在编译期已确定内存布局，Category 是运行时动态加载的，无法修改原类的内存布局，因此不能直接添加成员变量；\n\n- Category 中用 @property 声明属性时，编译器只会生成 getter/setter 方法的声明，不会自动生成实现和成员变量；\n\n- 解决方法：通过 关联对象（Associated Object） 间接实现属性效果：\n\n```oc\n// Category 中添加属性\n@interface UIView (Extension)\n@property (nonatomic, copy) NSString *identifier;\n@end\n\n@implementation UIView (Extension)\n// 关联对象的 key\nstatic const void *kIdentifierKey = &kIdentifierKey;\n- (void)setIdentifier:(NSString *)identifier {\n    objc_setAssociatedObject(self, kIdentifierKey, identifier, OBJC_ASSOCIATION_COPY_NONATOMIC);\n}\n- (NSString *)identifier {\n    return objc_getAssociatedObject(self, kIdentifierKey);\n}\n@end\n```\n\n## 2. iOS 中的内存管理方式是什么？ARC 原理是什么？MRC 下的引用计数规则？\n（1）核心内存管理方式\niOS 基于引用计数（Reference Counting） 管理内存，分为 MRC（手动引用计数）和 ARC（自动引用计数），核心原则：谁创建谁释放，谁持有谁释放。\n\n（2）ARC 原理\nARC 是编译器特性（非运行时），编译器在编译期自动插入 retain/release/autorelease 代码，无需手动管理，但底层仍基于引用计数：\n- 编译期分析代码上下文，在合适的位置插入内存管理方法；\n- ARC 遵循 “强引用持有对象，弱引用不持有” 的规则；\n- 限制：不能手动调用 retain/release/autorelease，不能使用 NSAutoreleasePool（改用 @autoreleasepool）。\n\n（3）MRC 引用计数规则\n![mrc png](https://image.xinwei.ltd/image1772510914934.png)\n示例（MRC）：\n```oc\nNSString *str = [[NSString alloc] initWithString:@\"test\"]; // 引用计数=1\n[str retain]; // 引用计数=2\n[str release]; // 引用计数=1\n[str release]; // 引用计数=0，对象销毁\n```\n\n## 3. UIKit 中 RunLoop 的作用是什么？RunLoop 与线程的关系？常用的 RunLoop Mode 有哪些？\n（1）RunLoop 核心作用\nRunLoop 是 “事件循环器”，核心功能是让线程在有任务时处理任务，无任务时休眠（避免占用 CPU），主要职责：\n- 处理事件（触摸、定时器、网络请求）；\n- 管理线程生命周期（如主线程 RunLoop 一直运行，子线程默认无 RunLoop）；\n- 处理 UI 刷新（CATransaction 提交后触发 RunLoop 刷新 UI）；\n- 延迟执行（performSelector:withObject:afterDelay: 依赖 RunLoop）。\n\n（2）RunLoop 与线程的关系\n- 一一对应：一个线程对应一个 RunLoop，RunLoop 存储在全局字典中，线程为 key，RunLoop 为 value；\n- 懒加载：RunLoop 不会随线程创建而创建，调用 [NSRunLoop currentRunLoop] 时才创建；\n- 主线程 RunLoop 自动创建且一直运行，子线程 RunLoop 需要手动启动，且运行后需手动停止。\n\n（3）常用 RunLoop Mode\nRunLoop Mode 是 “事件过滤器”，同一时间 RunLoop 只能处于一个 Mode，仅处理该 Mode 下的事件：\n- kCFRunLoopDefaultMode：默认模式，处理 UI 事件、定时器、常规任务（主线程默认处于该 Mode）；\n- UITrackingRunLoopMode：跟踪模式，处理滑动 / 滚动事件（如 UIScrollView 滑动时，RunLoop 切换到该 Mode，默认 Mode 事件暂停）；\n- kCFRunLoopCommonModes：伪模式，是 Default + Tracking 的集合（设置该 Mode 后，两种场景都能处理事件）。\n\n实战场景：UIScrollView 滑动时定时器暂停，可将定时器加入 CommonModes 解决：\n```oc\nNSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {\n    NSLog(@\"定时器执行\");\n}];\n[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];\n```\n\n## 4. Swift 中值类型（Struct/Enum）和引用类型（Class）的区别？各自的适用场景？\n（1）核心区别\n![](https://image.xinwei.ltd/struct_enum.png)\n（2）适用场景\n- 值类型：\n适合存储独立、不可共享的数据（如模型数据、基础类型、工具类）：\n基础数据结构：Int/String/Array/Dictionary（Swift 中均为 Struct）；\n业务模型：如 UserModel（存储用户 ID、姓名等，避免多地方修改导致数据不一致）；\n轻量工具：如 Size（存储宽高）、Point（存储坐标）。\n- 引用类型：\n适合需要共享状态、继承、生命周期管理的场景：\n- [x] UI 组件：UIViewController/UIView（需继承、共享实例）；\n- [x] 业务管理器：如 NetworkManager（单例，全局共享网络请求逻辑）；\n- [x] 需析构的对象：如 FileManager（需在 deinit 中关闭文件句柄）。\n\n\n## 5. iOS 中 tableView/collectionView 的性能优化方式有哪些？\n### 5.1 复用机制优化：\n- 必须使用 dequeueReusableCell(withIdentifier:) 复用单元格，避免重复创建；\n- 提前注册单元格（register(_:forCellWithReuseIdentifier:)），避免运行时查找 nib 文件；\n- 自定义单元格时，将子视图封装为属性，避免重复调用 viewWithTag:（耗时）。\n\n### 5.2 布局计算优化：\n- 提前计算并缓存单元格高度（如在模型中存储 cellHeight，避免 heightForRowAt 中重复计算）；\n- iOS 8+ 启用 estimatedRowHeight + automaticDimension 自动计算高度时，需固定约束，减少布局遍历；\n- 避免在 cellForRowAt/willDisplayCell 中执行复杂计算（如数据转换、正则匹配）。\n\n### 5.3 渲染优化：\n- 图片优化：使用异步加载（SDWebImage）、缓存图片、压缩图片尺寸（匹配单元格大小）；\n- 减少离屏渲染：避免设置 cornerRadius+masksToBounds（改用贝塞尔曲线绘制）、避免阴影（shadowPath 优化）；\n\n### 5.4 数据加载优化：\n- 分页加载：滚动到底部时加载下一页数据，避免一次性加载大量数据；\n- 预加载：提前加载下一页数据（如滚动到倒数 10 行时）；\n- 异步处理：数据解析、模型转换放在子线程，主线程仅负责渲染。\n\n### 5.5 其他优化：\n- 关闭不必要的动画：如 cell.selectionStyle = .none（无选中样式）；\n- 使用 UITableViewCell 的 prepareForReuse 方法重置单元格状态（避免数据残留）；\n- 大列表使用 UICollectionView 替代 UITableView（布局更灵活，性能更优）；\n- 启用 shouldRasterize（光栅化）：对复杂单元格，将其渲染为位图缓存，减少重复绘制（注意：仅适合静态单元格）。\n\n## 6. iOS 中的代理（Delegate）和通知（Notification）的区别？各自的适用场景？\n（1）核心区别\n![delegate png](https://image.xinwei.ltd/delegate.png)\n（2）适用场景\n- Delegate：\n  适合需要双向通信、实时响应、类型安全的场景：\n- [x] 控件交互：UITableViewDelegate（点击单元格、返回高度）、UIScrollViewDelegate（滚动监听）；\n- [x] 页面传值：子控制器通过代理向父控制器传递数据（如修改后的数据）；\n- [x] 回调结果：网络请求类通过代理返回请求结果（同步处理）。\n- Notification：\n适合跨层级、解耦、一对多的通信场景：\n- [x] 全局状态变更：如用户登录 / 退出、网络状态变化（通知所有相关页面刷新）；\n- [x] 跨组件通信：如首页、个人中心同时监听购物车数量变化；\n- [x] 系统事件监听：如键盘弹出 / 收起（UIKeyboardWillShowNotification）。\n\n注意：通知必须在 deinit 中注销，避免崩溃：\n```swift\n// Swift 注销通知\ndeinit {\n    NotificationCenter.default.removeObserver(self)\n}\n```\n\n## 7. iOS 中 Block 的原理是什么？Block 的循环引用如何解决？\n（1）Block 原理\nBlock 是 OC 中的 “闭包”，本质是封装了函数调用和函数环境的 OC 对象（底层是 struct __block_impl 结构体）：\n- Block 存储在栈 / 堆 / 全局区：\n全局 Block：捕获不到外部变量，存储在全局区（如 ^{NSLog(@\"test\");}）；\n栈 Block：捕获外部变量，存储在栈区（生命周期由系统管理，易销毁）；\n堆 Block：栈 Block 调用 copy 后，转移到堆区（手动管理生命周期）。\n- Block 捕获外部变量：\n值捕获：基本数据类型（int/NSString）捕获值，Block 内修改不影响外部；\n引用捕获：对象类型捕获指针，需注意循环引用；\n__block 修饰：让变量变为可修改（栈变量转为堆变量）。\n\n（2）Block 循环引用原因及解决\n- 原因：Block 会强引用捕获的对象，若对象同时强引用 Block（如控制器持有 Block，Block 捕获 self），则形成循环引用，导致对象无法释放。\n- 解决方法：\n1. 弱引用 + 强引用（常用）：\n```oc\n// OC\n__weak typeof(self) weakSelf = self;\nself.block = ^{\n    __strong typeof(weakSelf) strongSelf = weakSelf; // 避免self提前释放\n    if (strongSelf) {\n        [strongSelf doSomething];\n    }\n};\n```\n```swift\n// Swift\nweak var weakSelf = self\nself.block = { [weak self] in\n    guard let strongSelf = self else { return }\n    strongSelf.doSomething()\n}\n```\n2. 使用 __unsafe_unretained：类似 weak，但不会自动置空（慎用，可能野指针）；\n3. 打破循环：使用完成后将 Block 置为 nil（如网络请求完成后，self.block = nil）；\n4. 捕获局部变量：若 Block 仅在方法内使用，捕获局部变量（非 self 属性），避免循环。\n\n## 8. iOS 中的多线程方案有哪些？GCD 的核心概念和常用方法？\n（1）iOS 多线程方案对比\n![thread png](https://image.xinwei.ltd/thread.png)\n\n（2）GCD 核心概念\n- 队列（Queue）：存储任务的容器，分为：\n串行队列（Serial）：任务按顺序执行，只有一个线程；\n并发队列（Concurrent）：任务同时执行，多个线程（由系统管理）；\n主队列（Main Queue）：串行队列，运行在主线程，处理 UI 操作。\n\n- 任务（Task）：分为同步（sync）和异步（async）：\n同步任务：阻塞当前线程，任务执行完才返回；\n异步任务：不阻塞当前线程，任务后台执行。\n\n（3）GCD 常用方法\n1.异步并发（常用，非 UI 任务）：\n```swift\n// Swift\nlet concurrentQueue = DispatchQueue(label: \"com.example.concurrent\", attributes: .concurrent)\nconcurrentQueue.async {\n    // 子线程执行耗时任务（如网络请求、数据解析）\n    print(\"任务1：\\(Thread.current)\")\n}\nconcurrentQueue.async {\n    print(\"任务2：\\(Thread.current)\")\n}\n```\n\n2. 异步串行（顺序执行任务）：\n```swift\nlet serialQueue = DispatchQueue(label: \"com.example.serial\")\nserialQueue.async { print(\"任务1\") }\nserialQueue.async { print(\"任务2\") } // 任务1执行完才执行\n```\n\n3. 主线程更新 UI：\n```swift\nDispatchQueue.main.async {\n    self.label.text = \"更新UI\" // 必须在主线程执行\n}\n```\n\n4. 延时执行：\n```swift\nDispatchQueue.main.asyncAfter(deadline: .now() + 2) {\n    print(\"2秒后执行\")\n}\n```\n\n5. 栅栏函数（控制并发队列的任务顺序）：\n```swift\nconcurrentQueue.async { print(\"任务1\") }\nconcurrentQueue.async { print(\"任务2\") }\n// 栅栏：等待前面任务执行完，执行自己，再执行后面任务\nconcurrentQueue.async(flags: .barrier) { print(\"栅栏任务\") }\nconcurrentQueue.async { print(\"任务3\") }\n```\n\n## 9. iOS 中 App 的启动流程是什么？如何优化 App 启动速度？\n（1）App 启动流程（冷启动）\n冷启动是 App 从完全关闭到打开的过程，分为 3 个阶段：\n- dyld 阶段（动态链接器）：\n加载可执行文件（Mach-O）；\n加载动态库（系统库如 UIKit、自定义库）；\n重定位符号，绑定依赖。\n- runtime 阶段：\n初始化 OC 运行时（objc_init）；\n加载类、分类、方法（+load 方法执行）；\n初始化全局变量、静态变量。\n- 主线程初始化阶段：\n执行 UIApplicationMain；\n加载 AppDelegate，执行 application:didFinishLaunchingWithOptions:；\n加载根控制器，渲染首屏 UI。\n\n（2）启动速度优化\n核心思路：“减少加载项、延迟执行、异步处理”：\n- dyld 阶段优化：\n减少动态库数量（合并静态库，移除无用动态库）；\n启用 bitcode（编译器优化二进制）；\n减少符号表（strip 无用符号）。\n\n- runtime 阶段优化：\n避免在 +load 方法中执行耗时操作（改用 +initialize 或延迟执行）；\n减少类 / 分类数量（合并无用分类）；\n优化全局变量 / 静态变量（避免初始化耗时）。\n\n\n- 主线程初始化阶段优化：\n延迟加载非首屏资源（如广告、统计、第三方 SDK）；\n异步初始化第三方 SDK（如埋点、推送，放在子线程）；\n首屏 UI 简化：使用骨架屏替代复杂视图，首屏只加载核心控件；\n懒加载：控制器的子视图、数据请求等延迟到 viewDidAppear 执行。\n\n\n- 其他优化：\n启用启动优化（Xcode 的 Build Settings 中开启 Optimization Level）；\n压缩资源（图片、字体，减少加载时间）；\n使用 Instruments 的 Time Profiler 分析启动耗时，定位瓶颈。\n\n\n## 10. iOS 中常见的崩溃类型有哪些？如何捕获和排查崩溃？\n（1）常见崩溃类型\n- 野指针崩溃（EXC_BAD_ACCESS）：\n访问已释放的对象（如通知未注销、Block 循环引用导致对象提前释放）；\n访问空指针（如 nil 对象调用不存在的方法）。\n\n- 数组越界（NSRangeException）：\n访问数组索引超出范围（如 array[10]，但数组只有 5 个元素）。\n\n\n- 字典键值异常：\n字典设置 nil 键 / 值（OC 中字典键值不能为 nil，Swift 可）；\n访问不存在的键（虽不崩溃，但返回 nil，可能导致后续逻辑异常）。\n\n\n- 死锁（Deadlock）：\n主线程同步等待子线程，子线程又等待主线程（如 GCD 同步任务入主队列）。\n\n\n- 内存溢出（OOM）：\n加载大量图片 / 数据，内存占用超过系统限制，被系统杀死。\n\n\n- 类型转换失败（NSInvalidArgumentException）：\n如将 NSString 强制转为 NSNumber。\n\n\n（2）崩溃捕获与排查\n- 开发阶段排查：\nXcode 控制台：直接查看崩溃日志（含崩溃类型、调用栈）；\nInstruments：Zombies 工具检测野指针，Leaks 工具检测内存泄漏；\n开启 Zombie Objects（Xcode -> Edit Scheme -> Diagnostics -> Enable Zombie Objects），定位野指针。\n\n- 线上崩溃捕获：\n第三方工具：集成 Crashlytics（Fabric）、Bugly、友盟统计，自动捕获崩溃日志；\n自定义崩溃捕获：通过 NSSetUncaughtExceptionHandler 捕获 OC 异常，signal 捕获信号量崩溃：\n```oc\n// 捕获 OC 异常\nNSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);\nvoid UncaughtExceptionHandler(NSException *exception) {\n    // 记录崩溃信息：调用栈、异常原因\n    NSArray *callStack = [exception callStackSymbols];\n    NSString *reason = [exception reason];\n    // 上传到服务器\n}\n```\n\n- 崩溃排查步骤：\n\n查看崩溃日志的 Exception Type（崩溃类型）和 Exception Message（原因）；\n\n分析调用栈（Thread 0 Crashed 是崩溃线程），定位崩溃代码行；\n\n复现崩溃场景（如特定操作、特定设备），逐步排查；\n\n针对野指针：检查内存管理（ARC 引用、代理是否为 weak）；\n\n针对逻辑崩溃：添加断言（NSAssert）、边界检查（数组 / 字典访问前判断范围）。\n\n以上。\n\n\n\n\n","这里总结一下iOS的高频面试题，希望对正在面试的iOS开发者有所帮助，不一定全对，可自行判读哈。","https://image.xinwei.ltd/image1772510914934.png",525978185445445,{"phone":15,"userId":13,"nickName":16,"vipType":17,"avatar":18,"sign":19,"createdAt":20},"16100000003","上班墨鱼",1,"https://image.xinwei.ltd/bbd06233d9b5c67c9f8ddd35b63cb81f1710561355517.jpeg","没有什么缺点，大家低调点","2024-03-16T00:30:44.932Z",2,{"id":21,"name":23},"IT技术",3,{"id":24,"name":26,"parentId":21},"iOS",[],"",70,0,"ios面试题, iOS swift面试题,iOS高频面试题,面试题,ios面试题"]