Home > Software design >  Simultaneous accesses to 0x14572f2a0, but modification requires exclusive access
Simultaneous accesses to 0x14572f2a0, but modification requires exclusive access

Time:09-21

I am following example code from the book "Design Patterns in Swift 5". The example is for the section on the State Pattern.

I am getting a memory access error: "Simultaneous accesses to 0x14572f2a0, but modification requires exclusive access." when I try to run the code.

I am not sure what's going on... Thanks in advance!

Code:

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)
            context.state.transactionCompleted(success: false)
            return
        }
        print("pin ok")
        context.state = WithdrawState(context: context)
    }
}

fileprivate struct TransactionCompleteState: ATMState {
    let context: ATM
    
    func transactionCompleted(success: Bool) {
        var statusMessage = success ? "Transaction complete..." : "Transaction failed..."
        print(statusMessage)
        context.state = IdleState()
    }
}

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)
            context.state.transactionCompleted(success: false)
            return false
        }
        availableFunds -= amount
        context.state = TransactionCompleteState(context: context)
        context.state.transactionCompleted(success: true)
        return true
    }
}

let atm = ATM()
atm.enter(pin:"1234")
atm.withdraw(amount: 10)

CodePudding user response:

This is what you get for testing the code in a playground — there's no debugger! If you try that code in a real app, the debugger tells you exactly what the problem is.

The problem is here:

public func withdraw(amount: Float) -> Bool {
    return state.withdraw(amount: amount) // <-- *
}

The withdraw state's withdraw method sets the state property of its context, which is this ATM instance. So the call to state.withdraw means that we both mutate this ATM's state value directly, by replacing it with a different state object, and call its mutating method withdraw, in a single move. You can't do that, because the double mutation, to a property of the struct and to a reference to the struct as a whole, is incoherent. You have to proceed in stages, like this:

public func withdraw(amount: Float) -> Bool {
    // fetch
    var theState = self.state
    // mutate
    return theState.withdraw(amount: amount)
}

The alternative would be to make all of the state types classes instead of structs. A class, unlike a struct, is mutable in place. That would require considerably more rewriting, however, so that is left as an exercise for the reader.

  • Related