Asked  1 Year ago    Answers:  5   Viewed   179 times

I want to pass my Swift Array account.chats to chatsViewController.chats by reference (so that when I add a chat to account.chats, chatsViewController.chats still points to account.chats). I.e., I don't want Swift to separate the two arrays when the length of account.chats changes.

 Answers

5

Structs in Swift are passed by value, but you can use the inout modifier to modify your array (see answers below). Classes are passed by reference. Array and Dictionary in Swift are implemented as structs.

Thursday, June 3, 2021
 
Nickool
 
3

Types of Things in Swift

The rule is:

  • Class instances are reference types (i.e. your reference to a class instance is effectively a pointer)

  • Functions are reference types

  • Everything else is a value type; "everything else" simply means instances of structs and instances of enums, because that's all there is in Swift. Arrays and strings are struct instances, for example. You can pass a reference to one of those things (as a function argument) by using inout and taking the address, as newacct has pointed out. But the type is itself a value type.

What Reference Types Mean For You

A reference type object is special in practice because:

  • Mere assignment or passing to function can yield multiple references to the same object

  • The object itself is mutable even if the reference to it is a constant (let, either explicit or implied).

  • A mutation to the object affects that object as seen by all references to it.

Those can be dangers, so keep an eye out. On the other hand, passing a reference type is clearly efficient because only a pointer is copied and passed, which is trivial.

What Value Types Mean For You

Clearly, passing a value type is "safer", and let means what it says: you can't mutate a struct instance or enum instance through a let reference. On the other hand, that safety is achieved by making a separate copy of the value, isn't it? Doesn't that make passing a value type potentially expensive?

Well, yes and no. It isn't as bad as you might think. As Nate Cook has said, passing a value type does not necessarily imply copying, because let (explicit or implied) guarantees immutability so there's no need to copy anything. And even passing into a var reference doesn't mean that things will be copied, only that they can be if necessary (because there's a mutation). The docs specifically advise you not to get your knickers in a twist.

Tuesday, June 1, 2021
 
tdous
 
5

You can still do this in Swift by making the property an NSMutableArray, just as before. Foundation types still exist if you ask for them. But this is bad design in both ObjC and Swift. It creates spooky action at a distance (when things magically change values that were not part of the call) and likely breaks MVC (if the data is persistent, it should live in the model, not in the view controllers).

There are two common patterns in Cocoa: store the data in the model, or pass it via delegation.

If this array represents some kind of persistent state (such as a list of items in the system), then that belongs in the model layer, and both view controllers should read and manipulate it there rather than by communicating with each other. (See Model-View-Controller.)

If this array is a transient piece of data (such as selections from a list), then the calling VC should set itself as the delegate to the receiving VC, and when the receiving VC finishes, it should pass the data back to its delegate. (See Delegates and Data Sources.)

Saturday, August 7, 2021
 
Null
 
2

Swift uses ARC (Automatic Reference Counting) when dealing with arrays, and it delays copying arrays until one of the copies is modified:

For example:

var a = [1, 2, 3, 4, 5]
let b = a
let c = a   // 1

a.append(6) // 2

print(a.count)
print(b.count)
print(c.count)

At step 1 above, there is only one copy of [1, 2, 3, 4, 5] in memory, and a, b, and c are references to it.

When a is modified in step 2, Swift gives a and new copy of the array and b and c continue to reference the original array. So now there are 2 copies of the array in memory.


Let's look at a much more involved example:

class Person: CustomStringConvertible {
    let name: String
    var friends: [Person] = []

    init(name: String) {
        self.name = name
    }

    var description: String { return name }
}

func createFredsFriends() -> [Person] {
    let barney = Person(name: "Barney")
    let wilma = Person(name: "Wilma")
    let betty = Person(name: "Betty")
    let friends = [barney, wilma, betty]  // 1

    return friends
}

func createFred() -> Person {
    let fred = Person(name: "Fred")

    let friends = createFredsFriends() // 2
    fred.friends = friends             // 3

    return fred
}

let fred = createFred()  // 4

print(fred.friends)  // [Barney, Wilma, Betty]
  • At step 1, the array of friends is created. It is referenced by the local variable friends.

  • This reference goes away when createFredsFriends() returns, and the only reference to the array is held then by the local variable friends at step 2. Ownership of the array has been passed.

  • At step 3, a second reference to the array has been assigned to the friends property of fred.

  • At step 4, createFred() has returned, so the local variable friends is gone and no longer references the array. The only reference is in the property of the fred object which is held by the variable fred.

So the array was created once, several references to it were created, and in the end there is a single reference to the array and all of this was done without a single copy operation.


Since Swift arrays are value types and copied when changed, you can't pass an array and then expect the original to be updated when the copy is. If you need that level of functionality, you can create a class wrapper for the array and then always access that array through the instance of that class.

Here I've modified the previous example to show how that would work:

// Class to wrap array so that everyone references the same copy
class FriendsWrapper {
    var friends: [Person]

    init(friends: [Person]) {
        self.friends = friends
    }
}

class Person: CustomStringConvertible {
    let name: String
    var friendsWrapper: FriendsWrapper?

    init(name: String) {
        self.name = name
    }

    func addFriend(friend: Person) {
        if let wrapper = friendsWrapper {
            wrapper.friends.append(friend)
        } else {
            friendsWrapper = FriendsWrapper(friends: [friend])
        }
    }

    var description: String { return name }
}

func createFredsFriends() -> [Person] {
    let barney = Person(name: "Barney")
    let wilma = Person(name: "Wilma")
    let betty = Person(name: "Betty")
    let friends = [barney, wilma, betty]

    return friends
}

func createFred() -> Person {
    let fred = Person(name: "Fred")

    let friendsWrapper = FriendsWrapper(friends: createFredsFriends())
    fred.friendsWrapper = friendsWrapper

    // Add friend to Fred object
    fred.addFriend(Person(name: "Bam Bam"))

    // Copy of array in local friendsWrapper is updated
    print(friendsWrapper.friends)  // [Barney, Wilma, Betty, Bam Bam]

    return fred
}

let fred = createFred()
Saturday, November 20, 2021
 
Optimus
 
2

Arrays are reference types, so the iArr variable holds a reference to an array.

In other words, when you call

Arrays.sort(iArr);

you're passing a reference (by value) to the sort method, which sorts the array that iArr refers to.


From comments:

What does passing a reference (by value) actually mean?

What pass by reference means is that you're basically passing the variable itself to the method. I.e., what ever the method does with the variable affects the variable on the outside. This is never the case in Java. (Try implementing a swap method and you'll see what I mean.) Passing by value means that you pass the value that's stored in the variable. In this case the value is a reference, so it's passing a reference by value.


Re. second update:

Judging from your image, I think you've understood the situation very well, and I think it boils down to terminology.

If we forget about C++ for a while, it's really simple. All you need to keep in mind is that (A) when you invoke method(var) the argument is a copy of whatever var contains, and (B) the content of a non-primitive variable is a reference (a "pointer" if you so like).

Note that in your question you have

int iArr[] = {2, 1, 9, 6, 4};

which is equivalent to

int[] iArr = new int[] { 2, 1, 9, 6, 4 };

so it all checks out: iArr holds a reference and new returns a reference.

When you invoke Arrays.sort(iArr) the content of iArr is passed (i.e. the reference to the array). This is still not pass-by-reference because the value is passed, not the variable itself. If you reassign the formal parameter inside the method to point to some other array, iArr will still point to the original array when the method returns.

If we do think in terms of C++ things tend to be a bit more complicated; C++ notion of reference is slightly different. With a C++ reference you can in fact implement a real swap:

void swap(int &x, int &y)
{
   int temp = x;
   x = y;
   y = temp;
}

I.e. you can pass in "a variable" (as opposed to just the content of a variable). I like to think of this as you're sharing the scope of the variable with the method you're calling. This can't be done in Java.

So with that in mind, I'd say Java reference are much more like C++ pointers, except that they are limited in the sense that you can't dereference using * operator as you can in C++ (you can't do *person in Java, even though person stores what corresponds to a pointer to a person) and you can't get the address of an object using & operator. Also you can't do any pointer arithmetic. You can't for instance do iArr + 3 to get to the fourth element of your array.

Thursday, December 23, 2021
 
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :