Thread Security of Swift Array copy
NSArray inherits from NSObject, belongs to object, and has copy method. Swift's Array is struct, and there is no copy method. Assigning an Array variable to another variable has the same memory address? Relevant multithreading security issues. This paper explores these two issues.
Memory address
Define test class es and struct s
class MyClass { var intArr = [Int]() var structArr = [MyStructElement]() var objectArr = [MyClassElement]() } struct MyStructElement {} class MyClassElement {}
closure defining the output memory address
let memoryAddress: (Any) -> String = { guard let cVarArg = $0 as? CVarArg else { return "Can not find memory address" } return String(format: "%p", cVarArg) }
Test Int array
private func testIntArr() { print(#function) let my = MyClass() for i in 0...10000 { my.intArr.append(i) } print("Before arr address:", memoryAddress(my.intArr)) // Copy Array is NOT thread safe let arr = my.intArr // If move this into async closure, crash print("Temp arr address:", memoryAddress(arr)) // Copy. Address different from my.intArr DispatchQueue.global().async { var sum = 0 for i in arr { sum += i } print("Sum:", sum) // 0 + 1 + ... + 10000 = 50005000 } my.intArr.removeAll() for _ in 0...10000 { my.intArr.append(0) } print("After arr address:", memoryAddress(my.intArr)) // New address }
Testing in view controller
override func viewDidLoad() { super.viewDidLoad() for _ in 0...1000 { testIntArr() } }
Result
Int array has different memory addresses, and the assignment process produces a copy.
Testing struct array
private func testStructArr() { print(#function) let my = MyClass() for _ in 0...10000 { my.structArr.append(MyStructElement()) } print("Before arr address:", memoryAddress(my.structArr)) // Copy Array is NOT thread safe let arr = my.structArr // If move this into async closure, crash print("Temp arr address:", memoryAddress(arr)) // Copy. Address different from my.structArr DispatchQueue.global().async { var sum = 0 for _ in arr { sum += 1 } print("Sum:", sum) // 10001 } my.structArr.removeAll() for _ in 0...10000 { my.structArr.append(MyStructElement()) } print("After arr address:", memoryAddress(my.structArr)) // New address }
Testing in view controller
override func viewDidLoad() { super.viewDidLoad() for _ in 0...1000 { testStructArr() } }
Result
Struct array has different memory addresses, and the assignment process is copied.
Test Object array
private func testObjectArr() { print(#function) let my = MyClass() for _ in 0...10000 { my.objectArr.append(MyClassElement()) } print("Before arr address:", memoryAddress(my.objectArr)) // Copy Array is NOT thread safe let arr = my.objectArr // If move this into async closure, crash print("Temp arr address:", memoryAddress(arr)) // Not copy. Same as my.objectArr DispatchQueue.global().async { var sum = 0 for _ in arr { sum += 1 } print("Sum:", sum) // 10001 } my.objectArr.removeAll() for _ in 0...10000 { my.objectArr.append(MyClassElement()) } print("After arr address:", memoryAddress(my.objectArr)) // New address }
Testing in view controller
override func viewDidLoad() { super.viewDidLoad() for _ in 0...1000 { testObjectArr() } }
Result
An object array variable is assigned to another variable, and the memory addresses of the two variables are the same, that is, there is no copy. After the original array changes, the memory address changes, but does not affect the assigned variables.
Thread security issues
The above writing will not be wrong. If the assignment of array is written to async closure, an error will be reported. If you try several times, you will make different mistakes.
Int array error
DispatchQueue.global().async { let arr = my.intArr // There will be an error in assignment. var sum = 0 for i in arr { sum += i } print("Sum:", sum) }
Struct array error
DispatchQueue.global().async { let arr = my.structArr // There will be an error in assignment. var sum = 0 for _ in arr { sum += 1 } print("Sum:", sum) }
Object array error
DispatchQueue.global().async { let arr = my.objectArr // There will be an error in assignment. var sum = 0 for _ in arr { sum += 1 } print("Sum:", sum) }
For Int array and struct array, the assignment is copied, but this step should not be an atomic operation, so putting async closure will make a mistake. For object array, although the assignment process does not copy, to change the original array and keep the assigned object unchanged, it should also copy; that is, copy only when the array is updated. It is assumed that copying is not an atomic operation at this time, so putting async closure will make a mistake.
Whether Array's assignment process copies or not depends on the type of element in it. If the elements of array are Int, struct, etc., copy when assigned. If an array element is an object, it does not copy when it is assigned, and only copies when one of the array variables is updated after the assignment. Array's copy is thread insecure.