Are swift classes inside struct passed by copy during assignment?
Asked Answered
S

4

20

If I have a struct in swift with inside a class attribute and I copy the struct object, is the class attribute copied or passed by reference?

Separatist answered 8/12, 2016 at 17:39 Comment(0)
S
39

Passed by reference. You can test it. Declare:

class A{}
struct B { let a = A()}

then:

let b = B()
print("A = \(unsafeAddressOf(b.a))")//0x0000600000019450
let b_copy = b
print("A = \(unsafeAddressOf(b_copy.a))")//0x0000600000019450
Sussex answered 8/12, 2016 at 17:46 Comment(4)
To make it work with swift 5 use: print(Unmanaged.passUnretained(b.a).toOpaque())Frye
Why isn't init called again?Irrigation
Will it increment the reference count also?Skittle
of course it will increment the reference count of a by oneSussex
G
10

All properties of a struct are copied (as if you assigned (=) each property of the old struct to the corresponding property of the new struct) when the struct is copied, regardless of type.

When you say "class attribute", I am assuming you mean a variable of reference type. (The type with the same name as a class denotes a reference type for references that point to objects of that class.) Copying a value of reference type (a reference) produces another reference that points to the same object. Note that "objects" are not values in Swift -- there are no "object types" -- rather, objects are always manipulated through references that point to them.

Glove answered 9/12, 2016 at 19:19 Comment(1)
Objects can be represented without reference that points to them if it is a value type object (struct) and it has less then 3 words (ivars) in it's contents. Type of those ivars must allow them to be be allocated on stack. In other case object will be allocated on the heap with corresponding pointer. Source developer.apple.com/videos/play/wwdc2016/416Divergent
K
5

I tested the above experiment in swift 5: Let's see the result:

class A {
    var id: Int
    init(id: Int) {
        self.id = id
    }
}

struct B {
    var grade: Int
    var a: A
}

Experiment based on the result:

var a = A(id: 1)
var a_copy = a

var b1 = B(grade: 2, a: a)
var copy_b1 = b1

print(b1.a.id)
b1.a.id = 5
print(copy_b1.a.id)

print(b1.grade)
b1.grade = 3
print(copy_b1.grade)

Output:

1
5 // call by reference, same result
2
2 // call by value, no change in result

Conclusion:

struct does copy when we create another object of it. It copies its struct property (call by value) but refer the same instance of class property (call by reference)

Do experiment via address:

Do an Experiment on Class:

var a = A(id: 1)
var a_copy = a

withUnsafePointer(to: &a) { (address) in
    print("address of a (class) = \(address)")
}
withUnsafePointer(to: &a_copy) { (address) in
    print("address of a_copy (class) = \(address)")
}
withUnsafePointer(to: &a.id) { (address) in
    print("address of a.id (struct) = \(address)")
}
withUnsafePointer(to: &a_copy.id) { (address) in
    print("address of a_copy.id (struct) = \(address)")
}

Output

address of a (class) = 0x0000000114747f80
address of a_copy (class) = 0x0000000114747f88
address of a.id (struct) = 0x000060000285a390
address of a_copy.id (struct) = 0x000060000285a390

Observation 1:

Both instances of the class are referring to same location of its property.

Let's do the Experiment on struct:

print("\n\n\n")
withUnsafePointer(to: &b1) { (address) in
    print("address of b1 (struct) = \(address)")
}
withUnsafePointer(to: &b1.grade) { (address) in
    print("address of b1.grade (struct) = \(address)")
}
withUnsafePointer(to: &b1.a) { (address) in
    print("address of b1.a (class) = \(address)")
}
withUnsafePointer(to: &b1.a.id) { (address) in
    print("address of b1.a.id (class) = \(address)")
}

Output:

address of b1 (struct) = 0x0000000109382770
address of b1.grade (struct) = 0x0000000109382770
address of b1.a (class) = 0x0000000109382778
address of b1.a.id (class) = 0x0000600001e5cfd0
print("\n\n\n")
withUnsafePointer(to: &copy_b1) { (address) in
    print("address of copy_b1 (struct) = \(address)")
}
withUnsafePointer(to: &copy_b1.grade) { (address) in
    print("address of copy_b1.grade (struct) = \(address)")
}
withUnsafePointer(to: &copy_b1.a) { (address) in
    print("address of copy_b1.a (class) = \(address)")
}
withUnsafePointer(to: &copy_b1.a.id) { (address) in
    print("address of copy_b1.a.id (class) = \(address)")
}

Output:

address of copy_b1 (struct) = 0x0000000109382780
address of copy_b1.grade (struct) = 0x0000000109382780
address of copy_b1.a (class) = 0x0000000109382788
address of copy_b1.a.id (class) = 0x0000600001e5cfd0

conclusion: both &b1.a.id and &copy_b1.a.id are referring the same address.

Kearns answered 30/5, 2020 at 17:12 Comment(0)
G
4

Seems, we have to consider modifying the object (as the optimizer will use the copy-on-write technique)

Value Type (User) contains Reference Type (Address) Class inside a Struct

observation:

  • On assignment of value type, any value type (ex: User) that contains a reference type (ex: Address), that reference type (ex: Address) is always passed as a reference.

Reference Type (User) contains Value Types (Address) enter image description here

observation:

  • On assignment of reference type, any reference type (ex: User) contains value types (ex: Address), that value types (ex: Address) point to the same parent reference object (ex: User) (parent may contain many references - ex: both u1, u2 refers to same memory address).
Genaro answered 1/6, 2021 at 8:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.