You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中如何从Setter抛出错误?附账户结构体优化需求

Swift属性Setter能否抛出错误?

首先明确:Swift的属性setter不支持抛出错误,这是语言层面的设计限制,无法直接在set块中使用throw语句。针对你的需求,下面提供几种兼顾语法自然性和错误处理的重构方案:

方案1:用@dynamicMemberLookup实现可抛出的"类赋值"语法

通过@dynamicMemberLookup特性,我们可以模拟原生赋值的语法风格,同时支持错误抛出。步骤如下:

  1. 先定义错误类型:
enum AccountError: Error {
    case unresolvedStrikes
    case invalidLevelIncrease
}
  1. 修改Account结构体,添加动态成员查找支持:
@dynamicMemberLookup
struct Account {
    var strikes: Int = 0
    private var _monetizationLevel: Int = 1
    
    // 保持原生只读的getter
    var monetizationLevel: Int {
        _monetizationLevel
    }
    
    // 实现动态成员下标,模拟赋值并支持抛出错误
    subscript(dynamicMember key: String) -> (Int) throws -> Void {
        switch key {
        case "monetizationLevel":
            return { [weak self] newValue in
                guard let self = self else { return }
                guard self.strikes == 0 else {
                    throw AccountError.unresolvedStrikes
                }
                guard newValue <= self._monetizationLevel else {
                    throw AccountError.invalidLevelIncrease
                }
                self._monetizationLevel = newValue
            }
        default:
            fatalError("不支持的属性:\(key)")
        }
    }
}

使用方式(语法接近原生赋值):

var myAccount = Account()
do {
    try myAccount.monetizationLevel(2)
} catch AccountError.unresolvedStrikes {
    print("需先解决版权警告才能调整变现等级")
} catch AccountError.invalidLevelIncrease {
    print("变现等级仅能由服务器管理员提升")
}

方案2:使用明确的抛出方法(语义清晰,社区推荐)

虽然你觉得违背Setter简化赋值的初衷,但在Swift中,可能失败的修改操作更适合用方法而非属性——因为属性赋值通常被预期是无副作用、必然成功的。这种方案语义最清晰,也符合Swift的设计哲学:

enum AccountError: Error {
    case unresolvedStrikes
    case invalidLevelIncrease
}

struct Account {
    var strikes: Int = 0
    private(set) var monetizationLevel: Int = 1 // 设为private(set),禁止直接赋值
    
    mutating func setMonetizationLevel(to level: Int) throws {
        guard strikes == 0 else {
            throw AccountError.unresolvedStrikes
        }
        guard level <= monetizationLevel else {
            throw AccountError.invalidLevelIncrease
        }
        monetizationLevel = level
    }
}

使用方式:

var myAccount = Account()
do {
    try myAccount.setMonetizationLevel(to: 2)
} catch {
    // 根据错误类型做对应处理
}

方案3:使用Result类型的回调式赋值

如果希望更贴近赋值语法,可以用一个返回Result的属性,但语法稍显繁琐:

enum AccountError: Error {
    case unresolvedStrikes
    case invalidLevelIncrease
}

struct Account {
    var strikes: Int = 0
    private var _monetizationLevel: Int = 1
    
    var monetizationLevel: Int {
        _monetizationLevel
    }
    
    var updateMonetizationLevel: (Int) -> Result<Void, AccountError> {
        { [weak self] newValue in
            guard let self = self else { return .success(()) }
            guard self.strikes == 0 else {
                return .failure(.unresolvedStrikes)
            }
            guard newValue <= self._monetizationLevel else {
                return .failure(.invalidLevelIncrease)
            }
            self._monetizationLevel = newValue
            return .success(())
        }
    }
}

使用方式:

let result = myAccount.updateMonetizationLevel(2)
switch result {
case .success:
    // 处理成功逻辑
case .failure(let error):
    // 处理错误逻辑
}

总结

  • 原生Setter无法抛出错误,这是语言设计的硬性限制;
  • 若追求接近赋值的语法,方案1的@dynamicMemberLookup是最优选择;
  • 若注重代码语义清晰,方案2的抛出方法更符合Swift社区的最佳实践;

内容的提问来源于stack exchange,提问作者rayaantaneja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:42:45