iOS面试100题

一、Objective-C & Swift 基础

1. ★ Objective-C 中 #import#include 的区别?

答案

  • #import:自动防止重复包含,OC推荐使用。
  • #include:需配合 #ifndef 避免重复包含,C/C++中使用。

2. Swift 中 structclass 的核心区别?

答案

  • struct:值类型,栈存储,不可继承。
  • class:引用类型,堆存储,支持继承。

3. @property (nonatomic, copy)copy 的作用?

答案
防止可变对象(如 NSMutableString)被篡改,深拷贝为不可变对象。


4. Swift 的 optional 本质是什么?

答案
枚举类型:

enum Optional<Wrapped> { case none; case some(Wrapped) }

5. __block 关键字在 Block 中的作用?

答案
允许 Block 内修改外部局部变量(默认捕获的变量是只读的)。


6. ★ weakunowned 的区别?

答案

  • weak:对象释放后自动置 nil,必须为 Optional。
  • unowned:对象释放后不置 nil,访问会崩溃,性能稍高。

7. Swift 中 final 关键字的作用?

答案
禁止类或方法被继承/重写,提高性能(静态派发)。


8. @synthesize@dynamic 的区别?

答案

  • @synthesize:编译器自动生成 getter/setter 和实例变量。
  • @dynamic:开发者需手动实现存取方法(如 Core Data 模型)。

9. performSelector: 为什么可能引发内存问题?

答案
可能因 ARC 无法识别方法返回值类型,导致内存泄漏或过早释放。


10. Swift 协议中 associatedtype 的作用?

答案
定义协议中的泛型占位类型,允许实现时指定具体类型:

protocol Container { associatedtype Item; func add(_ item: Item) }

二、内存管理

11. ★ ARC 下哪些情况会导致循环引用?

答案

  • 对象互相强引用(如 A 强持有 BB 强持有 A)。
  • Block 强持有 selfself 强持有 Block。

12. NSTimer 如何避免内存泄漏?

答案

  • 使用 weak self
  • 手动调用 invalidate() 并置 nil

13. autoreleasepool 的使用场景?

答案
循环中创建大量临时对象时减少内存峰值(如解析 JSON 数组)。


14. ★ CADisplayLinkNSTimer 的内存管理区别?

答案

  • CADisplayLink:与屏幕刷新率同步,需手动移除 RunLoop
  • NSTimer:需手动 invalidate,否则可能泄漏。

15. CFRelease 在 ARC 环境是否需要调用?

答案
需要,Core Foundation 对象不受 ARC 管理。


16. __unsafe_unretained 的使用风险?

答案
对象释放后指针不置 nil,访问会野指针崩溃。


17. 如何检测 iOS 应用的内存泄漏?

答案

  • Xcode Debug Memory Graph。
  • Instruments 的 Leaks 工具。

18. ★ dealloc 方法中不能调用什么?

答案

  • 不能调用 [super dealloc](ARC 禁止)。
  • 避免访问 __weak 变量(可能已释放)。

19. NSMapTableNSDictionary 的内存特性区别?

答案

  • NSMapTable:可配置弱引用键/值(如 NSPointerFunctionsWeakMemory)。
  • NSDictionary:始终强引用键和值。

20. UIGraphicsBeginImageContext 后是否需要手动释放内存?

答案
需要,调用 UIGraphicsEndImageContext() 释放上下文。

三、多线程与GCD(续)

21. GCD的dispatch_barrier_async作用?

答案
在并发队列中充当“栅栏”,确保屏障前的任务全部完成后,才执行屏障后的任务。常用于多线程读写安全。

let queue = DispatchQueue(label: "com.test", attributes: .concurrent)  
queue.async { /* 读操作 */ }  
queue.async(flags: .barrier) { /* 写操作 */ } // 确保写操作独占  

22. NSOperation相比GCD的优势?

答案

  • 支持任务依赖(addDependency)。
  • 可取消(cancel())、暂停(isSuspended)和设置优先级(queuePriority)。
  • 更面向对象,适合复杂任务管理。

23. 多线程读写问题如何解决?

答案

  • GCD方案dispatch_barrier_async + 并发队列。
  • 锁方案os_unfair_lock(iOS 10+)或 NSLock
  • Swift方案Actor(Swift 5.5+)。

24. 主队列(main queue)和全局队列(global queue)的区别?

答案

  • 主队列:串行,UI更新必须在此队列执行。
  • 全局队列:并发,分优先级(.default.background等),适合耗时任务。

25. dispatch_once的实现原理?

答案
通过静态变量和原子操作确保代码块仅执行一次(Swift中废弃,改用静态属性或lazy)。

class Singleton {  
    static let shared = Singleton() // 替代dispatch_once  
}  

四、网络通信

31. HTTPS握手流程?

答案

  1. ClientHello:客户端发送支持的加密套件列表。
  2. ServerHello:服务端选择加密套件并返回证书。
  3. 密钥交换:客户端验证证书后生成预主密钥(Premaster Secret)。
  4. 加密通信:双方使用对称加密(如AES)传输数据。

32. NSURLSessiondelegatecompletionHandler区别?

答案

  • Delegate:适合大文件下载(实时回调进度),需遵守协议。
  • CompletionHandler:简单请求,回调在主线程,代码更紧凑。

33. 如何优化HTTP/2的多路复用?

答案

  • 减少域名分片(避免DNS查询)。
  • 使用长连接(Keep-Alive)。
  • 压缩请求头(HPACK算法)。

34. TCP粘包问题如何解决?

答案

  • 固定长度:每条消息定长(如1024字节)。
  • 分隔符:用特殊字符(如\n)分割消息。
  • 长度前缀:消息头声明长度(如4字节表示消息体大小)。

35. WebSocketHTTP的长连接区别?

答案

  • HTTP长连接:基于TCP,仍需每次携带完整Header。
  • WebSocket:全双工通信,首次握手后无需重复Header,适合实时聊天。

五、UI与动画

41. layoutIfNeededsetNeedsLayout的区别?

答案

  • setNeedsLayout:标记视图需要更新布局,异步执行。
  • layoutIfNeeded:立即强制更新布局(常用于动画中同步布局)。

42. drawRect:的性能优化技巧?

答案

  • 避免频繁调用:用CALayer替代自定义绘制。
  • 限制绘制区域:setNeedsDisplayInRect(_:)
  • 离屏渲染优化:使用shouldRasterize缓存复杂图层。

43. UITableView复用机制原理?

答案
通过reuseIdentifier从复用池获取闲置Cell,减少频繁创建销毁。

// 复用池逻辑伪代码  
func dequeueReusableCell(withIdentifier: String) -> UITableViewCell? {  
    if let cell = reusePool[identifier]?.popLast() {  
        return cell // 从池中取出  
    }  
    return nil  
}  

44. 离屏渲染(Offscreen Rendering)的触发条件?

答案

  • 圆角+裁剪(cornerRadius + masksToBounds)。
  • 阴影(shadowPath未设置时)。
  • 高斯模糊(UIVisualEffectView)。

45. Core AnimationUIView动画的区别?

答案

  • UIView动画:基于CALayer的封装,自动处理图层属性。
  • Core Animation:更底层,可控制动画暂停、反转、插值。

六、性能优化

51. InstrumentsTime Profiler的使用技巧?

答案

  • 勾选Hide System Libraries:聚焦App代码耗时。
  • 检查Heavy Stack Trace:定位耗时函数调用链。

52. 如何降低App启动时间?

答案

  • 减少动态库:合并或静态化。
  • 延迟初始化:非必要任务放到首屏渲染后。
  • 二进制重排:调整函数布局减少Page Fault。

六、性能优化(续)

53. UIImage加载大图的内存优化方案?

答案

  • 分块加载:使用CGImageSourceCreateThumbnailAtIndex生成缩略图。
  • 降采样:通过CGImageSource设置kCGImageSourceShouldCacheImmediatelyfalse
  • 内存映射:用mmap读取文件(适合极大型图片)。
let options = [kCGImageSourceShouldCache: false] as CFDictionary  
let source = CGImageSourceCreateWithURL(url as CFURL, options)!  

54. ★ UITableView滚动卡顿的排查步骤?

答案

  1. 检查离屏渲染:用Core Animation工具检测黄色警告。
  2. 分析主线程阻塞Time Profiler定位耗时方法。
  3. 优化Cell布局:避免动态计算高度(缓存systemLayoutSizeFitting结果)。
  4. 减少图层混合:确保Cell背景色不透明(backgroundColor = .white)。

55. ARC环境下如何减少内存峰值?

答案

  • 局部自动释放池:在循环内嵌套autoreleasepool
  • 懒加载:延迟初始化大对象。
  • 图片缓存:用NSCache替代全局变量存储图片。

56. NSDateFormatter的性能优化技巧?

答案

  • 全局缓存:静态共享一个NSDateFormatter实例(线程安全需加锁)。
  • 设置固定Locale:避免系统语言切换导致性能损耗。
static let sharedFormatter: DateFormatter = {  
    let formatter = DateFormatter()  
    formatter.locale = Locale(identifier: "en_US_POSIX")  
    return formatter  
}()  

57. 如何检测CPU使用率过高?

答案

  • InstrumentsCPU Profiler工具查看各线程占比。
  • 代码监控
    mach_task_self(), &threads)  
    thread_info(threads[i], THREAD_BASIC_INFO, &info, &count)  
    

58. ★ 离屏渲染的检测与优化方法?

答案

  • 检测:Xcode调试面板勾选Color Offscreen-Rendered(黄色标记)。
  • 优化
    • CAShapeLayer绘制圆角替代cornerRadius
    • 预渲染静态内容为位图(UIGraphicsBeginImageContext)。

59. WebView白屏问题的解决方案?

答案

  • 内存不足:监听didReceiveMemoryWarning,清理缓存。
  • 进程崩溃:WKWebView独立进程,需实现webViewWebContentProcessDidTerminate恢复。
  • 资源加载失败:检查本地HTML文件路径或CDN可用性。

60. APM(性能监控)需要采集哪些指标?

答案

  • 基础指标:CPU、内存、FPS、启动耗时。
  • 网络指标:请求成功率、延迟、流量消耗。
  • Crash:堆栈符号化、发生场景(前后台)。

七、架构与设计模式

61. ★ MVC的缺点及改进方案?

答案

  • 缺点:Controller臃肿、单元测试困难。
  • 改进
    • MVVM:将业务逻辑抽到ViewModel
    • VIPER:进一步拆分职责(Router、Interactor)。

62. MVVMViewModel的职责边界?

答案

  • 负责:数据格式化、网络请求、业务逻辑。
  • 不负责:直接操作UI或持有View引用。

63. 如何实现组件化通信?(如URL Router

答案

  • URL路由:定义统一Scheme(如app://detail?id=1)。
  • 协议抽象:各组件暴露接口协议,依赖注入实现类。
protocol UserServiceProtocol { func getUser(id: Int) -> User }  

64. ★ 单例模式的线程安全实现?

答案

class Singleton {  
    static let shared = Singleton()  
    private init() {} // 防止外部实例化  
}  

65. DelegateNotification的使用场景区别?

答案

  • Delegate:1对1精准回调(如UITableViewDelegate)。
  • Notification:1对多广播(如全局事件通知)。

66. 如何设计一个缓存模块?(如NSCache替代方案)

答案

  • 内存缓存NSCache(自动清理)。
  • 磁盘缓存FileManager + 序列化(如Codable)。
  • LRU策略:链表+字典实现最近最少使用淘汰。

67. ★ 响应式编程(如Combine)的核心思想?

答案

  • 数据流:将事件、状态封装为流(Publisher)。
  • 声明式:通过subscribe自动响应数据变化。

68. 依赖注入在iOS中的实践?

答案

  • 构造函数注入
    class UserService {  
        init(api: APIClientProtocol) { ... }  
    }  
    
  • 属性注入:通过setter方法动态替换依赖。

69. 如何避免Massive ViewController

答案

  • 职责拆分
    • 数据源抽离为单独类(如TableViewDataSource)。
    • 业务逻辑移到PresenterInteractor

70. Clean Architecture的分层逻辑?

答案

  1. Entities:核心业务模型。
  2. Use Cases:业务规则封装。
  3. Interface Adapters:转换数据格式(如DTO)。
  4. Frameworks:UI、数据库等具体实现。

八、系统与安全

71. ★ 沙盒机制的目录结构及权限?

答案

  • Documents:用户数据(iCloud同步)。
  • Library/Caches:缓存(系统可清理)。
  • tmp:临时文件(App重启可能删除)。

72. 如何防止API数据被抓包?

答案

  • 证书绑定URLSessionDelegate中校验服务器证书指纹。
  • 请求加密:敏感参数AES加密。

73. KeychainUserDefaults的安全区别?

答案

  • Keychain:加密存储(适合密码、Token)。
  • UserDefaults:明文存储(仅适合非敏感配置)。

74. ★ 越狱检测的常见方法?

答案

  • 文件检测:检查/Applications/Cydia.app是否存在。
  • API检测:调用stat函数判断动态库注入。

75. App签名与证书的工作原理?

答案

  1. 开发者生成私钥,苹果用公钥验证签名。
  2. 打包时嵌入embedded.mobileprovision文件。

76. 如何实现敏感逻辑的代码混淆?

答案

  • LLVM混淆:编译时重命名符号(如-mllvm -fla)。
  • 字符串加密:运行时动态解密关键字符串。

77. ★ Biometric(指纹/面容)的集成步骤?

答案

  1. 导入LocalAuthentication框架。
  2. 调用LAContext().evaluatePolicy()

78. Universal Link的配置流程?

答案

  1. 配置apple-app-site-association文件到HTTPS服务器。
  2. Xcode中开启Associated Domains能力。

79. 如何防御SQL注入?

答案

  • 参数化查询
    let query = "SELECT * FROM Users WHERE id = ?"  
    sqlite3_bind_int(stmt, 1, userID)  
    

80. iOS系统权限的动态申请策略?

答案

  • 按需申请:首次使用功能时弹出权限对话框。
  • 引导设置:被拒绝后跳转UIApplication.openSettingsURLString

iOS面试问题答案整理(第81题到第100题)

九、Swift 高级特性(10题)

81. ★ 泛型在协议中的使用(associatedtype)?

答案
associatedtype 是 Swift 协议中用于定义泛型关联类型的关键字。它允许协议声明一个或多个占位类型,由遵循该协议的类型在实现时指定具体类型。例如:

protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
}

遵循该协议的类型需要指定 Item 的具体类型,如 IntString


82. Result 类型的作用?

答案
Result 是 Swift 标准库中的枚举类型,用于封装异步操作的成功或失败结果。其定义为:

public enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

它取代了传统的回调方式(如 (T?, Error?)),强制开发者显式处理成功和失败两种情况,避免遗漏错误处理。


83. Swift 中 反射(Mirror)的局限性?

答案
Mirror 是 Swift 的反射机制,用于在运行时检查类型信息,但有以下局限性:

  1. 性能较差,不适合高频调用。
  2. 只能获取有限的类型信息(如属性名和值),无法修改属性或调用方法。
  3. 不支持获取泛型类型的具体参数信息。
  4. 无法访问 privatefileprivate 成员。

84. ★ @escaping@nonescaping 的区别?

答案

  • @nonescaping(默认):闭包在函数返回前必须被调用,不能逃逸出函数作用域。编译器可以优化内存管理。
  • @escaping:闭包可能在函数返回后被调用(如存储为属性或异步回调)。需要显式标记,且闭包内引用 self 时必须捕获(如 [weak self])。

85. Property Wrapper 的实现原理?

答案
属性包装器(@propertyWrapper)通过包装属性的存储逻辑来复用代码。例如:

@propertyWrapper
struct Trimmed {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set { value = newValue.trimmingCharacters(in: .whitespaces) }
    }
}

使用时:

@Trimmed var name: String // 自动去除首尾空格

86. Swift 与 Objective-C 混编的注意事项?

答案

  1. 兼容性:Swift 类需继承 NSObject 或标记 @objc 才能在 Objective-C 中使用。
  2. 命名冲突:避免使用 Swift 特有语法(如元组)暴露给 Objective-C。
  3. 桥接文件:通过 {ProjectName}-Bridging-Header.h 导入 Objective-C 头文件。
  4. 类型映射:如 Swift 的 String 对应 Objective-C 的 NSString

87. ★ async/await 如何替代回调地狱?

答案
async/await 是 Swift Concurrency 的特性,通过同步写法处理异步代码。例如:

func fetchData() async throws -> Data {
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}

替代传统嵌套回调,代码更简洁,错误处理更直观(直接使用 try/catch)。


88. Swift Concurrency 的 Actor 作用?

答案
Actor 是 Swift 中用于线程安全的类型,通过隔离数据访问避免竞态条件:

actor Counter {
    private var value = 0
    func increment() { value += 1 }
}

访问 Actor 的方法必须使用 await,确保串行执行。


89. Lazy 属性的线程安全性?

答案
Swift 的 lazy 属性不是线程安全的。首次访问时若多个线程同时调用,可能导致初始化多次。解决方案:

  1. 使用 DispatchQueue 加锁。
  2. 改用 static let(线程安全的单例初始化)。

90. Swift 中 KVO 的实现方式?

答案
Swift 通过 @objc dynamic 标记属性和继承 NSObject 来支持 KVO:

class Observed: NSObject {
    @objc dynamic var value: String = ""
}
let observer = observed.observe(\.value, options: [.new]) { _, change in
    print("New value: \(change.newValue ?? "")")
}

十、综合场景(10题)

91. ★ 如何实现 Dark Mode 动态切换?

答案

  1. 颜色与图片:使用 UIColor(dynamicProvider:)UIImageAsset 定义动态资源。
  2. 监听模式变化:通过 traitCollectionDidChange(_:) 响应系统模式变化。
  3. 强制切换
    window.overrideUserInterfaceStyle = .dark // 或 .light
    
  4. 自定义模式:结合 UserDefaults 存储用户偏好。

92. 国际化 的完整实现流程?

答案

  1. 字符串本地化:将文本放入 Localizable.strings 文件,按语言分组(如 en.lprojzh-Hans.lproj)。
  2. 图片本地化:在 Assets.xcassets 中为图片设置不同语言版本。
  3. 代码调用
    NSLocalizedString("key", comment: "")
    
  4. 动态切换语言:需重启 App 或手动刷新 UI。

93. App 从点击图标到启动完成的流程?

答案

  1. 系统加载:内核加载 Mach-O 可执行文件,动态链接器加载依赖库。
  2. 运行时初始化:调用 +load+initialize 方法(Swift 中已废弃)。
  3. main() 函数:执行 UIApplicationMain,创建 AppDelegate
  4. 生命周期回调:调用 application(_:didFinishLaunchingWithOptions:)
  5. 主线程 RunLoop:启动事件循环,显示 LaunchScreen,加载根视图控制器。

94. ★ Crash 日志的符号化解析步骤?

答案

  1. 获取日志:从设备(Xcode -> Window -> Devices)或第三方平台(如 Firebase)。
  2. 匹配符号文件(dSYM):确保 UUID 一致(通过 dwarfdump --uuid 检查)。
  3. 符号化
    atos -arch arm64 -o AppName.app.dSYM -l 0x100000000 0x100003000
    
  4. 工具辅助:使用 Xcode 的 symbolicatecrash 脚本或第三方服务。

95. 如何实现 增量更新(热更新)?

答案

  1. JavaScriptCore/JSI:通过 React Native 或自研引擎下发 JS 脚本。
  2. 二进制补丁:使用 bsdiff 生成差异文件,客户端合并(需审核合规)。
  3. 服务器控制:动态配置资源或业务逻辑(如 A/B 测试)。
  4. 注意事项:苹果禁止下载可执行代码,需确保符合 App Store 政策。

96. App Clips 的开发注意事项?

答案

  1. 大小限制:压缩资源至 10MB 以内。
  2. 功能精简:仅保留核心功能(如支付、扫码)。
  3. 关联体验:配置 Associated DomainsUniversal Links
  4. 生命周期:数据存储使用 NSUserActivity 或共享容器。

97. ★ Widget 的数据共享方案?

答案

  1. App Groups:启用 Capability,使用 UserDefaults(suiteName:)FileManager 共享容器。
  2. Timeline Provider:通过 Intent 配置动态数据。
  3. 网络请求:Widget 内直接发起请求(需精简数据量)。
  4. 刷新策略:合理设置 Timeline 的刷新频率。

98. 如何优化 App Store 审核通过率?

答案

  1. 遵守指南:避免私有 API、隐藏功能。
  2. 元数据完整:提供清晰的截图和描述。
  3. 测试账号:审核人员可访问所有功能。
  4. 崩溃修复:确保提交版本稳定。
  5. 快速响应:若被拒,针对性修改后重新提交。

99. SwiftUIUIKit 的混合开发技巧?

答案

  1. 嵌入 SwiftUI:使用 UIHostingController 包装 View
    let vc = UIHostingController(rootView: ContentView())
    
  2. 嵌入 UIKit:通过 UIViewRepresentableUIViewControllerRepresentable 包装。
  3. 数据同步:使用 @ObservedObject@StateObject 共享状态。

100. 如何设计一个 高性能日志系统

答案

  1. 异步写入:使用 DispatchQueue 分离日志记录与主线程。
  2. 分级过滤:支持 ErrorDebug 等级别,按需输出。
  3. 内存缓冲:批量写入磁盘,减少 I/O 操作。
  4. 崩溃保护:通过 mmap 或原子操作避免日志损坏。
  5. 符号化:集成崩溃堆栈的离线解析功能。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容