0

We are creating a global struct where we store our products for a shopping cart. Here is the code to create it

class Cart : NSObject {
    var allProductsInCart = [Product]()

    class var sharedCart: Cart {
        struct Static {
            static let instance = Cart()
        }
        return Static.instance
    }
}

In a separate view controller (called ProductVC), we are creating an instance of Product. We add that product to the array listed above, allProductsInCart like this and then change the value:

let newProduct = Product()
newProduct.name = "Costco"
Cart.sharedCart.allProductsInCart.append(newProduct)
newProduct.name = "test2"
print ("value is: \(Cart.sharedCart.allProductsInCart[0].name)") //It prints "test2" not "Costco" 

When the separate instance of product is modified in ProductVC, it is also changed in the struct. It is definitely a separate instance of product because it has a different variable name as seen above.

It should print Costco still because the instance of Product within the ProductVC was modified after it was added to the struct and we want to print the instance in the struct. Right? Or do I have something wrong?

Is this normal behavior? Is there a reason this is happening? Is there a better way that a global variable is supposed to be created or a better way to create a sharedCart that we can access in any view controller?

Nevin Jethmalani
  • 2,726
  • 4
  • 23
  • 58

2 Answers2

3

This happens because newProduct is a reference type (defined by a class) so when you change the name it just changes the name of the same reference. There is only one product in the cart at this point, not two. For reference, the easiest way to define a singleton in Swift would be

class Cart {
    static let shared = Cart()

    var products = [Product]()
}

So, just following your example:

let newProduct1 = Product()
newProduct1.name = "Costco"

Cart.sharedCart.products.append(newProduct1)

let newProduct2 = Product()  // a new product
newProduct2.name = "test2"
// I assume you will want to add this product as well
Cart.shared.products.append(newProduct2)

//This will print "Costco" 
print ("value is: \(Cart.sharedCart.products[0].name)") 
Guillermo Alvarez
  • 1,695
  • 2
  • 18
  • 23
  • We don't want to have a second new product. We want to change the value of the first newProduct1 to "test2" and have it only modify the local instance of variable newProduct1 not the one in the shared cart. – Nevin Jethmalani Jul 29 '17 at 00:04
  • I just tried it. Even if we change class var sharedCart: Cart { struct Static { static let instance = Cart() } return Static.instance } to this: static let shared = Cart() it still does not fix the issue. – Nevin Jethmalani Jul 29 '17 at 00:07
  • I think there is a misunderstanding here but there is only *one* instance of *newProduct1* because it is a reference type. It isn't copied. Notice that if *Product* was a value type you wouldn't even be able to mutate its properties since it is defined using `let` – Guillermo Alvarez Jul 29 '17 at 00:09
  • 1
    If you want to create a copy of `Product` then you need to make `Product` a struct. – Guillermo Alvarez Jul 29 '17 at 00:10
  • For your use case, it sounds like what you actually want however is to store a `Product` in the **VC** (local copy) and then when you are ready to save it to the store that is the point where you add it to the singleton. – Guillermo Alvarez Jul 29 '17 at 00:12
  • Yes we want a value in the VC and then another copy in something like a singleton that we can access from any VC. Essentially like a shopping cart that can be accessed from anywhere in the app. – Nevin Jethmalani Jul 29 '17 at 00:22
0

The reason why allProductsInCart is returning a different value is because of a concept known as Pass by Value versus Pass by Reference, it is nothing to do with the variable being static, or global.

Product is an object. All objects are known as Pass by Reference. This means that the value of the object points to a location in memory, and whenever that value at that location in memory is changed, then, the value is changed everywhere else pointing to that location in memory.

As allProductsInCart stores an array of Product, then it is storing an array of Objects, which means, whenever you change the value of the object, you are changing the value of wherever it is stored in memory.

Edit: If you wanted it to be pass by value, you would have to convert your array to a primitive data type. For example:

var productName = [String]() would prevent the value from being changed.

Your code would then look like:

class Cart : NSObject {
    var productName = [String]()
    static let instance = Cart()
}

Then when you call

let newProduct = Product()
newProduct.name = "Costco"
Cart.instance.productName.append(newProduct.name!)
newProduct.name = "test2"
print("Value is \(Cart.instance.productName[0])")

It will print Costco.

Look at What's the difference between passing by reference vs. passing by value? For further information about pass by value and pass by reference

Baleroc
  • 167
  • 4
  • 15
  • Perfect. Thanks! So is there a way to create a separate instance in memory that does not use pass by reference? How would I solve the issue I am having? – Nevin Jethmalani Jul 29 '17 at 05:49