avatarAvijit Nagare

Summary

The provided text discusses memory safety in Swift, emphasizing the importance of managing access to memory to prevent conflicts and ensure predictable behavior in applications.

Abstract

The article "How Memory Safety works in Swift" explains that memory safety is crucial for applications to function correctly, as concurrent access to memory can lead to unpredictable results. Swift enforces memory safety by ensuring variables are initialized before use, preventing access to deallocated memory, checking array indices for out-of-bounds errors, and guaranteeing exclusive access to shared memory. The text outlines different types of memory access, including read or write access, the duration of access, and the location in memory, and stresses that overlapping accesses, particularly with in-out parameters and mutating methods in structures, can cause conflicts. The article provides code examples to illustrate common scenarios that lead to access conflicts and offers solutions to resolve them, such as creating explicit copies of in-out parameters to avoid simultaneous writes to the same memory location. It also discusses the specifics of memory access in the context of Swift's value semantics for structures and enumerations, highlighting the need for careful management when dealing with properties of these types. The author concludes by encouraging readers to apply these concepts to write conflict-free code and contribute to the robustness of their Swift applications.

Opinions

  • The author believes that understanding memory safety is essential for writing reliable Swift code.
  • Overlapping accesses, especially in the context of in-out parameters and mutating methods, are seen as common sources of memory access conflicts.
  • The author suggests that making explicit copies of variables can effectively prevent conflicts when dealing with in-out parameters.
  • Swift's value semantics for structures and enumerations are considered beneficial for memory safety but require developers to be mindful of how they access properties to avoid conflicts.
  • The author values the practice of initializing variables before use, checking array bounds, and ensuring no access to deallocated memory as key principles for maintaining memory safety.
  • The article implies that developers should be cautious when capturing structures in closures, particularly regarding escaping versus nonescaping closures, to maintain memory safety.
  • The author encourages the audience to engage with the content by clapping if they find the information useful, indicating a desire for community feedback and appreciation for the dissemination of knowledge.

How Memory Safety works in Swift

Single or multiple access to memory (Diagram by author).

Every content/text you see on screen is already saved in memory and being used whenever user interact with application. If you declared a variable say var number = 1, here value ‘1’ is being saved in some memory location and that location address is been saved in number variable.

This variable is read by multiple functions at same time is fine but what if multiple functions try to modify at the same time, there will be problem as you see unpredictable values in different functions. This can happen on a single thread and you will get compile/run time errors.

Memory safety guide comes to picture to avoid conflicts when accessing memory.

Ex. Swift ensure:

1. Variable initialization before use.

2. Memory isn’t accessed after it’s been deallocated.

3. Array indices are checked for out-of-bounds errors.

4. Ensure exclusive access to shared memory to avoid conflicts.

Memory is being access in following way:

  • Read or write access.
  • Duration of access (Time taken to either read or write).
  • Location in memory being accessed.

These above accesses are one at a time. i.e. resource being read/write by single thread at a time. There would is conflict problem is if

  1. At least one thread is a writing a memory or nonatomic(multiple) access at same location in memory.

2. Their duration of access is overlap (instant or long term).

func add(number: Int) -> Int {
 return number + 1
}
var result = 1
result = add(result)
print(result) 
//result = 2

Two instant accesses can’t happen at the same time. An access is instantaneous if it’s not possible for other code to run after that access starts but before it ends.

Long-term access: 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. Also, same possible in mutating methods of a structure.

Conflicting access to in-out parameter: A function has long-term write access to all of its in-out parameters until functions scope end.

var inOutParam = 1
func add(_ number: inout Int) { // Note: inout declaration
  number += inOutParam
} 
add(&inOutParam)
// Error: conflicting accesses to inOutParam
// Both number and inOutParam refer to the same location in memory.

To solve this conflict, make a new copy of inOutParam into some other variable say “copyOfInOutParam” so this new variable will get new location in memory.

// Make an explicit copy.
var copyOfInOutParam = inOutParam
add(&copyOfInOutParam)
// set updated value to original variable.
inOutParam = copyOfInOutParam
//The "number" and "inOutParam" variables are different locations in memory.

Single variable conflicts:

When a single variable as the argument for multiple in-out parameters of the same function produces a conflict. Ex.

func balance(_ x: inout Int, _ y: inout Int) {
  let sum = x + y
  x = sum / 2
  y = sum - x
}
var first = 42
var second = 30
balance(&first, &second) // OK
balance(&first, &first) // Same variable passed to both argument. i.e. Access conflicts.

Conflicting access to self in method.

A mutating method on a structure has write access to self for the duration of the method call. Ex.

Ex. struct Player {
  var health: Int
  var energy: Int
  static let maxHealth = 10
  mutating func restoreHealth() {
    health = Player.maxHealth
  }
} // Write access start in restoreHealth() function.

//Create overlapping access now with in-out parameter.
extension Player {
  mutating func shareHealth(with teammate: inout Player) {
    balance(&teammate.health, &health)
  }
} 

var p1= Player(name: "P1", health: 10, energy: 10)
var p2= Player(name: "P2", health: 5, energy: 10)
p1.shareHealth(with: &p2) // OK
//As p1 and p2 are structure both are value type both different locations in memory so no conflicts. 
p1.shareHealth(with: &p1) // Error: conflicting accesses to P1
//Same instance as passed as inOut parameter so this will cause conflicts.

Conflicting access to properties:

If Structure or Enum mutating any piece i.e. any single property it mutates the whole Enum/Structure value, meaning read or write access to one of the properties requires read or write access to the whole Enum/Structure value. As those are value types.

var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation
// Both tuple values passed as inOut to balance function so the entire “playerInformation” needs 2 write access so causes conflicts.
//Same error in case of Structure:
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // Error
//Note: Structure instance are global instance so causing conflicts.
//Most access to the properties of a structure can overlap safely.
//In case of local variable of Structure will not cause conflicts because two store propertiesof Structure does not interact in any way.
func someFunction() {
  var oscar = Player(name: "Oscar", health: 10, energy: 10)
  balance(&oscar.health, &oscar.energy) // OK
}

Structure is safe if you are accessing only stored properties of an instance, not computed properties or class properties. The structure is either not captured by any closures, or it’s captured only by nonescaping closures.

Hope this gave you overall idea to write a conflict free code when you work with in-out parameter and structures.

Please do clap and happy coding!!

iOS
Swift Programming
Memory Management
Recommended from ReadMedium