- 教程
- 文档
【免费下载链接】Awesome-Swift-Education
:fire: Learn some Swift
本指南以开源仓库 Awesome-Swift-Education 中 Swift /Error Handling/ErrorHandling.md 学习清单为骨架,系统梳理 Swift 错误处理的五大支柱:以guard为代表的早期返回、以throws为标志的失败声明、以do-catch为入口的错误消费、以assert/precondition为手段的开发期校验,以及以ErrorType/Result为载体的类型化错误建模。读完本文,你将掌握 Swift 2 时代奠基、至今仍在演进的核心错误处理语法与设计取舍,并能在真实项目中为"函数何时该抛错、如何抛、如何接、如何断言"做出有依据的决策。
1. 为什么需要一个"错误处理"专题页
在 Swift 语言体系中,错误处理不是孤立的语法糖,而是贯穿函数声明、控制流、可选类型与类型系统的一条主线。Awesome-Swift-Education 仓库在 Swift /Error Handling/ErrorHandling.md 中专门开辟了一节,按Guard、Throw、try / catch、Asserts、ErrorType、Other六个子主题组织资源,恰好对应 Swift 错误处理的完整闭环:
- 预防——用
guard在入口处拦截非法状态,避免深层嵌套; - 声明——用
throws明确告知调用方"这个函数可能失败"; - 消费——用
try/catch或try?/try!决定失败如何被处理; - 校验——用
assert/precondition在开发期暴露程序逻辑错误; - 建模——用
ErrorType协议与Result<T, E>把"失败"本身变成类型系统可以表达和组合的值。
需要说明的是,该文档汇编于 Swift 2.x 时期(2015–2016 年),正文代码示例以 Swift 2 语法为基准;后文凡涉及版本演进(如ErrorType更名为Error),都会明确标注,便于你对照当前 Swift 版本阅读。
2. Guard:把"验证"写在前面的早期返回
guard是 Swift 错误预防的第一道防线。它的核心价值在于翻转控制流:与其用多层if嵌套把正常路径越包越深,不如用guard把异常路径在函数开头一次性排除,让正常代码保持平铺直叙。
2.1 guard 与 if 的本质差异
guard与if最大的不同在于语义方向:guard要求"条件必须成立,否则立即退出当前作用域",因此else分支必须包含return、break、continue或throw等跳出语句,编译器会强制检查这一点。这一设计消除了"if 嵌套金字塔",也杜绝了"忘了 return 导致继续执行非法代码"的隐患。
// 糟糕:深层嵌套 func process(user: User?) { if let user = user { if user.isValid { if let name = user.name { print("Hello, \(name)") } } } } // 更好:guard 平铺 func process(user: User?) { guard let user = user else { return } guard user.isValid else { return } guard let name = user.name else { return } print("Hello, \(name)") }第二版代码中,每一次guard都明确表达了"这条路上有一个前置条件,不满足就停在这里",可读性和可维护性都显著提升。这也是文档中"The Value of Guard"(guard 的价值)与"guard is better than if"(guard 优于 if)两个条目的核心论点。
2.2 何时用 guard、何时不要用
文档专门列出了"When to (not) use guard"(何时使用、何时不要使用 guard),其讨论要点可以归纳为三条实用准则:
- 用 guard 的场景:函数需要多个前置条件才能继续、每次失败都需要提前退出、以及需要在作用域内保留解包后的常量(
guard let解绑的可选项在整个作用域内都可用,而if let解绑的常量只在 if 块内有效); - 不用 guard 的场景:只有一个可选判断且后续逻辑需要同时处理"成功"与"失败"两个分支(此时
if-else更直白);或者失败后并不想退出作用域,而是想继续走同一条路径(此时应使用if); - 注意争议:文档收录了社区中"guard 应当被避免"及其反驳文章(
RE: Why Swift Guard Should Be Avoided)。这类讨论的核心并非 guard 本身有错,而是提醒开发者不要机械地"把所有 if 都改成 guard"——guard 的适用前提是"失败即退出",如果退出条件设计不当,反而会掩盖错误的层级与位置。
2.3 三个让 guard 更简洁的实践技巧
文档收录的"Three tips for concise Swift using the Guard statement"总结的常见简洁化手法包括:
- 合并条件:多个互不相关的布尔条件可以写在同一个
guard中,用逗号分隔,等价于&&,减少重复的else { return }:
guard isLoggedIn, hasValidToken, userID != nil else { return }让 guard 只做"检查":
guard的else分支应当保持精简(一句 return / throw),不要在else里塞入复杂逻辑,否则就违背了"前置条件校验"的初衷;组合
guard let与附加条件:在解绑的同时继续校验,例如:
guard let json = try? JSONSerialization.jsonObject(with: data), let dict = json as? [String: Any], let id = dict["id"] as? Int else { return }2.4 guard 与 defer 的搭配
文档将"Guard and Defer"作为一个独立条目列出,正是因为两者经常成对出现:guard负责"提前退出",defer负责"退出前必须做的事"。例如一个需要释放锁或关闭文件的函数:
func withLockedResource() throws { lock.lock() defer { lock.unlock() } // 无论正常返回还是 throw,都会执行 guard let resource = try fetchResource() else { return } // ...使用 resource }guard让多个退出点变得合法,defer则保证每个退出点都不会漏掉清理工作——两者的组合是 Swift 中"安全提前返回"的标准范式。
3. Defer:作用域退出时的确定性清理
defer是文档在 Guard 子主题下重点介绍的另一机制。它的语义是:把一段代码的执行推迟到当前作用域退出之时,无论退出是正常 return、throw、break 还是程序流程自然结束。
3.1 defer 的执行时机与顺序
func example() { defer { print("first defer") } defer { print("second defer") } print("body") } // 输出顺序:body -> second defer -> first defer要点:
defer块在当前作用域结束前执行,但具体时机是"所有defer语句注册之后、作用域真正退出之前";- 多个
defer按LIFO(后进先出)顺序执行,即后注册的先执行; defer的作用范围是它所在的词法作用域(函数体、循环体、do块等),退出该作用域即触发。
3.2 defer 的典型应用
文档条目"Defer in Swift"覆盖的典型场景包括:
- 资源释放:文件句柄、数据库连接、
dispatch_semaphore信号量等必须在函数退出时无条件释放; - 锁的成对加解锁:避免提前
return导致死锁; - 性能测量:在函数开头注册
defer { stopMeasuring() },无论哪个分支返回都能记录耗时; - 状态恢复:修改了某个全局状态(如
UIApplication.shared.isNetworkActivityIndicatorVisible = true)后,在退出时恢复。
需要留意的是,defer块中不应包含会改变控制流的语句(如return),因为作用域已经决定退出,defer 中的"返回意图"没有意义。
4. Throw:声明"这个函数会失败"
如果说guard是防御,那么throws就是契约。文档以"Let it Throw!"为标题的条目,强调的核心观点是:不要让错误被静默吞掉,让它通过函数签名显式地传播出去。
4.1 throws 函数与 throw 语句
enum FileError: ErrorType { case NotFound case PermissionDenied } func readFile(at path: String) throws -> String { guard fileExists(at: path) else { throw FileError.NotFound // 声明失败 } guard canRead(at: path) else { throw FileError.PermissionDenied } return contents(of: path) // 正常返回 }- 一个函数只要在签名中标注
throws,内部就可以用throw抛出任意符合ErrorType的值; - 调用方必须显式处理这种"可能失败",编译器会强制检查;
throws与返回值可以并存:throws -> String表示"要么返回 String,要么抛出错误"。
4.2 "Throws 就像 Either 类型"
文档用一个比喻(Throws is like an Either Type)帮助理解:Either<Error, T>类型显式地表达"这个计算要么是错误(Left),要么是成功值(Right)"。而throws -> T在语义上与之等价——函数的返回类型是T或错误,只是错误通过异常通道传递,而非通过返回值传递。这个视角的价值在于:
- 它提醒你:throws 是返回类型的一部分,设计函数时要把"失败"当成一等公民来设计;
- 它也解释了为什么许多函数式风格的 Swift 代码倾向用
Result<T, E>替代 throws(详见第 8 节),因为把错误作为值返回可以让错误参与 map/flatMap 等组合运算。
4.3 Throwing vs failing vs crashing vs bool:四种失败模式
文档列出的"Throwing vs failing vs crashing vs bool"讨论,实际上是在帮开发者回答"失败应该用哪种方式表达":
| 方式 | 语义 | 适用场景 |
|---|---|---|
throw | 函数显式失败,调用方必须处理 | 业务规则不满足、外部资源不可用等可预期且需要调用方决策的错误 |
返回Bool | 只表达"成功/失败"两种结果,不携带细节 | 校验类、过滤类逻辑,调用方只关心"过没过" |
返回Optional | 失败时返回nil,语义为"没有结果" | 查找、解析等"失败即无值"的场景(详见 Swift /Optionals/Optionals.md) |
| 直接崩溃(crash / fatalError / 强制解包失败) | 程序状态已不可恢复 | 程序员逻辑错误、不可能出现的先决条件被违反 |
判断准则可以概括为:错误越"可恢复、需要调用方决策",越应该用 throw;错误越"不可恢复、属于程序 bug",越应该用断言或崩溃(第 7 节会展开)。不要把所有失败都抛成异常,也不要用Bool掩盖错误的细节信息。
4.4 throws 与 completion callback 的配合
异步场景下,throws无法直接跨线程传播(回调函数无法向调用栈上层抛错),文档的"Using Swift Throws with Completion Callbacks"条目给出的是经典解法:把错误放入回调参数。
// 同步:throws 传播 func fetchSync() throws -> Data // 异步:错误作为回调参数 func fetchAsync(completion: (Result<Data, ErrorType>) -> Void) { DispatchQueue.global().async { // 成功:completion(.Success(data)) // 失败:completion(.Failure(error)) } }这种模式把"错误"从异常通道转移到值通道,与第 8 节的Result建模殊途同归——这也是为什么异步错误处理(见第 9 节)在 Swift 社区长期是热点话题。
4.5 rethrows:闭包抛错时的"再抛出"
文档单独列出"Rethrows in Swift 2"。rethrows适用于以闭包为参数、且闭包可能 throws 的高阶函数,语义是:这个函数本身不抛出错误,但它的闭包参数如果抛错,错误会继续向上传播。
// map 内部不抛错,但 closure 可以抛,所以整体声明为 rethrows func myMap<T, U>(_ items: [T], transform: (T) throws -> U) rethrows -> [U] { var result: [U] = [] for item in items { result.append(try transform(item)) } return result } // 传入不抛错的闭包时,调用方无需 try;传入 throws 闭包时,调用方必须 try let doubled = myMap([1, 2, 3]) { $0 * 2 } // 无需 try let parsed = try myMap(["1", "2"]) { try parse($0) } // 需要 tryrethrows的精妙之处在于让"可能失败性"随闭包类型传递:闭包不抛错,调用点就不必写try,接口更干净;闭包抛错,错误也不会被吞掉。map、filter、forEach等标准库高阶函数均采用这一声明。仓库中 Swift /Closures + Functions/Closures+Functions.md 收录的@noreturn条目(标注永不返回的函数,如fatalError)与此同属"函数失败/终止行为"的签名级表达,可一并参考。
5. try/catch:消费错误
throws声明了"会失败",try与catch则负责"消费失败"。Swift 的do-catch结构把错误处理显式地写入控制流,这是文档"try/catch with asynchronous closures"、"do-try-catch syntax"与"try, catch, do and throw"几个条目共同覆盖的语法核心。
5.1 基本形态:do-catch
do { let content = try readFile(at: "/etc/hosts") print(content) } catch FileError.NotFound { print("文件不存在") } catch FileError.PermissionDenied { print("没有权限") } catch { print("其他错误:\(error)") }语法要点:
- 所有可能抛出错误的调用前必须写
try; catch可以按具体错误类型/模式匹配,从上到下逐一尝试;- 兜底的
catch(无模式)会绑定一个隐式常量error; - 若
do块内某个try抛错,控制流立即跳到匹配的catch,块内后续代码不再执行。
5.2 catch 的模式匹配能力
catch本质上是模式匹配,因而可以配合 Swift 强大的模式语法:
do { try dangerousOperation() } catch let error as NetworkError where error.isRetryable { retry() } catch NetworkError.Timeout { print("超时") } catch let error { print("未知错误:\(error)") }文档在"Other"部分收录的"Swift 2 Error Handling(控制流)"条目,正是讨论do-catch与switch在模式匹配上的相似性——如果你熟悉switch的case语法,就能无缝迁移到catch上。
5.3 try 的三种形态:try、try?、try!
文档专门列出"What Is the Difference Between try, try?, and try!",这是每个 Swift 开发者都必须精确掌握的区分:
| 形态 | 行为 | 适用场景 |
|---|---|---|
try | 必须位于do-catch或throws函数内,错误由catch或上层函数处理 | 需要针对错误类型做分支处理、或需要向上传播 |
try? | 成功返回Optional<T>,失败返回nil(错误被吞掉,不进入 catch) | 只关心"有没有结果",失败即nil,与可选绑定天然配合 |
try! | 成功返回T,失败直接崩溃 | 你百分之百确定不会失败(如读取硬编码的合法 JSON),失败即视为程序 bug |
// try?:失败变成 nil let data1 = try? readFile(at: path) // String? 类型 guard let content = try? readFile(at: path) else { return } // try!:绝不失败,失败即崩溃 let data2 = try! readFile(at: knownGoodPath) // String 类型使用建议:优先try(保留错误信息),其次try?(错误不关心),try!仅限"失败在逻辑上不可能"的场合——它与强制解包!一样,是把"程序 bug"转换成崩溃的手段。文档中"do or die"条目的讨论也与这一取舍相关:与其让错误在运行时"或死或生"地靠运气,不如尽量用类型把失败编码为可预期的值。
6. 异步闭包中的 try/catch
文档的"try/catch with asynchronous closures"条目提醒了一个重要陷阱:do-catch只能捕获当前同步执行上下文中的错误,无法捕获异步回调中抛出的错误。
// 错误示范:catch 捕获不到闭包内抛出的错误 do { DispatchQueue.global().async { try self.performNetworkCall() // 编译错误或错误逃逸 } } catch { // 永远不会执行 }正确做法是把错误传回异步边界(回调参数或Result):
func performAsync(completion: (Result<Data, ErrorType>) -> Void) { DispatchQueue.global().async { do { let data = try self.performNetworkCall() completion(.Success(data)) } catch { completion(.Failure(error)) } } }这一模式是 Swift 异步错误处理的基础共识,也是第 9 节"异步错误处理"进阶主题的前提。
7. Asserts 与 precondition:开发期校验与运行期守卫
错误处理的前四个支柱解决"可恢复错误",而assert/precondition解决**"不可恢复的程序逻辑错误"**——它们不属于业务失败,而是代码 bug 的早期暴露。文档在Asserts子主题下列出了"Swift assertions"、"Using the assert() keyword"(assert 缺失手册)、"Assert vs precondition"等多个条目。
7.1 assert 与 assertionFailure
let index = 5 assert(index < array.count, "索引越界!") // 条件为假时触发 assertionFailure("此处代码不应被执行到") // 无条件触发关键特性:
assert仅在Debug(调试)构建中生效,Release 构建会被编译器移除,不产生运行时开销;- 它表达的是"开发期帮我验证我的假设",适合校验不变量、前置条件等纯开发期检查;
- 条件为假时触发,程序终止并打印消息(Xcode 中会停在断点处)。
7.2 precondition 与 assert 的区别
文档的"Assert vs precondition"条目正是为了厘清这对易混淆的兄弟。核心区别在生效范围:
| 函数 | Debug 构建 | Release 构建 | 适用场景 |
|---|---|---|---|
assert | 生效 | 不生效(被移除) | 纯开发期假设验证 |
precondition | 生效 | 依然生效 | 无论什么构建都必须成立的前置条件,如"下标必须合法" |
fatalError | 生效 | 生效 | 无条件终止(如@noreturn场景) |
// 即便发布版,数组下标也绝不能越界——用 precondition 而非 assert precondition(index >= 0 && index < array.count, "下标越界")判断准则:如果条件失败意味着"这个程序在发布版也不该继续运行",用precondition;如果只是开发期帮我排查问题,用assert。仓库中 Swift /Debugging/Debugging.md 与 Swift /Testing/Testing.md 分别从调试与测试角度与本节呼应(测试框架中的XCTAssert*家族即断言思想在单元测试中的延伸)。
8. ErrorType:把错误建模成类型
Swift 2 中,所有错误都必须符合ErrorType协议(Swift 3 起更名为Error)。文档的"ErrorType"子主题(含"Swift 2: Error handling using ErrorType, throws, try and do-catch"与"Testing Swift's ErrorType")展示了这一建模思想。
8.1 用枚举建模错误域
ErrorType的核心用法是用枚举把一类错误的所有可能情况枚举出来,让错误成为可模式匹配、可穷举的类型:
enum NetworkError: ErrorType { case Timeout case ServerError(code: Int) case NoConnection } func fetchUser() throws -> User { guard isConnected else { throw NetworkError.NoConnection } // ... throw NetworkError.ServerError(code: 500) }枚举错误相比字符串/整数错误码的优势:
- 类型安全:编译器知道全部可能的错误情况,
catch可穷举; - 可携带数据:关联值(如
code: Int)让错误自带上下文; - 可模式匹配:
catch NetworkError.ServerError(let code) where code >= 500这类写法成为可能。
8.2 测试错误处理逻辑
文档的"Testing Swift's ErrorType"强调:错误路径与正常路径一样需要单元测试。典型做法是断言"调用确实抛出了预期错误":
func testFetchThrowsOnNoConnection() { // 关闭网络后调用 fetchUser,断言抛出 NetworkError.NoConnection // (Swift 2 时代常见做法:用 do-catch 捕获并匹配错误类型) }8.3 Result<T, E>:成功/失败的类型化建模
文档在 ErrorType 子主题下特别收录了Result(antitypical/Result)库,并将其描述为"Swift type modelling the success/failure of arbitrary operations"——一个建模任意操作成功/失败的 Swift 类型。其核心形态是一个泛型枚举:
enum Result<T, E> { case Success(T) case Failure(E) }Result的价值在于把错误放回值通道:
- 函数签名直接体现"可能失败",无需
throws; - 失败值可以像普通值一样被存储、传递、组合(
map/flatMap); - 天然适配异步回调(见 4.4 节)与函数式编程风格。
这一思想与仓库中 Swift /Functional Programming/FunctionalProgramming.md 的 Monad 主题一脉相承——Result(以及Either)是"用类型表达计算可能失败"的典型函数式建模;文档"Swift 2 Error Handling vs Result<T, E>"条目正是对比"异常通道(throws)"与"值通道(Result)"两条路线的取舍:前者语法内建、控制流显式,后者可组合、可存储、对异步友好。需要说明的是,随着 Swift 5 将Result引入标准库并推出async/await,这两种思路在现代 Swift 中已趋于融合,但理解其设计差异仍是阅读历史代码与设计新 API 的重要基础。
9. 进阶主题:文档 "Other" 部分的完整继承
原文档在Other子主题下列出的条目,构成错误处理的延伸视野,逐条展开如下。
9.1 异步错误处理(Async Error Handling)
异步错误无法用同步的do-catch捕获(见第 6 节),社区给出的演进路线是:completion callback + Result→ Promise/Future 风格封装 → 语言级async/await。无论哪一代方案,核心不变:错误必须穿过异步边界,且不应丢失类型信息。
9.2 错误分类学:unexpected、composite、non-pure、external
文档收录的"Errors: unexpected, composite, non-pure, external"提出了一种错误分类框架,值得在设计错误类型时参考:
- unexpected(意外错误):不可预测、通常也不可恢复(如内存问题),往往对应崩溃而非 throw;
- composite(复合错误):多个错误叠加(如批量任务中部分失败),需要聚合建模;
- non-pure(非纯错误):函数不仅"返回错误",还产生了副作用(如部分写入了数据库),错误处理必须考虑已执行的副作用;
- external(外部错误):源自函数外部环境(网络、磁盘、用户输入),是 throws 的主要用武之地。
9.3 Unmanaged Type
文档收录的"Unmanaged Type"条目属于错误处理的边缘话题:Unmanaged<T>用于手动管理 Core Foundation 对象的引用计数,绕过 ARC 的自动管理。当手动释放内存的时机出现错误时,容易产生内存安全问题——它提醒我们,底层资源错误是错误处理中风险最高的一类。
9.4 Error Styling in Swift
"Error Styling in Swift"讨论的是错误消息的表达风格:错误文案如何写才便于用户理解与调试(包括错误枚举的命名、关联值携带的上下文、CustomStringConvertible输出的可读性等)。实践要点是:错误类型要具体(不要一律Error.generic),消息要可操作(告诉调用方"下一步能做什么")。
9.5 Magic Methods in Swift
文档收录 Apple 官方 Swift 博客的"Magic Methods in Swift"条目。它虽然不直接讲 throw,但与错误处理相关:Swift 中try/catch/defer等"魔法方法"由编译器提供语法支持,理解它们的底层语义(如 defer 的作用域规则、throws 的错误通道)有助于避免误用。
9.6 Swift 2 Error Handling 总览与社区实践
文档"Other"部分还收录了三类补充材料,可视为对第 1–8 节的整体回顾:
- 入门总览:"Error Handling in Swift"、"Easy Error Handling"、"Swift 2 Error Handling"(Big Nerd Ranch)等条目覆盖从零开始的完整教程;
- 对比视角:"Swift 2 Error Handling vs Result<T, E>"、Monad/
Either相关讨论,帮助你理解类型化错误与异常通道的关系; - 实战分享:"Three Stories of Error Handling in Swift"(try!Swift 社区演讲)通过真实项目案例讲述错误处理设计的演进与教训——社区共识是:错误类型设计要与业务域对齐,错误路径与正常路径同样需要测试覆盖。
10. 仓库导航:如何在 Awesome-Swift-Education 中继续深入
本文对应的原始学习清单位于 Swift /Error Handling/ErrorHandling.md,与仓库其他章节构成完整的 Swift 学习地图:
- 语言基础延伸:Swift /Optionals/Optionals.md(可选类型的解包与 guard 密切相关)、Swift /Closures + Functions/Closures+Functions.md(
@noreturn、闭包与 rethrows 的承接关系)、Swift /Enums and Pattern Matching/Enums+PatternMatching.md(错误枚举与 catch 模式匹配的基础); - 工程实践延伸:Swift /Testing/Testing.md(错误路径的单元测试)、Swift /Debugging/Debugging.md(断言与崩溃的排查)、Swift /Functional Programming/FunctionalProgramming.md(
Result/Monad 的函数式背景)、Swift /Design Patterns/DesignPatterns.md(错误处理相关的模式设计); - 仓库动态:README.md 概括了仓库定位("Learn some Swift"),MostRecentAdditions.md 记录了持续更新的资源(其中
try, try! & try?、do-try-catch syntax、ErrorType、Three Stories of Error Handling等条目与本文主题直接相关),Contributing.md 说明了贡献方式。
11. 小结:Swift 错误处理决策速查
把本文全部内容压缩为一张决策表,供日常编码快速对照:
| 场景 | 推荐手段 | 关键理由 |
|---|---|---|
| 函数入口有多个前置条件 | guard ... else { return } | 平铺控制流,解绑常量作用域更大 |
| 需要退出作用域前清理资源 | defer | 无论 return/throw 都保证执行,LIFO 顺序 |
| 业务失败需要调用方决策 | throws+ErrorType枚举 | 类型安全、可穷举、可模式匹配 |
| 异步回调中传播失败 | 回调携带Result<T, E> | 错误跨异步边界,可组合 |
| 只想关心"有没有结果" | try? | 失败变nil,配合可选绑定 |
| 失败在逻辑上不可能 | try! | 失败即视为 bug,直接崩溃暴露 |
| 开发期验证假设 | assert | Debug 有效、Release 零开销 |
| 发布版也必须满足的前置条件 | precondition | 任何构建都生效 |
| 把失败作为值参与组合 | Result<T, E> | map/flatMap 组合,函数式友好 |
Swift 的错误处理体系是一套"预防(guard)— 声明(throws)— 消费(try/catch)— 校验(assert)— 建模(ErrorType/Result)"的完整闭环。以 Swift /Error Handling/ErrorHandling.md 为索引、以本文为导览,你可以沿着这条主线逐步构建自己的错误处理方法论——先让函数诚实地声明失败,再让调用方有选择地处理失败,最后让错误本身成为类型系统里可组合、可测试、可对话的一等公民。
- 教程
- 文档
【免费下载链接】Awesome-Swift-Education
:fire: Learn some Swift
相关推荐
Swift-Paperless终极指南:在iOS上轻松管理您的纸质文档
Swift Paperless终极指南:在iOS上轻松管理您的纸质文档 想要随时随地管理您的数字文档吗?Swift Paperless是专为Paperless
Swift 错误处理完全指南:throw、do-catch、try?/try! 与类型化抛错实战解析(《Swift 编程语言》中文版)
Swift 错误处理完全指南:throw、do catch、try?/try! 与类型化抛错实战解析(《Swift 编程语言》中文版) Swift 的错误处理机
文档教程Bark-server完全指南:如何在5分钟内搭建专属iOS推送服务器
Bark server完全指南:如何在5分钟内搭建专属iOS推送服务器 想要为自己的iOS应用搭建专属推送服务器吗?Bark server就是你的终极解决方案!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考