As you know, Swift is a type-safe language that prevents unsafe behavior in your code through compiler errors. For example, variables must be declared before use, memory cannot be accessed after a variable is destroyed, and arrays are out of bounds.

Swift ensures that your code does not encounter memory access conflicts by having mutually exclusive access to the same block of memory at a time (only one write permission can be allowed at a time). Although Swift does manage memory automatically, in most cases you don’t need to care about this. But it is also necessary to understand when memory access conflicts occur.

First, let’s look at what memory access conflicts are.

Memory access conflict

When you set a value or read the value of a variable, you access memory.

Var age = 10 // Write permission print(age) // Read permissionCopy the code

When we read and write to the same block of memory at the same time, unpredictable errors will occur. For example, the age above, if some other code sets it to 20 while you’re reading it, you could read 10 or 20. This creates problems.

Memory access conflict: A memory access conflict occurs when read/write operations or multiple write operations are performed on the same memory block.

Now that you know what a memory access conflict is, let’s take a look at what can cause a memory access conflict.

In-out parameters

When the in-out parameter is a global variable and the variable is modified inside the function, memory access conflicts occur. For example:

var age = 10

func increment(_ num: inout Int) { // step1
    num += age // step2
}
increment(&age)
Copy the code

Increment (:) add write permission to all in-out parameters In the function. In the above code, step1 has obtained the write permission of age, while step2 has obtained the read permission of age, thus causing the same block of memory to be read and written at the same time. This creates memory access conflicts.

The above problem can be solved by making a copy of age:

// step1
var copyOfAge = age
increment(&copyOfAge)
age = copyOfAge
Copy the code

Step1 copy the value of age to another block of memory, so that there is read permission to age and write permission to copyOfAge in the function body, because age and copyOfAge are two pieces of memory, so there is no memory access conflict.

The mutating function of a structure

For a struct’s mutating function, the entire function body has write permission to self.

struct Person {
    var age: Int
    mutating func increment(_ num: inout Int) { 
        age += num 
    }
}

var p1 = Person(age: 10)
p1.increment(&p1.age)
Copy the code

The code compiler reports no conflicting data to ‘P1 ‘, but modification requires exclusive access; Think of copying to a local variable. This is clearly a memory access conflict.

The in-out parameter obtains write permission for P1. The mutating function also has write permission for P1. There are two write operations on the same block of memory. Memory access conflicts occur. This can be solved by copying operations as above.

Property of a value type

For structs, enumerations, and primitive equivalent types, modifying their attributes is equivalent to modifying their entire values. For example:

func increment(_ num1: inout Int, _ num2: inout Int) {
    print(num1 + num2)
}

var tuple = (age: 10, height: 20)
increment(&tuple.age, &tuple.height)
Copy the code

&tuple. Age gets write permission for tuple. &tuple. Height gets write permission for tuple. There are two write operations on the same block of memory. Memory access conflicts occur.

This problem can be solved with local variables:

func someFunction() {
    var tuple = (age: 10, height: 20)
    increment(&tuple.age, &tuple.height)
}
Copy the code

Because age and height do not interact with each other in the someFunction() function, the compiler keeps memory safe.

PS: Regarding the comment section question, what does it mean that there is no interaction in the someFunction() function?

A: In someFunction(), the compiler can make sure that no other thread reads or modifiers the tuple. Therefore, memory security can be guaranteed. For global variables, the compiler has no guarantee that another thread is reading or modifying them.

The following code is code that interacts with a tuple inside a function. Although it is a local variable, it involves multiple threads modifying the value of the tuple, thus causing a memory access conflict:

func someFunction() {
    var tuple = (age: 10, height: 20)
    
    DispatchQueue.main.async {
        tuple.age += 10
    }
    
    DispatchQueue.main.async {
        increment(&tuple.age, &tuple.height)
    }
}
Copy the code

conclusion

  • Memory access conflicts occur when simultaneous read/write operations or multiple write operations are performed on the same memory block.
  • Conditions that cause memory access conflicts:
    • In-out is a global parameter and modifies it In the function body.
    • Modifies the value of a structure within the structure’s mutating function.
    • Multiple attributes of the same value type are used as in-out arguments to the function.