3

Given an Array with struct

    import Foundation
    
    struct Card {
        var flag: String = ""
    }
    
    var cards = Array<Card>()
    cards.append(Card())

The following operation will NOT modify original array element

    // A copy is created.
    var cardCopy = cards[0]
    
    // Will NOT modify cards[0] 
    cardCopy.flag = "modify0"
    
    print(cards[0].flag)

The following operation will modify original array element

    // We can modify cards[0] by
    cards[0].flag = "modify"

    print(cards[0].flag)

However, it isn't efficient in the sense, we need to perform indexing access each time. Imagine

    cards[0].flag0 = "modify"
    cards[0].flag1 = "modify"
    cards[0].flag2 = "modify"
    cards[0].flag3 = "modify"
    ...

Is there a way, we can create reference to element of array of struct? So that we can write

// How to create a reference to cards[0]?
var cardReference = ...
    cardReference.flag0 = "modify"
    cardReference.flag1 = "modify"
    cardReference.flag2 = "modify"
    cardReference.flag3 = "modify"
    ...

One of the possibilities is to replace struct with class. But, I would like to explore other alternative, before doing so.

3
  • If you need to modify your Card instance a lot maybe you should make it a class instead or create a new instance with new values and replace the whole object in the array. Commented Aug 19, 2020 at 11:13
  • @JoakimDanielson Thank you. I would like to explore other possible alternatives (possible to create a reference?), before converting it from struct to class. Commented Aug 19, 2020 at 11:17
  • 1
    You cannot turn a value type into a reference type. As others have pointed out, inout is as close as you're going to get. Commented Aug 19, 2020 at 13:57

2 Answers 2

7

You can achieve that using a function to make your changes and pass the Card struct by reference like this:

func update(card: inout Card) {
    card.flag0 = "modify"
    card.flag1 = "modify"
    card.flag2 = "modify"
    card.flag3 = "modify"
}

var cards = Array<Card>()
cards.append(Card())

update(card: &cards[0])

or even better by using a mutating function in Card type and pass as a closure your changes like that:

struct Card {
    var flag0: String = ""
    var flag1: String = ""
    var flag2: String = ""
    var flag3: String = ""
    
    mutating func update(block: (inout Card) -> Void) {
        block(&self)
    }
}
    
var cards = Array<Card>()
cards.append(Card())

cards[0].update {
    $0.flag0 = "modify"
    $0.flag1 = "modify"
    $0.flag2 = "modify"
    $0.flag3 = "modify"
}

Update: To make the second approach even more reusable, you can define a protocol like this:

protocol Updatable {
    mutating func update(block: (inout Self) -> Void)
}

extension Updatable {
    mutating func update(block: (inout Self) -> Void) {
        block(&self)
    }
}

and make Card struct conform to it:

struct Card: Updatable {
    var flag0: String = ""
    var flag1: String = ""
    var flag2: String = ""
    var flag3: String = ""
}

Then you can use it just like above.

Sign up to request clarification or add additional context in comments.

5 Comments

I really like this trick of making the struct generally self-updatable; I can't believe I've never thought of that. :)
I'd like to include this idea in my book (attributed to you of course); please let me know if that's okay.
@matt I did some research about when I first saw something like this and found this gist.github.com/nicklockwood/9b4aac87e7f88c80e932ba3c843252df. So, for me it's OK of course, but to be fair this wasn't entirely my idea.
Well, your idea here is not really taken from that gist, so I still think you get the credit.
@matt well, yes it's like another usage of the same technique.
0

This is the expected behaviour, since Array is a struct and structs are value types.

What you need is reference type behaviour, so you should convert your struct to a class.

Another solution for modifying several properties of a value type in one go is to create a mutating function that does that and calling that on the array element.

struct Card {
    var flag: String = ""
    var flag2 = ""

    mutating func update(flag: String, flag2: String) {
        self.flag = flag
        self.flag2 = flag2
    }
}

var cards = [Card(flag: "a", flag2: "b")]
cards[0].update(flag: "b", flag2: "c")

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.