Swift中如何从Setter抛出错误?附账户结构体优化需求
Swift属性Setter能否抛出错误?
首先明确:Swift的属性setter不支持抛出错误,这是语言层面的设计限制,无法直接在set块中使用throw语句。针对你的需求,下面提供几种兼顾语法自然性和错误处理的重构方案:
方案1:用@dynamicMemberLookup实现可抛出的"类赋值"语法
通过@dynamicMemberLookup特性,我们可以模拟原生赋值的语法风格,同时支持错误抛出。步骤如下:
- 先定义错误类型:
enum AccountError: Error { case unresolvedStrikes case invalidLevelIncrease }
- 修改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
相关产品推荐
相关产品推荐

