简体   繁体   中英

Using Swift protocol delegation on a struct to change values?

I have a project where I want there are factories with orders (an array of Ints ) that can be mutated.

I want all the code mutating, adding, removing, validating, etc of orders in another class (ie: almost like a proxy pattern) and when ready update the factory with the new orders.

I follow a delegate pattern to kick the new orders back to the factory for updating, however the factory orders never update.

Note: I know this is because the factory is a struct and that it is a value type

I am wondering if its possible to update the struct using a delegate pattern; or must I change it to a reference type (a class) in order to resolve the issue.

In the following code I've stripped out all the validation, push, pop and other features and am keeping it simple for this query by force changing the order array and then using a delegate to kick back the changed orders.

// Swift playground code

protocol OrderUpdatedDelegate {
    mutating func ordersDidUpdate(_ orders: [Int])
}

//  This class will handle all the validation to do with 
// orders array, but for now; lets just force 
// change the orders to test the delegate pattern
class OrderBook {
    var delegate: OrderUpdatedDelegate?
    var orders: [Int] = [Int]()

    init(orders: [Int]) {
        self.orders = orders
    }

    func changeOrders() {
        self.orders = [7,8,1]
        print ("updated orders to -> \(orders)")
        delegate?.ordersDidUpdate(orders)
    }
}

struct Factory {
    var orders: [Int] = [Int]()

    init(orders: [Int]) {
        self.orders = orders
    }
}

extension Factory: OrderUpdatedDelegate {
    mutating func ordersDidUpdate(_ orders: [Int]) {
        print ("recieved: \(orders)")
        self.orders = orders
    }
}

var shop = Factory(orders: [1,2,3])
let book = OrderBook.init(orders: shop.orders)
book.changeOrders()

print ("\nBook.orders = \(book.orders)")
print ("Shop.orders = \(shop.orders)")

Output:

Book.orders = [7, 8, 1]

Shop.orders = [1, 2, 3]

Again, I know the reason is because I've declared factory to be a struct ; but I'm wondering if its possible to use a delegate pattern to mutate the orders array within the struct?

If not, I'll change it to a class; but I appreciate any feedback on this.

With thanks

There are 2 problems with your code, both of which needs fixing for it to work:

  • using a value type
  • not setting the delegate

Once you set the delegate, you'll see ordersDidUpdate actually getting called, but shop.orders will still have its original value. That is because as soon as you mutate your Factory , the delegate set on OrderBook will be a different object from the mutated Factory , which was updated in the delegate call ordersDidUpdate .

Using a reference type fixes this issue.

Couple of things to keep in mind when you switch to a class delegate. Make your OrderUpdatedDelegate be a class-bound protocol, then remove mutating from the function declaration. And most importantly, always declare class-bound delegates as weak to avoid strong reference cycles.

protocol OrderUpdatedDelegate: class {
    func ordersDidUpdate(_ orders: [Int])
}

//  This class will handle all the validation to do with
// orders array, but for now; lets just force
// change the orders to test the delegate pattern
class OrderBook {
    weak var delegate: OrderUpdatedDelegate?
    var orders: [Int] = []

    init(orders: [Int]) {
        self.orders = orders
    }

    func changeOrders() {
        self.orders = [7,8,1]
        print ("updated orders to -> \(orders)")
        delegate?.ordersDidUpdate(orders)
    }
}

class Factory {
    var orders: [Int] = []

    init(orders: [Int]) {
        self.orders = orders
    }
}

extension Factory: OrderUpdatedDelegate {
    func ordersDidUpdate(_ orders: [Int]) {
        print("receieved: \(orders)")
        self.orders = orders
        print("updated order: \(self.orders)")
    }
}

var shop = Factory(orders: [1,2,3])
let book = OrderBook(orders: shop.orders)
book.delegate = shop
book.changeOrders()

print ("Book.orders = \(book.orders)")
print ("Shop.orders = \(shop.orders)")

As you said since Factory is a struct, when setting OrderBook delegate its already copied there so the delegate is actually a copy of your original factory instance.

A class is the appropriate solution for this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM