Value & Reference Types
Types in Swift fall into one of two categories: first, "value types", where each instance keeps a unique copy of its data. The second, "reference types", where instances share a single copy of the data.
Reference Types
Reference types consist of shared instances that can be passed around and referenced by multiple variables.
Value Types
Value types are referenced completely differently than reference types. A value type instance is an independent instance and holds its data in its own memory allocation.
Pointers
With reference types, this is not so important, since they are already passed by reference. But if we are talking about value types this can be useful.
Reference Types
Reference types consist of shared instances that can be passed around and referenced by multiple variables. Each instance points to the same object in memory, that is why this type has such a name. If we create a new instance that points to the existing one, then when changing any of them, the values will change in both.
class Owner {

    var name: String

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

let sergey = Owner(name: "Sergey")
let richard = sergey

richard.name = "Richard"

print(sergey.name) // Richard
print(richard.name) // Richard
print(sergey.description) // <Owner: 0x60000221a960>
print(richard.description) // <Owner: 0x60000221a960>

The same pointer for all instances
Under certain conditions, this behavior may be unacceptable. Especially if we are talking about multithreading. Apple recommends using value types.

Swift 3.0:

  • struct: 124
  • enum: 19
  • class: 3
isKnownUniquelyReferenced
Returns a Boolean value indicating whether the given object is known to have a single strong reference.
The isKnownUniquelyReferenced(_:) function is useful for implementing the copy-on-write optimization for the deep storage of value types. isKnownUniquelyReferenced(_:) checks only for strong references to the given object---if `object` has additional weak or unowned references, the result may still be `true`. Because weak and unowned references cannot be the only reference to an object, passing a weak or unowned reference as `object` always results in `false`.

If the instance passed as `object` is being accessed by multiple threads simultaneously, this function may still return `true`. Therefore, you must only call this function from mutating methods with appropriate thread synchronization. That will ensure that isKnownUniquelyReferenced(_:) only returns `true` when there is really one accessor, or when there is a race condition, which is already undefined behavior.
var owner = Owner(name: "Thanos") 
print(isKnownUniquelyReferenced(&owner)) // True. There is only one link on the instance 

var newOwner = owner 
print(isKnownUniquelyReferenced(&owner)) // False. Multiple links on the instance
Identity Operators
Because classes are reference types, it's possible for multiple constants and variables to refer to the same single instance of a class behind the scenes. (The same isn't true for structures and enumerations, because they're always copied when they're assigned to a constant or variable, or passed to a function.)

It can sometimes be useful to find out whether two constants or variables refer to exactly the same instance of a class. To enable this, Swift provides two identity operators:

  • Identical to ( ===)
  • Not identical to ( !==)

Note that identical to (represented by three equals signs, or ===) doesn't mean the same thing as equal to (represented by two equals signs, or ==). Identical to means that two constants or variables of class type refer to exactly the same class instance. Equal to means that two instances are considered equal or equivalent in value, for some appropriate meaning of equal, as defined by the type's designer.

Use these operators to check whether two constants or variables refer to the same single instance:
let playerOne = PlayerClass(name: "One", health: 100, energy: 10)
let playerTwo = PlayerClass(name: "One", health: 100, energy: 10)

if (playerOne == playerTwo) {
    print("Equal")
}
if (playerOne === playerTwo) {
    print("Identical")
}
Value Types
Value types are referenced completely differently than reference types. A value type instance is an independent instance and holds its data in its own memory allocation.
struct Stone {

    enum `Type` {
        case space
        case mind
        case reality
        case power
        case time
        case soul
    }

    var type: Type // Enum - value type
    let owner: Owner // Class - reference type
}

let spaceStone = Stone(type: .space, owner: Owner(name: "Elon"))
var spaceStone2 = spaceStone // Creating a copy

// Changing the values
spaceStone2.type = .mind
spaceStone2.owner.name = "Loki"

/**
    # The value has NOT changed
*/
print(spaceStone.type) // Space
print(spaceStone2.type) // Mind

/**
    # The value has changed.
*/
print(spaceStone.owner.name) // Loki
print(spaceStone2.owner.name) // Loki
So, what's the core difference between these two types? The quick and dirty explanation is that reference types share a single copy of their data while value types keep a unique copy of their data.

<Stone: 0x60000221a156>
<Stone: 0x60000221a186>

Different pointers for all instances
Value types
  • Struct
  • Enum
  • Tuple
  • Array
  • Dictionary
  • String
Reference type
  • Class
  • Function
  • Closure
    Pointers
    In order to pass to the function not the object itself but a pointer to it you need a keyword inout. With reference types, this is not so important, since they are already passed by reference. But if we are talking about value types this can be useful.
    var zeus = Owner(name: "Zeus")
    var stone = Stone(type: .reality, owner: zeus)
    
    func update(stone: inout Stone, owner: Owner) {
        owner.name = "Vision"
        stone.type = .time // Without `inout` we can't change it
    }
    
    update(stone: &stone, owner: zeus)
    Characteristics of Memory Access
    There are three characteristics of memory access to consider in the context of conflicting access: whether the access is a read or a write, the duration of the access, and the location in memory being accessed. Specifically, a conflict occurs if you have two accesses that meet all of the following conditions:

    • At least one is a write access or a nonatomic access.
    • They access the same location in memory.
    • Their durations overlap.
    The difference between a read and write access is usually obvious: a write access changes the location in memory, but a read access doesn't. The location in memory refers to what is being accessed—for example, a variable, constant, or property. The duration of a memory access is either instantaneous or long-term.

    An access is instantaneous if it's not possible for other code to run after that access starts but before it ends. By their nature, two instantaneous accesses can't happen at the same time. Most memory access is instantaneous. For example, all the read and write accesses in the code listing below are instantaneous:
    func oneMore(than number: Int) -> Int {
        return number + 1
    }
    
    var myNumber = 1
    myNumber = oneMore(than: myNumber)
    print(myNumber)
    // Prints "2"
    However, there are several ways to access memory, called long-term accesses, that span the execution of other code. The difference between instantaneous access and long-term access is that it's possible for other code to run after a long-term access starts but before it ends, which is called overlap. A long-term access can overlap with other long-term accesses and instantaneous accesses.

    Overlapping accesses appear primarily in code that uses in-out parameters in functions and methods or mutating methods of a structure.
    Conflicting Access to In-Out Parameters
    A function has long-term write access to all of its in-out parameters. The write access for an in-out parameter starts after all of the non-in-out parameters have been evaluated and lasts for the entire duration of that function call. If there are multiple in-out parameters, the write accesses start in the same order as the parameters appear.

    One consequence of this long-term write access is that you can't access the original variable that was passed as in-out, even if scoping rules and access control would otherwise permit it—any access to the original creates a conflict. For example:

    var stepSize = 1
    
    func increment(_ number: inout Int) {
        number += stepSize
    }
    
    increment(&stepSize)
    // Error: conflicting accesses to stepSize
    
    
    One way to solve this conflict is to make an explicit copy of stepSize:
    
    // Make an explicit copy.
    var copyOfStepSize = stepSize
    increment(&copyOfStepSize)
    
    // Update the original.
    stepSize = copyOfStepSize
    // stepSize is now 2
    In the code above, stepSize is a global variable, and it's normally accessible from within increment(_:). However, the read access to stepSize overlaps with the write access to number. As shown in the figure below, both number and stepSize refer to the same location in memory. The read and write accesses refer to the same memory and they overlap, producing a conflict.
    Conclusion:

    When it comes to global variables or structures that are passed as an in-out parameter. It is necessary to ensure that there is no access conflict during the read/write operation. Usually, the compiler itself will warn about this.
    How to Choose?

    Use a value type when:
    • Comparing instance data with == makes sense
    • You want copies to have independent state
    • The data will be used in code across multiple threads

    Use a reference type (e.g. use a class) when:
    • Comparing instance identity with === makes sense
    • You want to create shared, mutable state
    Original source:
    docs.swift.org
    Mutability

    var and let function differently for reference types and value types. Notice that you defined Owner as constants with let, yet you were able to change the name property. How's that possible?

    For reference types, let means the reference must remain constant. In other words, you can't change the instance the constant references, but you can mutate the instance itself.

    For value types, let means the instance must remain constant. No properties of the instance will ever change, regardless of whether the property is declared with let or var. But you can change the property if you add the keyword nonmutating
    The Role of Mutation in Safety

    One of the primary reasons to choose value types over reference types is the ability to more easily reason about your code. If you always get a unique, copied instance, you can trust that no other part of your app is changing the data under the covers. This is especially helpful in multi-threaded environments where a different thread could alter your data out from under you. This can create nasty bugs that are extremely hard to debug.

    Because the difference is defined in terms of what happens when you change data, there's one case where value and reference types overlap: when instances have no writable data. In the absence of mutation, values and references act exactly the same way.

    What's the difference between == and ===?
    Swift gives us two equality operators, == and ===, that do slightly different things. You will almost certainly need to use both of them so it's worth taking the time to learn them.

    First, == is the equality operator, which tests that two things are equal for whatever definition of "equal" those things use. For example, 5 == 5 is true because there == means an integer comparison, and the same is true for other built-in value types such as strings, booleans, and doubles.

    Things get more complicated when == is used with a struct you built, because by default they cannot be compared – you need to make them conform to the Equatable protocol.

    In comparison, === is the identity operator, which checks whether two instances of a class point to the same memory. This is different from equality, because two objects that were created independently using the same values will be considered equal using == but not === because they are different objects.

    The === operator is available only when using classes because structs are designed so they are always uniquely referenced.