iOS 面试基础题
一、Objective-C 基础
问题: Objective-C 的动态特性有哪些?与 Swift 的区别是什么?
答案:- 动态特性:
- 运行时(Runtime):支持方法动态调用(objc_msgSend)、动态添加方法、动态交换方法(method swizzling)。
- KVC/KVO:键值编码和键值观察,动态访问和监听属性。
- Category:运行时扩展类功能。
- 与 Swift 的区别:
- Swift 是静态类型语言,编译时优化更好,性能更高。
- Objective-C 依赖运行时,灵活但性能稍逊。
- Swift 的类型安全更强,Optional 避免空指针问题。
- 动态特性:
问题: 解释 Objective-C 的消息传递机制。
答案:- Objective-C 使用消息传递(message passing)而非直接方法调用。
- 核心函数
objc_msgSend
将消息发送给对象,运行时查找实现。 - 示例:
[self performSelector:@selector(doSomething) withObject:nil];
- 注意:
- 若方法不存在,可能导致崩溃(
unrecognized selector
)。 - 使用
respondsToSelector:
检查方法存在性。
- 若方法不存在,可能导致崩溃(
问题: Objective-C 中的属性(@property)有哪些常见修饰符?
答案:- 原子性:
atomic
(默认,线程安全但性能低)、nonatomic
(非线程安全,性能高)。 - 内存管理:
strong
(持有引用)、weak
(弱引用)、copy
(复制对象)、assign
(基本类型或弱引用)。 - 读写:
readonly
、readwrite
(默认)。 - 示例:
@property (nonatomic, strong) NSString *name;
@property (nonatomic, weak) id<Delegate> delegate;
- 原子性:
问题: Objective-C 中 block 的定义和使用?
答案:- 定义:类似 Swift 闭包,捕获上下文变量。
- 语法:
void (^myBlock)(int) = ^(int value) {
NSLog(@"Value: %d", value);
};
myBlock(42); - 注意:
- block 捕获变量默认是值捕获,需用
__block
修饰变量以支持修改。 - 避免循环引用,使用
__weak typeof(self) weakSelf = self
。
- block 捕获变量默认是值捕获,需用
二、Swift 基础
问题: Swift 中 struct 和 class 的主要区别是什么?
答案:- 值类型 vs 引用类型: struct 是值类型,复制时创建新副本;class 是引用类型,复制时共享同一实例。
- 继承: class 支持继承,struct 不支持。
- 内存管理: struct 通常在栈上分配,class 在堆上分配,需 ARC 管理。
- 初始化: struct 自动生成成员初始化器,class 需手动定义。
- 可变性: struct 的 mutating 方法需显式声明,class 方法默认可修改实例。
- 使用场景: struct 适合简单数据模型(如 DTO),class 适合复杂对象(如视图控制器)。
问题: struct 和 class 在初始化时,属性和 init 函数有哪些注意事项?
答案:- 属性初始化:
- 非可选属性必须在初始化时赋值,或提供默认值。
- 常量(let)属性只能在初始化时赋值。
- struct 自动生成成员初始化器(若无自定义 init),class 需显式定义。
- init 函数:
- class 的 init 需确保所有非可选属性初始化完成。
- class 支持指定初始化器(designated)和便利初始化器(convenience),convenience 必须调用 designated。
- struct 的 init 不需要考虑继承,但若自定义 init,自动生成的成员初始化器失效。
- deinit 仅适用于 class,用于释放资源。
- 示例:
class Animal {
var name: String
init(name: String) { self.name = name }
}
struct Point {
var x: Int, y: Int
// 自动生成 init(x:y:)
}
- 属性初始化:
问题: class 是否可以服从 Codable 协议?如何实现?
答案:- 是的,class 可以服从 Codable 协议(包括 Encodable 和 Decodable)。
- 实现方式:
- 确保 class 的所有存储属性都符合 Codable。
- 若属性是自定义类型,自定义类型也需符合 Codable。
- 示例:
class User: Codable {
var name: String
var age: Int
}
- 注意:class 的继承需要额外处理,父类和子类都需符合 Codable,可能需自定义编码/解码逻辑。
问题: 如何利用 Codable 协议兼容接口下发的字段类型或字段名?
答案:- 兼容字段名:
- 使用
CodingKeys
枚举映射接口字段名和 Swift 属性名。 - 示例:
struct User: Codable {
var userName: String
var userAge: Int
enum CodingKeys: String, CodingKey {
case userName = "name"
case userAge = "age"
}
}
- 使用
- 兼容字段类型:
- 自定义
init(from:)
和encode(to:)
方法处理类型转换。 - 示例(处理 age 可能是 String 或 Int):
struct User: Codable {
var name: String
var age: Int
enum CodingKeys: String, CodingKey {
case name, age
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
if let ageInt = try? container.decode(Int.self, forKey: .age) {
age = ageInt
} else if let ageString = try? container.decode(String.self, forKey: .age) {
age = Int(ageString) ?? 0
} else {
age = 0
}
}
}
- 自定义
- 兼容字段名:
问题: Optional 的本质是什么?
答案:- Optional 是一个枚举类型,定义如下:
enum Optional<Wrapped> {
case none
case some(Wrapped)
} - 本质:
- 表示一个值可能存在(some)或不存在(none)。
- 提供安全的方式处理空值,避免运行时崩溃。
- 使用场景:
- 解包:
if let
,guard let
,??
,map
,flatMap
。 - 链式调用:
optional?.property?.method()
。
- 解包:
- 注意:
- 避免强制解包(
!
),除非确定值存在。
- 避免强制解包(
- Optional 是一个枚举类型,定义如下:
问题: 解释 Swift 中的 GCD 多线程编程及其常见用法。
答案:- GCD(Grand Central Dispatch):
- 苹果提供的并发框架,用于管理任务队列和线程。
- 核心概念:DispatchQueue(串行/并发)、DispatchGroup、DispatchSemaphore。
- 常见用法:
- 异步任务:
DispatchQueue.global().async {
// 后台任务
DispatchQueue.main.async {
// 更新 UI
}
} - 串行队列:
let serialQueue = DispatchQueue(label: "com.example.serial")
serialQueue.async { /* 任务 1 */ }
serialQueue.async { /* 任务 2 */ } // 按顺序执行 - DispatchGroup:
let group = DispatchGroup()
group.enter()
DispatchQueue.global().async {
// 任务 1
group.leave()
}
group.notify(queue: .main) {
// 所有任务完成
}
- 异步任务:
- 注意:
- 避免在主线程执行耗时任务。
- 合理使用 QoS(服务质量)优先级。
- GCD(Grand Central Dispatch):
问题: 解释 Swift 的 Task 和 async/await 多线程编程。
答案:- async/await:
- Swift 5.5 引入的结构化并发模型,简化异步编程。
- 使用
async
标记异步函数,await
暂停等待结果。
- Task:
- 用于启动异步任务,运行于并发上下文。
- 示例:
func fetchData() async throws -> Data {
let url = URL(string: "https://example.com")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
Task {
do {
let data = try await fetchData()
print("Data fetched")
} catch {
print("Error: \(error)")
}
}
- 并发模型:
- Task Group:并行执行多个任务。
let result = try await withTaskGroup(of: Int.self) { group in
for i in 0..<5 {
group.addTask { return i * 2 }
}
var results: [Int] = []
for try await value in group {
results.append(value)
}
return results
} - Actor:线程安全的引用类型,避免数据竞争。
actor Counter {
var value = 0
func increment() { value += 1 }
}
- Task Group:并行执行多个任务。
- async/await:
问题: Swift 中的闭包、逃逸闭包和捕获列表?
答案:- 闭包定义:
- 自包含的代码块,可捕获上下文变量。
- 语法:
{ (parameters) -> ReturnType in statements }
- 捕获列表:
- 控制闭包如何捕获变量(值/引用)。
- 示例:
var x = 10
let closure = { [x] in print(x) } // 捕获 x 的值
x = 20
closure() // 输出 10
- 逃逸闭包:
- 闭包在函数返回后仍可被调用,需标记
@escaping
。 - 示例:
var completion: (() -> Void)?
func performTask(completion: @escaping () -> Void) {
self.completion = completion
}
- 闭包在函数返回后仍可被调用,需标记
- 闭包定义:
问题: 高阶函数(如 map、filter、reduce)的用途和示例?
答案:- 用途:
- 函数式编程核心,用于处理集合数据。
- 提高代码简洁性和可读性。
- 示例:
- map:
let numbers = [1, 2, 3]
let doubled = numbers.map { $0 * 2 } // [2, 4, 6] - filter:
let evens = numbers.filter { $0 % 2 == 0 } // [2]
- reduce:
let sum = numbers.reduce(0) { $0 + $1 } // 6
- map:
- 用途:
问题: Combine 框架的核心概念和使用场景?
答案:- 核心概念:
- 响应式编程框架,处理异步事件流。
- 核心组件:Publisher、Subscriber、Operator。
- 使用场景:
- 网络请求、 Connor输入、状态变化。
- 示例:
import Combine
let publisher = [1, 2, 3].publisher
let cancellable = publisher
.map { $0 * 2 }
.sink { print($0) } // 输出 2, 4, 6 - 注意:
- 使用
AnyCancellable
管理订阅生命周期。
- 使用
- 核心概念:
问题: RxSwift 的核心概念和与 Combine 的区别?
答案:- 核心概念:
- 基于观察者模式的响应式框架。
- 核心组件:Observable、Observer、Operator、Scheduler。
- 示例:
import RxSwift
let disposeBag = DisposeBag()
Observable.just([1, 2, 3])
.map { $0 * 2 }
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag) - 与 Combine 的区别:
- Combine 是苹果原生框架,RxSwift 是第三方库。
- Combine 集成 Swift 特性(如 property wrapper)。
- RxSwift 社区更大,跨平台支持更好。
- 核心概念:
问题: ARC 内存管理的原理和常见问题?
答案:- 原理:
- 自动引用计数(ARC)跟踪对象引用,引用计数为 0 时释放。
- 强引用(strong)、弱引用(weak)、无主引用(unowned)。
- 常见问题:
- 循环引用:
- 两个对象互相强引用,导致内存泄漏。
- 解决:使用 weak 或 unowned。
- 示例:
class Person {
var dog: Dog?
}
class Dog {
weak var owner: Person? // 避免循环引用
}
- 闭包捕获:
- 闭包捕获 self 可能导致循环引用。
- 解决:使用
[weak self]
。
- 循环引用:
- 原理:
三、iOS 开发基础
问题: 什么是 UIApplication 和 UIApplicationDelegate?
答案:- UIApplication:管理 iOS 应用的生命周期和全局状态(如通知、后台任务)。
- UIApplicationDelegate:处理应用生命周期事件(如 didFinishLaunchingWithOptions、applicationDidEnterBackground)。
- 示例:
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
}
问题: 解释 UIViewController 的生命周期。
答案:- viewDidLoad:视图加载完成,初始化 UI。
- viewWillAppear:视图即将显示。
- viewDidAppear:视图已显示。
- viewWillDisappear:视图即将消失。
- viewDidDisappear:视图已消失。
- 注意:避免在 viewDidLoad 执行耗时任务,UI 更新放在 viewWillAppear 或 viewDidAppear。
问题: Auto Layout 的基本原理和使用方式?
答案:- 原理:通过约束(constraints)定义视图的布局,适应不同屏幕大小。
- 使用方式:
- Interface Builder:拖拽设置约束。
- 代码:NSLayoutConstraint 或 Anchor API。
- 示例(Anchor):
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: superview.topAnchor, constant: 20),
view.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 20)
])
四、内存管理
问题: MRC 和 ARC 的区别?
答案:- MRC(手动引用计数):
- 开发者手动调用 retain、release、autorelease。
- 繁琐,易出错。
- ARC(自动引用计数):
- 编译器自动插入内存管理代码。
- 简化开发,但需注意循环引用。
- 注意:MRC 已基本淘汰,iOS 5 后默认 ARC。
- MRC(手动引用计数):
问题: 如何检测和解决内存泄漏?
答案:- 检测:
- 使用 Instruments 的 Leaks 工具。
- 分析对象引用计数。
- 解决:
- 使用 weak/unowned 打破循环引用。
- 确保 delegate 属性使用 weak。
- 检查闭包/block 中的 self 捕获。
- 检测:
五、多线程
问题: iOS 中有哪些多线程技术?
answers:- GCD:DispatchQueue 管理任务队列。
- NSOperation/OperationQueue:高级封装,支持依赖和取消。
- Thread:底层线程管理(不推荐)。
- async/await(Swift):结构化并发,简化异步编程。
- NSTimer/PThread:特定场景(如定时器、低级线程)。
问题: 主线程和全局线程的区别?如何确保 UI 更新在主线程?
答案:- 主线程:处理 UI 和用户交互,阻塞会导致卡顿。
- 全局线程:用于后台任务,如网络请求、数据处理。
- 确保主线程:
- GCD:
DispatchQueue.main.async { /* UI 更新 */ }
- async/await:
@MainActor
或Task { await MainActor.run { /* UI 更新 */ } }
- GCD:
六、设计模式
问题: iOS 开发中常用的设计模式有哪些?
答案:- 单例:如 UIApplication.shared。
- 委托(Delegate):如 UITableViewDelegate。
- 观察者:如 KVO 或 NotificationCenter。
- MVC:Model-View-Controller,iOS 默认架构。
- MVVM:结合 Combine/RxSwift 实现响应式架构。
- 工厂模式:创建对象(如 URLSessionConfiguration)。
- 适配器:桥接不同接口(如第三方库适配)。
问题: 实现一个简单的单例模式(Swift 和 Objective-C)。
答案:- Swift:
class Singleton {
static let shared = Singleton()
private init() {}
} - Objective-C:
@interface Singleton : NSObject
+ (instancetype)shared;
@end
@implementation Singleton
+ (instancetype)shared {
static Singleton *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[Singleton alloc] init];
});
return instance;
}
@end
- Swift:
七、网络
问题: iOS 中如何进行网络请求?
答案:- URLSession:
- 默认方式,支持 GET、POST、上传、下载。
- 示例(Swift):
let url = URL(string: "https://api.example.com")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
// 处理数据
}
task.resume()
- Alamofire(第三方库):
- 简化请求和响应处理。
- 示例:
import Alamofire
AF.request("https://api.example.com").responseJSON { response in
// 处理响应
}
- URLSession:
问题: 如何处理 HTTPS 和证书验证?
答案:- 默认 HTTPS:URLSession 默认支持 HTTPS。
- 自定义证书验证:
- 实现
URLSessionDelegate
的didReceiveChallenge
方法。 - 示例:
class NetworkManager: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
// 验证服务器证书
completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
}
}
- 实现
八、数据库
问题: iOS 中常见的数据库技术有哪些?
答案:- Core Data:
- 苹果原生框架,对象-关系映射(ORM)。
- 适合复杂数据模型。
- Realm:
- 第三方数据库,性能高,易用。
- 支持实时更新。
- SQLite:
- 轻量级数据库,需手动管理 SQL。
- 使用 FMDB 简化操作。
- UserDefaults:
- 轻量级键值存储,适合简单数据。
- Core Data:
问题: Core Data 的基本使用流程?
答案:- 步骤:
- 创建数据模型(.xcdatamodeld 文件)。
- 配置 NSPersistentContainer。
- 执行 CRUD 操作。
- 示例:
let container = NSPersistentContainer(name: "Model")
container.loadPersistentStores { _, error in
guard error == nil else { return }
let context = container.viewContext
let entity = NSEntityDescription.insertNewObject(forEntityName: "User", into: context)
entity.setValue("John", forKey: "name")
try? context.save()
}
- 步骤:
九、框架
问题: UIKit 和 SwiftUI 的区别和适用场景?
答案:- UIKit:
- 基于命令式编程,成熟稳定。
- 适合复杂应用,支持 iOS 9+。
- 使用 UIViewController、UIView 等。
- SwiftUI:
- 基于声明式编程,简洁现代。
- 适合新项目,iOS 13+。
- 使用 View 协议和 Combine。
- 选择:
- UIKit:兼容旧设备或需要复杂自定义。
- SwiftUI:快速开发,跨平台(iOS、macOS)。
- UIKit:
问题: 如何在 UIKit 中集成 SwiftUI?
答案:- 使用
UIHostingController
嵌入 SwiftUI 视图。 - 示例:
import SwiftUI
let swiftUIView = Text("Hello, SwiftUI!")
let hostingController = UIHostingController(rootView: swiftUIView)
navigationController?.pushViewController(hostingController, animated: true)
- 使用
十、综合问题
问题: 设计一个线程安全的网络请求单例类,支持 Codable(Swift)。
答案:import Foundation
final class NetworkManager: Codable {
static let shared = NetworkManager()
private let urlSession = URLSession.shared
private init() {}
enum CodingKeys: CodingKey {
case dummy
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
// 编码逻辑
}
init(from decoder: Decoder) throws {
// 单例无需恢复实例
}
func fetchData<T: Decodable>(from url: URL) async throws -> T {
let (data, _) = try await urlSession.data(from: url)
return try JSONDecoder().decode(T.self, from: data)
}
}问题: 如何优化 iOS 应用的性能?
答案:- 内存:避免循环引用,使用 Instruments 检测泄漏。
- CPU:减少复杂计算,使用 GCD 或 async/await 优化耗时任务。
- UI:异步加载图片,优化 Auto Layout,减少离屏渲染。
- 网络:缓存响应,压缩数据,使用分页加载。
- 启动时间:延迟非必要初始化,主线程只处理 UI。
问题: 常见崩溃原因及解决方法?
答案:- 原因:
- 空指针(Objective-C)/强制解包(Swift)。
- 数组越界。
- 主线程阻塞。
- 循环引用导致内存不足。
- 解决:
- 使用 Optional 和安全解包。
- 检查数组索引。
- 耗时任务移至后台线程。
- 使用 weak/unowned 打破循环引用。
- 原因: