Swift @propertyWrapper(属性包装器)与 Result Builders (结果构建器)

属性包装器 (Property Wrappers)

基本概念

属性包装器是 Swift 5.1 引入的特性,允许你将通用的属性访问逻辑封装到可重用的包装器中。通过 @propertyWrapper 属性标记,可以创建自定义的属性行为。

基础语法

@propertyWrapper
struct MyWrapper<T> {
    private var value: T

    init(wrappedValue: T) {
        self.value = wrappedValue
    }

    var wrappedValue: T {
        get { value }
        set { value = newValue }
    }
}

// 使用
struct MyStruct {
    @MyWrapper var property: String = "Hello"
}

核心组件详解

1. wrappedValue - 包装值

@propertyWrapper
struct Capitalized {
    private var value: String = ""

    var wrappedValue: String {
        get { value }
        set { value = newValue.capitalized }
    }

    init(wrappedValue: String) {
        self.value = wrappedValue.capitalized
    }
}

struct Person {
    @Capitalized var firstName: String = ""
    @Capitalized var lastName: String = ""
}

var person = Person()
person.firstName = "john"     // 自动转换为 "John"
person.lastName = "doe"       // 自动转换为 "Doe"
print("\(person.firstName) \(person.lastName)") // "John Doe"

2. projectedValue - 投影值

@propertyWrapper
struct Logged<T> {
    private var value: T
    private(set) var accessCount = 0

    init(wrappedValue: T) {
        self.value = wrappedValue
    }

    var wrappedValue: T {
        get {
            accessCount += 1
            return value
        }
        set {
            value = newValue
        }
    }

    // 投影值:通过 $ 前缀访问
    var projectedValue: Int {
        accessCount
    }
}

struct DataModel {
    @Logged var data: String = "Initial"
}

var model = DataModel()
print(model.data)        // "Initial" (访问计数 +1)
print(model.data)        // "Initial" (访问计数 +1)
print(model.$data)       // 2 (通过 $ 访问投影值)

常用属性包装器实现

1. UserDefaults 包装器

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T

    var wrappedValue: T {
        get {
            UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }

    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
}

// 使用示例
struct Settings {
    @UserDefault("username", defaultValue: "")
    static var username: String

    @UserDefault("isFirstLaunch", defaultValue: true)
    static var isFirstLaunch: Bool

    @UserDefault("maxConnections", defaultValue: 5)
    static var maxConnections: Int
}

// 使用
Settings.username = "JohnDoe"
print(Settings.username)     // "JohnDoe"
print(Settings.isFirstLaunch) // true

2. 数值范围限制包装器

@propertyWrapper
struct Clamped<T: Comparable> {
    private var value: T
    private let range: ClosedRange<T>

    init(wrappedValue: T, _ range: ClosedRange<T>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }

    var wrappedValue: T {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }
}

struct GameCharacter {
    @Clamped(0...100) var health: Int = 100
    @Clamped(0...10) var level: Int = 1
    @Clamped(0.0...1.0) var volume: Double = 0.5
}

var character = GameCharacter()
character.health = 150  // 自动限制为 100
character.level = -5    // 自动限制为 0
character.volume = 1.5  // 自动限制为 1.0

print(character.health)  // 100
print(character.level)   // 0
print(character.volume)  // 1.0

3. 延迟初始化包装器

@propertyWrapper
struct Lazy<T> {
    private var _value: T?
    private let initializer: () -> T

    init(wrappedValue: @escaping @autoclosure () -> T) {
        self.initializer = wrappedValue
    }

    var wrappedValue: T {
        mutating get {
            if _value == nil {
                _value = initializer()
                print("懒加载初始化")
            }
            return _value!
        }
        set {
            _value = newValue
        }
    }
}

struct ExpensiveResource {
    @Lazy var data = createExpensiveData()

    private func createExpensiveData() -> [String] {
        print("创建昂贵的数据...")
        return Array(1...1000).map { "Item \($0)" }
    }
}

var resource = ExpensiveResource()
// 此时还没有创建数据
print("准备访问数据...")
let firstItem = resource.data.first // 现在才创建数据

4. 线程安全包装器

@propertyWrapper
struct Atomic<T> {
    private var value: T
    private let queue = DispatchQueue(label: "atomic.queue", attributes: .concurrent)

    init(wrappedValue: T) {
        self.value = wrappedValue
    }

    var wrappedValue: T {
        get {
            queue.sync { value }
        }
        set {
            queue.async(flags: .barrier) { [weak self] in
                self?.value = newValue
            }
        }
    }
}

class Counter {
    @Atomic var count: Int = 0

    func increment() {
        count += 1
    }
}

// 多线程安全的计数器
let counter = Counter()
DispatchQueue.concurrentPerform(iterations: 1000) { _ in
    counter.increment()
}

高级特性

1. 带参数的属性包装器

@propertyWrapper
struct Trimmed {
    private var value: String = ""
    private let characterSet: CharacterSet

    init(wrappedValue: String, _ characterSet: CharacterSet = .whitespacesAndNewlines) {
        self.characterSet = characterSet
        self.value = wrappedValue.trimmingCharacters(in: characterSet)
    }

    var wrappedValue: String {
        get { value }
        set { value = newValue.trimmingCharacters(in: characterSet) }
    }
}

struct FormData {
    @Trimmed var name: String = ""
    @Trimmed(.punctuationCharacters) var cleanText: String = ""
}

var form = FormData()
form.name = "   John Doe   "      // 自动去除空白字符
form.cleanText = "Hello, World!"  // 自动去除标点符号

print("'\(form.name)'")           // "'John Doe'"
print("'\(form.cleanText)'")      // "'Hello World'"

2. 组合属性包装器

@propertyWrapper
struct ValidatedEmail {
    private var value: String = ""

    var wrappedValue: String {
        get { value }
        set {
            if isValidEmail(newValue) {
                value = newValue
            } else {
                print("无效的邮箱地址: \(newValue)")
            }
        }
    }

    private func isValidEmail(_ email: String) -> Bool {
        let emailRegex = #"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"#
        return email.range(of: emailRegex, options: .regularExpression) != nil
    }

    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue
    }
}

struct User {
    @Trimmed @ValidatedEmail var email: String = ""
    // 注意:多个包装器的组合需要仔细考虑顺序
}


结果构建器 (Result Builders)

基本概念

结果构建器是 Swift 5.4 引入的特性(前身是 Function Builders),允许你创建领域特定语言(DSL)。它可以将一系列表达式转换为单一的结果。

基础语法

@resultBuilder
struct StringBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined(separator: " ")
    }
}

// 使用结果构建器
@StringBuilder
func makeMessage() -> String {
    "Hello"
    "World"
    "from"
    "Swift"
}

print(makeMessage()) // "Hello World from Swift"

核心方法详解

1. buildBlock - 构建块

@resultBuilder
struct ArrayBuilder<T> {
    static func buildBlock(_ components: T...) -> [T] {
        Array(components)
    }
}

@ArrayBuilder<Int>
func buildNumbers() -> [Int] {
    1
    2
    3
    4
    5
}

print(buildNumbers()) // [1, 2, 3, 4, 5]

2. buildIf - 条件构建

@resultBuilder
struct ConditionalBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined(separator: "\n")
    }

    static func buildIf(_ component: String?) -> String {
        component ?? ""
    }
}

@ConditionalBuilder
func makeConditionalMessage(includeGreeting: Bool) -> String {
    if includeGreeting {
        "Hello!"
    }
    "This is a message."
    "End of message."
}

print(makeConditionalMessage(includeGreeting: true))
// Hello!
// This is a message.
// End of message.

print(makeConditionalMessage(includeGreeting: false))
// This is a message.
// End of message.

3. buildEither - 分支构建

@resultBuilder
struct EitherBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined(separator: "\n")
    }

    static func buildEither(first component: String) -> String {
        "First: \(component)"
    }

    static func buildEither(second component: String) -> String {
        "Second: \(component)"
    }
}

@EitherBuilder
func makeEitherMessage(useFirst: Bool) -> String {
    if useFirst {
        "This is the first option"
    } else {
        "This is the second option"
    }
}

print(makeEitherMessage(useFirst: true))  // "First: This is the first option"
print(makeEitherMessage(useFirst: false)) // "Second: This is the second option"

4. buildArray - 数组构建

@resultBuilder
struct LoopBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined(separator: "\n")
    }

    static func buildArray(_ components: [String]) -> String {
        components.enumerated().map { index, item in
            "\(index + 1). \(item)"
        }.joined(separator: "\n")
    }
}

@LoopBuilder
func makeList() -> String {
    for fruit in ["Apple", "Banana", "Orange"] {
        "I like \(fruit)"
    }
}

print(makeList())
// 1\. I like Apple
// 2\. I like Banana
// 3\. I like Orange

实际应用示例

1. HTML 构建器

@resultBuilder
struct HTMLBuilder {
    static func buildBlock(_ components: HTMLElement...) -> HTMLElement {
        HTMLElement(tag: "div", content: components.map(\.html).joined())
    }

    static func buildIf(_ component: HTMLElement?) -> HTMLElement {
        component ?? HTMLElement(tag: "div", content: "")
    }

    static func buildEither(first component: HTMLElement) -> HTMLElement {
        component
    }

    static func buildEither(second component: HTMLElement) -> HTMLElement {
        component
    }

    static func buildArray(_ components: [HTMLElement]) -> HTMLElement {
        HTMLElement(tag: "div", content: components.map(\.html).joined())
    }
}

struct HTMLElement {
    let tag: String
    let content: String
    let attributes: [String: String]

    init(tag: String, content: String, attributes: [String: String] = [:]) {
        self.tag = tag
        self.content = content
        self.attributes = attributes
    }

    var html: String {
        let attrs = attributes.map { key, value in
            " \(key)=\"\(value)\""
        }.joined()

        return "<\(tag)\(attrs)>\(content)</\(tag)>"
    }
}

// HTML 元素构造函数
func h1(_ content: String, attributes: [String: String] = [:]) -> HTMLElement {
    HTMLElement(tag: "h1", content: content, attributes: attributes)
}

func p(_ content: String, attributes: [String: String] = [:]) -> HTMLElement {
    HTMLElement(tag: "p", content: content, attributes: attributes)
}

func div(@HTMLBuilder content: () -> HTMLElement) -> HTMLElement {
    HTMLElement(tag: "div", content: content().html)
}

func ul(@HTMLBuilder content: () -> HTMLElement) -> HTMLElement {
    HTMLElement(tag: "ul", content: content().html)
}

func li(_ content: String) -> HTMLElement {
    HTMLElement(tag: "li", content: content)
}

// 使用示例
@HTMLBuilder
func buildWebPage(showWelcome: Bool) -> HTMLElement {
    h1("我的网站")

    if showWelcome {
        p("欢迎访问我的网站!")
    }

    div {
        p("这是一个段落")
        ul {
            for item in ["项目1", "项目2", "项目3"] {
                li(item)
            }
        }
    }
}

let webpage = buildWebPage(showWelcome: true)
print(webpage.html)

2. SQL 查询构建器

@resultBuilder
struct SQLBuilder {
    static func buildBlock(_ components: SQLComponent...) -> String {
        components.map(\.sql).joined(separator: " ")
    }

    static func buildIf(_ component: SQLComponent?) -> SQLComponent {
        component ?? SQLComponent("")
    }
}

struct SQLComponent {
    let sql: String

    init(_ sql: String) {
        self.sql = sql
    }
}

func SELECT(_ columns: String...) -> SQLComponent {
    SQLComponent("SELECT \(columns.joined(separator: ", "))")
}

func FROM(_ table: String) -> SQLComponent {
    SQLComponent("FROM \(table)")
}

func WHERE(_ condition: String) -> SQLComponent {
    SQLComponent("WHERE \(condition)")
}

func ORDER_BY(_ column: String) -> SQLComponent {
    SQLComponent("ORDER BY \(column)")
}

func LIMIT(_ count: Int) -> SQLComponent {
    SQLComponent("LIMIT \(count)")
}

@SQLBuilder
func buildUserQuery(includeOrder: Bool = false) -> String {
    SELECT("id", "name", "email")
    FROM("users")
    WHERE("active = 1")

    if includeOrder {
        ORDER_BY("name")
    }

    LIMIT(10)
}

print(buildUserQuery(includeOrder: true))
// SELECT id, name, email FROM users WHERE active = 1 ORDER BY name LIMIT 10

3. 配置构建器

@resultBuilder
struct ConfigurationBuilder {
    static func buildBlock(_ components: ConfigurationItem...) -> Configuration {
        Configuration(items: components)
    }

    static func buildIf(_ component: ConfigurationItem?) -> ConfigurationItem {
        component ?? ConfigurationItem(key: "", value: "")
    }
}

struct ConfigurationItem {
    let key: String
    let value: String
}

struct Configuration {
    let items: [ConfigurationItem]

    subscript(key: String) -> String? {
        items.first { $0.key == key }?.value
    }
}

func setting(_ key: String, _ value: String) -> ConfigurationItem {
    ConfigurationItem(key: key, value: value)
}

@ConfigurationBuilder
func buildAppConfig(isDevelopment: Bool) -> Configuration {
    setting("appName", "MyApp")
    setting("version", "1.0.0")

    if isDevelopment {
        setting("baseURL", "https://dev.api.example.com")
        setting("debugMode", "true")
    } else {
        setting("baseURL", "https://api.example.com")
        setting("debugMode", "false")
    }

    setting("timeout", "30")
}

let config = buildAppConfig(isDevelopment: true)
print(config["baseURL"]) // Optional("https://dev.api.example.com")
print(config["debugMode"]) // Optional("true")


实际应用场景

1. SwiftUI 中的应用

// SwiftUI 大量使用了结果构建器
struct ContentView: View {
    @State private var isVisible = true

    var body: some View { // 这里使用了 @ViewBuilder
        VStack {
            Text("Hello, World!")

            if isVisible {
                Text("我是可见的")
            }

            Button("切换可见性") {
                isVisible.toggle()
            }
        }
    }
}

2. 数据验证场景

@propertyWrapper
struct Validated<T> {
    private var value: T
    private let validator: (T) -> Bool
    private let errorMessage: String

    init(wrappedValue: T, validator: @escaping (T) -> Bool, errorMessage: String) {
        self.validator = validator
        self.errorMessage = errorMessage
        if validator(wrappedValue) {
            self.value = wrappedValue
        } else {
            fatalError(errorMessage)
        }
    }

    var wrappedValue: T {
        get { value }
        set {
            if validator(newValue) {
                value = newValue
            } else {
                print("验证失败: \(errorMessage)")
            }
        }
    }
}

struct UserRegistration {
    @Validated(
        validator: { $0.count >= 3 },
        errorMessage: "用户名至少需要3个字符"
    )
    var username: String = ""

    @Validated(
        validator: { $0.contains("@") && $0.contains(".") },
        errorMessage: "邮箱格式不正确"
    )
    var email: String = ""

    @Validated(
        validator: { $0.count >= 8 },
        errorMessage: "密码至少需要8个字符"
    )
    var password: String = ""
}

3. API 构建场景

@resultBuilder
struct APIBuilder {
    static func buildBlock(_ components: APIComponent...) -> APIRequest {
        APIRequest(components: components)
    }
}

protocol APIComponent {
    func apply(to request: inout URLRequest)
}

struct APIRequest {
    private let components: [APIComponent]

    init(components: [APIComponent]) {
        self.components = components
    }

    func build(baseURL: URL) -> URLRequest {
        var request = URLRequest(url: baseURL)
        components.forEach { $0.apply(to: &request) }
        return request
    }
}

struct HTTPMethod: APIComponent {
    let method: String

    func apply(to request: inout URLRequest) {
        request.httpMethod = method
    }
}

struct Header: APIComponent {
    let key: String
    let value: String

    func apply(to request: inout URLRequest) {
        request.setValue(value, forHTTPHeaderField: key)
    }
}

struct JSONBody: APIComponent {
    let data: Data

    func apply(to request: inout URLRequest) {
        request.httpBody = data
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    }
}

// 便利函数
func GET() -> HTTPMethod { HTTPMethod(method: "GET") }
func POST() -> HTTPMethod { HTTPMethod(method: "POST") }
func header(_ key: String, _ value: String) -> Header { Header(key: key, value: value) }
func jsonBody<T: Encodable>(_ object: T) -> JSONBody? {
    guard let data = try? JSONEncoder().encode(object) else { return nil }
    return JSONBody(data: data)
}

@APIBuilder
func buildLoginRequest() -> APIRequest {
    POST()
    header("Content-Type", "application/json")
    header("Authorization", "Bearer token")
}

let baseURL = URL(string: "https://api.example.com/login")!
let request = buildLoginRequest().build(baseURL: baseURL)


最佳实践与注意事项

属性包装器最佳实践

1. 命名约定

// ✅ 好的命名
@propertyWrapper struct Capitalized { }
@propertyWrapper struct UserDefault { }
@propertyWrapper struct Atomic { }

// ❌ 避免的命名
@propertyWrapper struct MyWrapper { }
@propertyWrapper struct Thing { }

2. 初始化器设计

@propertyWrapper
struct Clamped<T: Comparable> {
    // ✅ 提供多种初始化方式
    init(wrappedValue: T, _ range: ClosedRange<T>) {
        // 实现
    }

    init(_ range: ClosedRange<T>) {
        // 提供默认值的便利初始化器
    }
}

// 使用
struct Example {
    @Clamped(0...100) var percentage: Int = 50  // 使用第一个初始化器
    @Clamped(0...10) var level: Int            // 使用第二个初始化器,需要后续赋值
}

3. 投影值的合理使用

@propertyWrapper
struct Published<T> {
    private var value: T

    var wrappedValue: T {
        get { value }
        set {
            value = newValue
            publisher.send(newValue)
        }
    }

    // ✅ 投影值提供额外功能
    var projectedValue: AnyPublisher<T, Never> {
        publisher.eraseToAnyPublisher()
    }

    private let publisher = PassthroughSubject<T, Never>()

    init(wrappedValue: T) {
        self.value = wrappedValue
    }
}

结果构建器最佳实践

1. 完整的方法实现

@resultBuilder
struct CompleteBuilder {
    // 基础构建
    static func buildBlock(_ components: String...) -> String {
        components.joined(separator: "\n")
    }

    // 条件构建
    static func buildIf(_ component: String?) -> String {
        component ?? ""
    }

    // 分支构建
    static func buildEither(first component: String) -> String {
        component
    }

    static func buildEither(second component: String) -> String {
        component
    }

    // 数组构建(循环)
    static func buildArray(_ components: [String]) -> String {
        components.joined(separator: "\n")
    }

    // 可选构建
    static func buildOptional(_ component: String?) -> String {
        component ?? ""
    }
}

2. 类型安全

// ✅ 类型安全的构建器
@resultBuilder
struct TypeSafeBuilder<T> {
    static func buildBlock(_ components: T...) -> [T] {
        Array(components)
    }
}

// ❌ 避免类型不安全
@resultBuilder
struct UnsafeBuilder {
    static func buildBlock(_ components: Any...) -> String {
        // 可能导致运行时错误
        components.map { "\($0)" }.joined()
    }
}

性能考虑

1. 属性包装器性能

// ✅ 高效的实现
@propertyWrapper
struct Efficient<T> {
    private var _value: T

    var wrappedValue: T {
        get { _value }
        set { _value = newValue }
    }
}

// ❌ 低效的实现
@propertyWrapper
struct Inefficient<T> {
    var wrappedValue: T {
        get {
            // 每次访问都进行昂贵的计算
            return performExpensiveOperation()
        }
        set {
            // 复杂的设置逻辑
        }
    }
}

2. 结果构建器性能

// ✅ 高效的字符串构建
@resultBuilder
struct EfficientStringBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined() // 直接连接
    }
}

// ❌ 低效的实现
@resultBuilder
struct InefficientStringBuilder {
    static func buildBlock(_ components: String...) -> String {
        var result = ""
        for component in components {
            result += component // 多次字符串拷贝
        }
        return result
    }
}

调试技巧

1. 添加调试信息

@propertyWrapper
struct DebugWrapper<T> {
    private var value: T
    private let name: String

    init(wrappedValue: T, _ name: String = #function) {
        self.value = wrappedValue
        self.name = name
        print("🐛 初始化 \(name): \(wrappedValue)")
    }

    var wrappedValue: T {
        get {
            print("🐛 读取 \(name): \(value)")
            return value
        }
        set {
            print("🐛 设置 \(name): \(value) -> \(newValue)")
            value = newValue
        }
    }
}

2. 错误处理

@propertyWrapper
struct SafeWrapper<T> {
    private var value: T
    private let validator: (T) -> Bool

    var wrappedValue: T {
        get { value }
        set {
            guard validator(newValue) else {
                print("⚠️ 验证失败,保持原值: \(value)")
                return
            }
            value = newValue
        }
    }

    init(wrappedValue: T, validator: @escaping (T) -> Bool) {
        self.validator = validator
        if validator(wrappedValue) {
            self.value = wrappedValue
        } else {
            fatalError("初始值验证失败")
        }
    }
}

这两个特性是现代 Swift 开发中非常重要的工具,它们可以显著提高代码的可读性、可维护性和复用性。在实际项目中,合理使用这些特性可以创建更优雅和类型安全的 API。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容