iosswiftstructmutating-function

Swift Mutation inside switch with associated values


I have these structs I want to mutate:

public struct CheckoutViewModel {
    var sections: [Section]
    var total: String

    public struct Section {
        var title: String
        var description: String
        var kind: Kind
        var expandState: ExpandState

        enum Kind {
            case products([ProductOrderViewModel])
            case shippingMode(SelectableArray<ShippingMode>)
            case shippingTarget(SelectableArray<ShippingKind>)
            case billingAddress(SelectableArray<Address>)
            case payment(SelectableArray<PaymentProvider>)
            case promoCode(String?)
            case legalAdvice(userAccepted: Bool)
        }
    }
}

struct SelectableArray<T> {
    var selectedIndex: Int?
    let options: [T]

    init(options: [T]) {
        self.options = options
        self.selectedIndex = nil
    }

    mutating func select(atIndex: Int) throws -> T {
        guard atIndex < options.count else {
            throw SelectableArrayError.outOfBoundsIndex
        }
        selectedIndex = atIndex
        return options[atIndex]
    }

    var selectedElement: T? {
        guard let selectedIndex = selectedIndex else { return nil }
        return options[selectedIndex]
    }
}

I want to use this mutating func select() method inside SelectableArray, I am calling it from a chain of mutating functions (because Sections is nested inside a struct)

extension CheckoutViewModel {  
    mutating func select(atIndexPath indexPath: IndexPath) {
        sections[indexPath.section].select(atIndex: indexPath.row)
    }
}

extension CheckoutViewModel.Section {
    mutating func select(atIndex idx: Int) {
        switch kind {
            case .shippingMode(var modes):
                do { _ = try modes.select(atIndex: idx) } catch { return }
            default:
                return nil
        }
        dump(self) // Here self hasn't changed
    }
}

The problem is that the CheckoutViewModel struct is never mutated. I am guessing that switch is not a mutating function, so var modes inside that switch is non mutable and then it does not matter if the following functions mutate anything. The workaround I managed to do is this:

mutating func select(atIndex idx: Int) {
    switch kind {
    case .shippingMode(var modes):
        do {
            _ = try modes.select(atIndex: idx)
            self.kind = .shippingMode(modes)
        } catch { return }
    default:
        return
    }
}

Do you have any oher solution to this problem? Is there any sort of mutating switch function that I can use?


Solution

  • According to The Swift Programming Language:

    A switch case can bind the value or values it matches to temporary constants or variables, for use in the body of the case. This behavior is known as value binding, because the values are bound to temporary constants or variables within the case’s body.

    Changes to such a temporary variable (e.g. the modes variable) do not affect the contents of the enum that is being switched on (e.g. kind).

    For your first approach to work you would indeed need a different sort of switch statement that creates a reference to the enum's associated value, allowing you to modify that value in place. Such a statement doesn't exist in Swift 3.0.1.