CopyWithChanges

允许复制 Swift 类或结构体,同时更改任意字段。

概述

@CopyWithChanges 宏可以应用于结构体或类,以生成一个函数,该函数接收可选参数,每个参数对应目标的一个字段,并返回一个新的实例,该实例是原始实例的副本,除了提供的参数之外。 它支持可选类型。

原理

有时需要结构体的副本,但一个或多个字段存在差异。 正确的做法是使用每个字段的预期值调用 init,但这很笨拙。 通常发生的情况是,必须更改的字段被设置为可变,进行赋值复制,然后在复制后进行更改。

// original
let s1 = LargeStruct(a: 5, b: 2, c: 3, d: 7, e: 4, f: 1, g: 6)

// cumbersome copy changing b to 2
let s2 = LargeStruct(a: s1.a, b: 2, c: s1.c, d: s1.d, e: s1.e, f: s1.f, g: s1.g)

// reasonably simple copy + change, but requires that s3 AND b be mutable even if neither is changed outside this context
var s3 = s1
s3.b = 2

使实例可变是一个小的权衡,甚至可以通过使用初始块的某种变体来解决。 但是,仅仅为了方便初始化而使字段可变,可以被视为一种严重的反模式。

为了避免使字段可变的需求,@CopyWithChangesfunc with(...) 的形式提供所需的样板代码,它处理调用 init 时同时使用更改后的值和保留的值的样板代码。

// the solution proposed here
let s4 = s1.with(b: 2)

// the same, but changing two fields
let s5 = s1.with(c: 4, g: 7)

// will change f to nil (if field f is defined as Optional)
let s6 = s1.with(f: nil)

完整的行为是

该宏也可以应用于类。 在类或结构体中,都必须有一个逐个成员的 init。 在结构体中,除非在结构体声明中定义了自定义 init,否则编译器会自动合成此初始化器。 也可以通过 Xcode 的自动完成功能生成它。

用法

使用 @CopyWithChanges 宏注释目标类型以生成 func with(...) -> Self

import CopyWithChanges

@CopyWithChanges
struct Report {
    let venue: String
    let sponsor: String?
    let drinks: [String]
    let complexStructure: [Date: [(String, Int)]]
    let characters: [String]?
    let budget: Double
}

扩展后的代码

struct Report {
    let venue: String
    let sponsor: String?
    let drinks: [String]
    let complexStructure: [Date: [(String, Int)]]
    let characters: [String]?
    let budget: Double

    public func with(venue: String? = nil, sponsor: String?? = .some(nil), drinks: [String]? = nil, complexStructure: [Date: [(String, Int)]]? = nil, characters: [String]?? = .some(nil), budget: Double? = nil) -> Self {
        Self (
            venue: venue ?? self.venue,
            sponsor: sponsor == .none ? nil : self.sponsor,
            drinks: drinks ?? self.drinks,
            complexStructure: complexStructure ?? self.complexStructure,
            characters: characters == .none ? nil : self.characters,
            budget: budget ?? self.budget
        )
    }
}

安装

Swift Package Manager

Swift Package Manager 是一种用于自动化 Swift 代码分发的工具,并且集成到 swift 编译器中。

设置好 Swift 包后,添加 CopyWithChanges 作为依赖项就像将其添加到 Package.swiftdependencies 值中一样容易。

.package(url: "https://github.com/entonio/CopyWithChanges/tree/main", from: "1.1.0"),

然后,您可以通过将 CopyWithChanges 模块产品作为依赖项添加到您选择的 targetdependencies 值中,来将其添加到 target 中。

.product(name: "CopyWithChanges", package: "CopyWithChanges"),

许可

此包中的所有文件均为 NOTICE 文件中提到的包贡献者的版权所有,并根据 Apache 2.0 许可证获得许可,该许可证允许商业用途。