阅读视图

发现新文章,点击刷新页面。

编译器,靠你了!使用类型改善状态设计

在程序的开发和运行过程中,人往往是最不可靠的环节:一个不小心,逻辑错误(也就是 bug!)可能会悄然保留下来并进入最终的产品。与此相对,编译器要可靠得多。如果程序中存在错误,编译器通常会直接阻止生成产品。Swift 拥有非常强大的类型系统,通过它,我们可以尝试将一些运行时的逻辑“封装”到类型系统中,从而在编译期提前发现潜在的问题和错误。这种依靠类型系统来“保存”逻辑的设计方式可以称为类型状态。 一个简单例子:端到端加密 定义和使用 这个例子源自实际工作的需求。假设我们需要设计一个客户端之间的消息系统,并支持端到端加密:也就是说,这些消息可能包含用户的隐私敏感内容。在用户设备上,这些消息可以以明文形式显示,但一旦需要离开用户设备、发送到服务端(并进一步传递到另一个目标客户端),则必须加密。如果错误地将未加密的信息发送出去,可能会带来安全隐患,甚至损害用户的信任。 一个“简洁”的设计思路是设计一个带有状态的 Message,它包含文本并用一个状态来表示是否已加密: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct Message { e...

逆流而上的设计 - Swift 所有权和 ~Copyable

在 Rust 中,绝对安全和高效的内存使用得益于其独特的所有权(ownership)设计。七年前,Swift 团队发布了《所有权宣言》,以前瞻性的方式介绍了 Swift 中关于值的内存管理变化的一系列愿景。Swift 5.9 中(以和宣言里略微不同的语法)实现了这一愿景,引入了不可复制类型的标记 ~Copyable(non-copyable),以与 Rust 截然不同的(打补丁的)方式实现了更精确的所有权控制。在今年的 Swift 6 中,之前类型扩展(extension)和泛型(generic)不支持 ~Copyable 的不足也得到了解决,~Copyable 的可用性得到了提升。回顾 ~Copyable 及其设计,它为 Swift 引入了一种全新的设计思路:以往的协议是“为类型增加功能”,而 ~Copyable 则是“为类型解除限制”。本文尝试解释 ~Copyable 的设计和工作方式,以帮助读者更好地理解并利用这个特性。 ~Copyable 的工作方式 首先需要强调的是,即使目前完全不了解 Copyable 和 ~Copyable,以及相关的 consuming 和 b...

Swift 6 适配的一些体会以及对现状的小吐槽

最近对手上的两三个项目进行了 Swift 6 的迁移,整体过程并不算顺利,颇有一种梦回 Swift 3 的感觉。不过,最终还是有所收获和心得。趁着记忆还新鲜,我想稍微总结一下。此外,针对目前社区里的一些声音,以及自己这些年的感受,我会在文章后半部分对 Swift 生态进行一些不太重要的小唠叨。 Swift 6 迁移 Swift 6 的最大“卖点”当然是并发编程和编译时就能保证的完全线程安全,这也是在进行 Swift 6 迁移时开发者工作量最大的来源。通过引入一系列语言工具 (主要是 actor 隔离和 Sendable 标注),Swift 6 在开启完全的严格并发检查 (也就是-strict-concurrency=complete) 时,理想状态下可以完全确保在编译阶段就将数据竞争 (data race) 和线程问题排除掉。 关于 Swift 并发编程,我之前写过一些关于并发初步以及结构化并发的文章。对于 actor、Sendable 的概念及其如何确保数据竞争不再发生,我在《Swift 异步和并发》中也有所介绍。近几年的 WWDC 上,Apple 通过若干 sessio...

SwiftLog 和 OSLog:选择、使用以及坑

如果你还在用 NSLog 或者 print 打 log,那也许这篇文章正适合你,可以帮你转型到新的 log 方式。如果你已经在使用 OSLog 的相关功能,那也许本文可以帮助你加深理解,以及找到一些“进阶”用法。 选择:SwiftLog 和 OSLog 的区别 两者都是 Swift 中与 log 有关的框架。在进行选择时,我们的首要任务就是理清它们的区别。“SwiftLog 和 OSLog 我应该选哪个”,也是我在参加一个聚会时经常听到的问题。 SwiftLog 是前端 SwiftLog 首次发布于 2019 年,是一个 Swift server 主导的项目。它的目的是提供一个统一的日志记录接口,让包括服务器 app、命令行工具以及 iOS 和 macOS app 等各种使用 Swift 语言的场合下 (但主要是 server!),能使用同样的方法记录日志。SwiftLog 本身是一个 log 前端框架,这意味着它需要搭配后端使用:例如将日志输出到控制台、文件、数据库或远程日志收集服务。SwiftLog 注重模块化,允许开发者通过更换后端来灵活调整日志记录的行为。 OS...

深入理解 Observation - 原理,back porting 和性能

SwiftUI 遵循 Single Source of Truth 的原则,只有修改 View 所订阅的状态,才能改变 view tree 并触发对 body 的重新求值,进而刷新 UI。最初发布时,SwiftUI 提供了 @State、@ObservedObject 和 @EnvironmentObject 等属性包装器进行状态管理。在 iOS 14 中,Apple 添加了 @StateObject,它补全了 View 中持有引用类型实例的情况,使得 SwiftUI 的状态管理更加完善。 在订阅引用类型时,ObservableObject 扮演着 Model 类型的角色,但它存在一个严重的问题,即无法提供属性粒度的订阅。在 SwiftUI 的 View 中,对 ObservableObject 的订阅是基于整个实例的。只要 ObservableObject 上的任何一个 @Published 属性发生改变,都会触发整个实例的 objectWillChange 发布者发出变化,进而导致所有订阅了这个对象的 View 进行重新求值。在复杂的 SwiftUI 应用中,这可能会导致严...

一些关于开发的杂谈话题 - 测试

最近接手了一些陈旧项目的维护工作,需要把一部分质量很烂的代码进行重构甚至重写。在这个过程期间,我也有机会对一些开发中比较重要的而且通用的知识进行了一点重新的思考和整理,在这里想把它们用个两三篇文章,以杂谈的方式记录一下。这些内容在我刚入门程序开发的时候困扰过我一段时间,所以虽然可能对于已经有多年经验的大佬们用处不大,但是希望新入行的同学们能通过这些话题得到一些启发,如果能减少走弯路的时间,那就更好了。 今天的第一个话题是有关测试的。在以前,我也写过一些关于测试的文章,不过更多的还是对某个特定框架的使用。我自己本身也在很长一段时间内保持了给包括框架和 app 写测试的习惯,并来回倒腾过不少不同风格的测试。在这篇短文里,我想对一些基本的问题和想法的变化进行解释。 为什么要写测试?你会给项目和代码写测试吗? 这是一个每次我去参加各种技术分享会,在结束后的自由交流环节经常会被问到的问题。 我很理解由于工期紧张、需求变动频繁等原因,导致的对测试有意无意的忽视。但在这里,我还是想给出一个关于写测试的理由的答案。如果整篇文章只有一句话值得被记住,那就是: 合理的测试保证了开发者的生活...

不知所谓的 2022 年终总结

其实随着年龄增长,总感觉最近每年都很平淡,也几乎没有什么肉眼可见的进步。再加上疫情到了第三年,自己又长期在宅工作,无形中少了许多和这个世界接触的机会,更让自己的思想越来越僵化死板。不知道是不是因为长年在日本这种国度的关系,从感觉上来说似乎这个世界固化住了。一种即视感萦绕在周围,自己却没有什么新思路,也找不到突破的方式。如何才能在这种情况下继续前进,想来应该会成为今后重要的课题。 这篇年终总结一下笔,居然发现自己整一年都脑袋空空,实在是很不应该。既然没有什么特别想要写的,那就还是先按照每个月挑选一张照片配上说明,来简单回顾一下这一整年吧。一是抒发一下心绪,二来也算是一种见证。最后阶段会依照惯例补充一些今年的书籍、动漫和游戏。 如果硬要说自己对比去年有什么不同的话,大概两鬓新增的白发在寒风中所诉说的故事就是一切了。 图说 一月 姐妹两人操作香菱,帮助爸爸在璃月大地上做任务打工升级。 电子游戏早已是成熟的第九艺术,顶级的游戏必然有着顶级的图像、音乐以及故事。游戏早已是我人生中无法抹掉的印记。相比于用一些冰冷的法律法规,设置重重阻碍来限制她们的游戏时间,还不如多多引导,主...

Swift 正则速查手册

Swift 5.7 中引入了正则表达式的语法支持,整理一下相关的一些话题、方法和示例,以备今后自己能够速查。 总览 Swift 正则由标准库中的 Regex 类型驱动,需要 iOS 16.0 或 macOS 13.0,早期的 deploy 版本无法使用。 构建一个正则表达式的方式,分为传统的正则字面量构建,以及通过 Regex Builder DSL 的更加易读的方式。后者可以内嵌使用前者,以及其他一些已有的 parser,在可读性和功能上要强力很多。实践中,推荐结合使用字面量和 Builder API 在简洁和易读之间获取平衡。 常见字面量 和其他各语言正则表达式的字面量没有显著不同。 直接将字面量包裹在 /.../ 中使用,Swift 将把类似的声明转换为 Regex 类型的实例: 1 let bitcoinAddress_v1 = /([13][a-km-zA-HJ-NP-Z0-9]{26,33})/ 一些常用的字面量表达以及示例。更多非常用的例子,可以参考这里的 Cheat Sheet。 字符集 表达式 ...

Xcode 中使用 SPM 和 Build Configuration 的一些坑

TL;DR 当前,在 Xcode 中使用 Swift Package Manager 的包时,SPM 在编译 package 时将参照 Build Configuration 的名字,自动选择使用 debug 还是 release 来编译,这决定了像是 DEBUG 这样的编译 flag 以及最终的二进制产品的架构。在 Xcode 中使用默认的 “Debug” 和 “Release” 之外的自定义的 Build Configuration 时,这个自动选择可能会造成问题。 现在 (2022 年 10 月) 还并没有特别好的方式将 Xcode 中 Build Configuration 映射到 SPM 的编译环境中去。希望未来版本的 Xcode 和 SPM 能有所改善。 关于文中的一些例子,可以在这里找到源码。 Xcode 和 SPM 中的编译条件 默认的 DEBUG 编译条件 在 Xcode 中,创建项目时我们会自动得到两个 Build Configuration:Debug 和 Release。 在 SWIFT_ACTIVE_COMPILATION_CONDIT...

TCA - SwiftUI 的救星?(四)

这是一系列关于 TCA 文章的最后一篇。在系列中前面的几篇里,我们简述了 TCA 的最小 Feature 核心思想,并研究了绑定和环境值的处理,以及 Effect 角色和 Feature 组合的方式等话题。作为贯穿整个系列的示例 app,现在应该已经拥有一个可用的猜数字游戏了。这篇文章会综合运用之前的内容,来看看和 UI 以及日常操作更贴近的一些话题,比如如何用 TCA 的方式展示 List 并让结果可以被删除,如何处理导航以及 alert 弹窗等。 如果你想要跟做,可以直接使用上一篇文章完成练习后最后的状态,或者从这里获取到起始代码。 展示结果 List 在前一篇文章最后的练习中,我们使用了 var results: [GameResult] 来存放结果并显示已完成的状态数字。现在我们的 app 还只有一个单一页面,我们打算为 app 添加一个展示所有已猜测结果,并且可以对结果进行删除的新页面。 使用 IdentifiedArray 进行改造 在实际开始之前,来对 results 数组进行一些改造:使用 TCA 中定义的 IdentifiedArray 来代替...

TCA - SwiftUI 的救星?(三)

在上一篇关于 TCA 的文章中,我们看到了绑定的工作方式以及 Environment 在管理依赖和提供易测试性时发挥的作用。在这篇文章中,我们会继续深入,来看看 TCA 中的两个重要话题:Effect 角色到底是什么,以及如何通过组合的方式来把多个小 Feature 组合在一起,形成更加复杂的 UI 结构。 如果你想要跟做,可以直接使用上一篇文章完成练习后最后的状态,或者从这里获取到起始代码。 Effect 什么是 Effect Elm-like 的状态管理之所以能够保持可测试及可扩展,核心要求是 Reducer 的纯函数特性。Environment 通过提供依赖解决了 reducer 输入阶段的副作用 (比如 reducer 需要获取某个 Date 等),而 Effect 解决的则是 reducer 输出阶段的副作用:如果在 Reducer 接收到某个行为之后,需要作出非状态变化的反应,比如发送一个网络请求、向硬盘写一些数据、或者甚至是监听某个通知等,都需要通过返回 Effect 进行。Effect 定义了需要在纯函数外执行的代码,以及处理结果的方式:一般来说这...
❌