基于《Swift 5设计模式》状态模式示例代码出现内存访问错误的求助
嘿,我刚仔细看了你的代码,这个内存访问错误的原因其实挺明确的——你在同一代码路径里同时修改和访问了ATM实例的state属性,触发了Swift的内存独占访问检查。
具体来说,比如在EnterPinState的validate方法里:
context.state = TransactionCompleteState(context:context) context.state.transactionCompleted(success: false)
你刚把context.state赋值为新的状态对象,紧接着又去读取这个属性调用方法。Swift会认为这时候修改操作还没完全完成,你又发起了访问,违反了“修改时需要独占访问”的规则。同样的问题也出现在WithdrawState的withdraw方法里。
这里有两个靠谱的解决方案,你可以选一个适合你的:
方案一:延迟后续方法调用
把状态切换后的方法调用放到下一个运行循环,确保状态修改完成后再执行访问操作。比如修改EnterPinState的代码:
guard pin == "1234" else { print("wrong pin") context.state = TransactionCompleteState(context:context) // 延迟到下一个主队列循环执行 DispatchQueue.main.async { context.state.transactionCompleted(success: false) } return }
同样,把WithdrawState里对应的transactionCompleted调用也用DispatchQueue.main.async包裹起来。这种方式改动小,适合快速修复。
方案二:重构状态逻辑(更优雅)
既然TransactionCompleteState的核心工作就是处理交易完成的逻辑,不如把这个逻辑移到它的初始化方法里,避免切换状态后还要手动调用方法。修改后的TransactionCompleteState和相关调用如下:
首先重构TransactionCompleteState:
fileprivate struct TransactionCompleteState: ATMState { let context: ATM init(context: ATM, success: Bool) { self.context = context let statusMessage = success ? "Transaction complete..." : "Transaction failed..." print(statusMessage) context.state = IdleState() } // 协议方法留空,因为不需要外部调用 func transactionCompleted(success: Bool) {} }
然后修改EnterPinState里的错误分支:
guard pin == "1234" else { print("wrong pin") // 初始化时直接处理完成逻辑 context.state = TransactionCompleteState(context:context, success: false) return }
再修改WithdrawState里的对应部分:
guard amount > 0 && availableFunds >= amount else { print("invalid") context.state = TransactionCompleteState(context: context, success: false) return false } availableFunds -= amount context.state = TransactionCompleteState(context: context, success: true) return true
这种方式让状态对象的职责更清晰,也从根源上避免了同时访问的问题,是更符合状态模式设计思想的做法。
最后,我把用方案二修改后的完整代码贴出来,你可以直接运行测试:
import Foundation public final class ATM { fileprivate var state: ATMState = IdleState() public func enter(pin: String) { state = EnterPinState(context: self) state.validate(pin: pin) } public func withdraw(amount: Float) -> Bool { return state.withdraw(amount: amount) } } fileprivate protocol ATMState { func validate(pin: String) mutating func withdraw(amount: Float) -> Bool func transactionCompleted(success:Bool) } extension ATMState { func validate(pin:String) { print("not implemented") } mutating func withdraw(amount: Float) -> Bool { print("not implemented") return false } func transactionCompleted(success:Bool) { print("not implemented") } } fileprivate struct IdleState: ATMState { } fileprivate struct EnterPinState: ATMState { let context: ATM func validate(pin: String) { guard pin == "1234" else { print("wrong pin") context.state = TransactionCompleteState(context:context, success: false) return } print("pin ok") context.state = WithdrawState(context: context) } } fileprivate struct TransactionCompleteState: ATMState { let context: ATM init(context: ATM, success: Bool) { self.context = context let statusMessage = success ? "Transaction complete..." : "Transaction failed..." print(statusMessage) context.state = IdleState() } func transactionCompleted(success: Bool) {} } fileprivate struct WithdrawState: ATMState { var context: ATM var availableFunds: Float = 1000 init(context: ATM) { self.context = context } mutating func withdraw(amount: Float) -> Bool { print("Withdraw $\(amount)") guard amount > 0 && availableFunds >= amount else { print("invalid") context.state = TransactionCompleteState(context: context, success: false) return false } availableFunds -= amount context.state = TransactionCompleteState(context: context, success: true) return true } } let atm = ATM() atm.enter(pin:"1234") atm.withdraw(amount: 10)
运行这段代码,你会发现那个内存访问错误已经消失,程序会正常输出:
pin ok Withdraw $10.0 Transaction complete...
内容的提问来源于stack exchange,提问作者Michael Garfinkle

