面试问题
现代 iOS 开发面试问题
Section titled “现代 iOS 开发面试问题”什么是 Xcode?
Xcode 是 Apple 面向 macOS 的集成开发环境(IDE),用于开发 macOS、iOS、iPadOS、watchOS 和 tvOS 的软件。它包含一套软件开发工具,例如源代码编辑器、图形用户界面 (UI) 构建器(Interface Builder)、调试工具和模拟器。Xcode 主要支持 Swift 和 Objective-C,并且随着 SwiftUI 的引入,它还提供了实时预览和声明式 UI 开发方法。它还集成了 Swift Package Manager 用于管理依赖项。
如何在 Swift 中编写多行注释?
Swift 中的多行注释以斜杠后跟星号 (/) 开头,以星号后跟斜杠 (/) 结尾。
示例: /* 这是一个 Swift 中的 多行注释。 */
Swift 中可用的主要集合类型有哪些?
Swift 提供了三种主要的集合类型用于存储值集合:
- 数组 (Arrays): 同类型值的有序集合。示例:
let numbers: [Int] = [1, 2, 3] - 集合 (Sets): 同类型唯一值的无序集合。示例:
let uniqueLetters: Set<Character> = ["a", "b", "c"] - 字典 (Dictionaries): 无序的键-值关联集合,其中键是唯一的且类型相同,值类型相同。示例:
let capitals: [String: String] = ["France": "Paris", "Japan": "Tokyo"]
Swift 中使用的控制转移语句 (control transfer statements) 有哪些?
Swift 中的控制转移语句改变代码的执行顺序。它们包括:
- break: 立即终止整个控制流语句(如循环或 switch 语句)的执行。
- continue: 停止当前循环迭代并开始下一次迭代。
- fallthrough: 在
switch语句中,使执行继续进入下一个 case 或 default 块,而不检查其条件。(使用时需谨慎,在 Swift 中由于强大的模式匹配,它不那么常见)。 - return: 退出函数或方法,可以选择提供返回值。
- throw: 用于发出程序执行期间发生错误的信号,通常在标记为
throws的函数中。
如何在 Swift 中定义基类 (base classes)(或超类 - superclasses)?
在 Swift 中,类默认不继承自一个通用的基类(不像 Objective-C 中大多数类继承自 NSObject)。如果你定义一个类时不指定超类,它会自动成为一个基类——一个不继承自其他类的类。如果一个类需要与 Objective-C 框架交互或使用某些遗留 API,它可能需要继承自 NSObject 或其他 Objective-C 类。
基类示例:
class Vehicle { /* 属性和方法 */ }
子类示例:
class Car: Vehicle { /* 继承自 Vehicle */ }
Swift 中 switch 语句的关键特性是什么?
Swift 的 switch 语句非常强大,并具有几个关键特性:
- 多功能性 (Versatility):它可以匹配各种数据类型,包括整数、字符串、元组、范围以及遵循
Equatable的自定义类型。 - 穷尽性 (Exhaustiveness):
switch语句必须是穷尽的。这意味着被考虑类型的每个可能值都必须被其中一个case语句匹配。如果无法列出所有可能的 case(例如,对于未来可能添加新 case 的枚举,或非有限类型),则需要一个defaultcase。对于枚举,建议使用@unknown default以优雅地处理未来的 case。 - 无隐式贯穿 (No implicit fallthrough):与 C 类似语言不同,Swift 的
switch语句默认不会贯穿到下一个 case。如果需要贯穿行为,必须显式使用fallthrough关键字。 - 范围匹配 (Interval matching):Case 可以匹配值的范围(例如,
case 1...5:)。 - 元组匹配 (Tuple matching):Case 可以匹配元组并将其元素绑定到临时常量或变量。
- 值绑定 (Value bindings):Case 可以将其匹配的值绑定到临时常量或变量,供 case 体内部使用。
where子句 (where clauses):Case 可以使用where子句指定额外的条件,以实现更复杂的匹配逻辑。
问号 (?) 在 Swift 中表示什么?
Swift 中的问号 (?) 主要用于声明可选类型 (optionals)。可选类型表示变量或常量可能包含一个值,或者它可能包含 nil(无值)。这有助于安全地处理值缺失的情况。
示例: var middleName: String? (middleName 可以是 String 或 nil)
它还用于可选链 (object?.property?.method()),以及在某些上下文中作为 Optional.some(value) 的简写,以及在三元条件运算符 (condition ? valueIfTrue : valueIfFalse) 中使用。
空值合并运算符 (nil-coalescing operator)(双问号 ??)的用途是什么?
空值合并运算符 (a ?? b) 如果可选类型 a 包含一个值,则将其展开;如果 a 为 nil,则返回一个默认值 b。表达式 a 必须是可选类型。默认值 b 必须与 a 内部存储的值类型匹配。
示例:
let userDefinedColorName: String? = nil
let colorNameToUse = userDefinedColorName ?? "DefaultBlue"
// colorNameToUse 是 “DefaultBlue”,因为 userDefinedColorName 为 nil。
Swift 中 let 和 var 的区别是什么?
-
let:用于声明常量 (constants)。一旦给let常量赋值,其值就不能更改。 示例:let maximumLoginAttempts = 10 -
var:用于声明变量 (variables)。var变量的值在初次设置后可以更改(只要它不是一个不可变结构体且其属性未被更改)。 示例:var currentLoginAttempt = 0
默认使用 let 是为了安全和清晰的好习惯,提倡不可变性 (immutability)。
什么是 guard 语句在 Swift 中?
guard 语句用于在条件不满足时提前退出当前作用域(如函数、循环或条件语句)。它通常用于输入验证或检查前置条件。
guard 的主要特点:
- 它必须有一个
else子句,该子句在条件为false时执行。 else子句必须退出当前作用域(例如,使用return、break、continue或throw)。- 使用
guard let解包的任何可选类型,在guard语句所在的作用域的其余部分(在guard语句之后)都可以使用。
示例:
func processOrder(for itemID: String?) { guard let id = itemID else { print("Item ID is missing.") return } // 在这里使用 'id'(现在是非可选的 String) print("Processing order for item: \(id)") }
什么是 Grand Central Dispatch (GCD)?如何使用它?
Grand Central Dispatch (GCD) 是一个低级 API,用于管理并发操作。它通过将计算密集型或 I/O 密集型任务分派到后台队列 (background queues) 来提高应用的响应速度,防止主线程 (main thread)(UI 线程)阻塞。
如何使用:
GCD 提供并管理 DispatchQueue,它们是 FIFO(先进先出)队列,应用程序可以将任务以块对象 (block objects) 或函数的形式提交到其中。
- 主队列 (Main Queue) (
DispatchQueue.main):一个串行队列 (serial queue),在应用程序的主线程上执行任务。用于 UI 更新。 - 全局队列 (Global Queues):系统共享的并发队列 (concurrent queues),通过服务质量 (Quality of Service - QoS) 类区分(例如,
.userInitiated、.background)。这些队列上的任务可以并行运行。 - 自定义队列 (Custom Queues):你创建的队列,可以是串行或并发的。
任务可以同步 (synchronously) 分派 (sync) 或异步 (asynchronously) 分派 (async)。
示例(将任务分派到后台队列并在主队列上更新 UI):
DispatchQueue.global(qos: .userInitiated).async { // 执行繁重计算或网络请求 let result = performHeavyTask() DispatchQueue.main.async { // 使用结果更新 UI updateUI(with: result) } }
虽然 GCD 仍然是基础,但 Swift 的现代并发特性(async/await、Actors)现在在新代码中通常更受青睐,因为它们提供了一种更高级别、更安全的方式来编写并发程序。
GCD 中同步分派 (synchronous) 和异步分派 (asynchronous) 的区别是什么?
-
同步 (
sync):当你使用queue.sync { ... }同步分派任务时,你当前的执行线程会暂停并等待直到分派的任务在目标队列上执行完成,然后才继续。- 当你需要任务在当前线程继续之前完成时,使用
sync,但要小心在当前队列上调用sync(特别是主队列自己调用自己),因为它可能导致死锁。
- 当你需要任务在当前线程继续之前完成时,使用
-
异步 (
async):当你使用queue.async { ... }异步分派任务时,你的当前执行线程在将任务提交到队列后会立即继续。任务将在未来的某个时间点在目标队列上执行,但当前线程不会等待它完成。- 对可以独立运行的任务使用
async,例如网络请求或后台处理,以保持当前线程(尤其是主 UI 线程)的响应性。
- 对可以独立运行的任务使用
现代 Swift 并发使用 async/await 通常提供了一种更结构化的方式来管理异步操作,而无需在许多情况下直接处理 sync 和 async 分派。
什么是 MVC (Model-View-Controller)?
MVC(模型-视图-控制器)是一种用于实现用户界面的软件架构模式。它将应用程序分为三个相互连接的组件:
- 模型 (Model):管理应用程序的数据、逻辑和规则。它独立于 UI。模型通知控制器(有时也通过键值观察或其他机制直接通知视图)其状态的变化。
- 视图 (View):表示数据的可视化呈现(UI)。它显示从模型接收的数据,并将用户交互(如按钮点击)发送给控制器。视图应该相对“傻瓜”,不包含应用程序逻辑。
- 控制器 (Controller):充当模型和视图之间的中介。它根据从视图接收的用户操作更新模型,并使用模型的数据更新视图。在 iOS 中,
UIViewController子类通常扮演控制器的角色。
虽然 MVC 是 iOS 中的一个基础模式,但其他模式如 MVVM(模型-视图-视图模型)、MVP(模型-视图-表示器)、VIPER(视图-交互器-表示器-实体-路由器)和 TCA(可组合架构)也因解决了 MVC 的一些限制(特别是“臃肿的视图控制器”问题)而受到欢迎。
什么是委托 (delegates)(委托模式 - Delegation Pattern)?
委托是一种设计模式,它使类或结构体能够将其部分职责移交(或委托)给另一个类型的实例。委托对象持有对另一个对象(委托者)的引用,并在适当的时间向其发送消息。然后,委托者代表委托对象执行操作或提供其所需的数据。
在 Swift 中,委托通常使用协议 (protocols) 实现:
- 定义一个协议来指定委托者必须(或可选地)实现的方法。
- 委托类有一个可选的协议类型的属性(通常命名为
delegate)。 - 委托类遵循该协议并实现其方法。
示例:UITableViewDelegate 允许一个自定义对象代表 UITableView 管理表格视图的选择、页眉/页脚视图等。
这种模式促进了松耦合,并广泛应用于 Apple 的框架中。
什么是 Core Data?
Core Data 是 Apple 提供的一个强大且成熟的框架,用于对象图管理和持久化。它允许你管理应用程序中的模型层对象,包括它们的关系和数据持久化。
主要特性和概念:
- 对象图管理 (Object Graph Management):Core Data 管理一个模型对象图,跟踪它们的状态和关系。
- 持久化 (Persistence):它可以将对象图持久化到持久存储(persistent store)(例如,SQLite 数据库、XML、二进制存储或内存存储)并在以后检索它们。
- 模式定义 (Schema Definition):你使用 Xcode 中的可视化编辑器或通过编程方式定义你的数据模型(实体 - entities、属性 - attributes、关系 - relationships)。
NSManagedObject:你的模型对象通常是NSManagedObject的子类。NSManagedObjectContext:一个“草稿本”,你在这里获取、创建和修改托管对象。通过上下文将更改保存到持久存储。- 获取数据 (Fetching Data):使用
NSFetchRequest根据条件检索对象。 - 撤销/重做 (Undo/Redo):内置支持撤销和重做操作。
虽然 Core Data 功能强大,但对于更简单的数据持久化需求,开发者可能会使用 Codable 结合 FileManager 或 UserDefaults。对于新项目,Apple 还引入了 SwiftData,这是一个现代的、Swift 原生的持久化框架,与 SwiftUI 和现代 Swift 并发深度集成。
reuseIdentifier 的作用是什么?
reuseIdentifier 是一个与 UIKit 中 UITableViewCell 和 UICollectionViewCell(以及可重用视图如页眉/页脚)关联的字符串。它的目的是实现单元格复用 (cell reuse),这是表格视图和集合视图一个关键的性能优化。
当表格或集合视图需要显示一个已经滚动出屏幕的单元格时,它不会每次都创建一个新的单元格实例(这在内存和性能方面可能开销很大),而是可以从队列中取出一个具有相同 reuseIdentifier 且不再可见的现有、屏幕外的单元格。
通过使用 reuseIdentifiers:
- 内存使用减少了,因为内存中保留的单元格对象更少。
- 滚动性能提高了,因为避免了分配和初始化新单元格的开销。
当单元格从队列中取出后,你再配置其内容以显示新的数据。
示例 (UITableView):
tableView.register(MyCustomCell.self, forCellReuseIdentifier: "MyCellID")
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCellID", for: indexPath) as! MyCustomCell