visit
Pointers to a piece of memory without an explicit type. Returns the number of bytes. Usually contains Raw
in the name;
Pointers with an explicit type are specified during initialization as a generic parameter. Does not contain Raw
in the name;
Pointers to a memory area with the possibility of changing it. The name contains Mutable
;
Pointers to a piece of memory without the possibility of changing it. The name does not contain Mutable
;
Buffer
;Buffer
;
UnsafePointer<T>
;UnsafeMutablePointer<T>
;UnsafeBufferPointer<T>
;UnsafeMutableBufferPointer<T>
;UnsafeRawPointer
;UnsafeMutableRawPointer
;UnsafeRawBufferPointer
;UnsafeMutableRawBufferPointer
;
var x: Int = 10
let unsafePointer = UnsafePointer<Int>(&x)
Because UnsafePointer
is an immutable pointer, it can only be initialized by passing it an already initialized object directly.
unsafePointer.pointee // printed 10
Consider the creation of objects in the example of UnsafeMutablePointer
. Unlike UnsafePointer
, this pointer can be initialized before information is written to the memory area.
let size = MemoryLayout<Int>.size
let unsafeMutablePointer = UnsafeMutablePointer<Int>.allocate(capacity: size)
unsafeMutablePointer.pointee = 5
Now, through the pointee property, you can read and write the allocated memory area. Since we are working with the Int
data type, the capacity area was chosen taking into account the required size of the MemoryLayout
.
unsafeMutablePointer.deallocate()
unsafeMutablePointer.deinitialize(count: 1)
Consider the creation of elements in the example of UnsafeMutableRawPointer
. Since this pointer is mutable and without explicit typing, it is enough to allocate a memory area for an object and then write data to it. In this case, all operations for this pointer occur byte by byte without a specific data type.
let unsafeMutableRawPointer = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment) // 6000006E44F0
unsafeMutableRawPointer.storeBytes(of: 32, as: Int.self) // 6000006E44F0
unsafeMutableRawPointer.load(as: Int.self) // printed 32
The load
method allows you to read a memory area with a given data type. The storeBytes
method allows you to write to the allocated memory area. At the same time, because of the lack of binding to a specific data type, you can easily put and read data with a completely different type into the allocated area:
unsafeMutableRawPointer.initializeMemory(as: String.self, to: "123") // 6000006E44F0
unsafeMutableRawPointer.load(as: String.self) // printed “123”
unsafeMutableRawPointer.deallocate()
The address 6000006E44F0
was the number 32 with the data type Int
, but we rewrote it to the string "123"
, hello Python.
let size = MemoryLayout<Int>.size
let alignment = MemoryLayout<Int>.alignment
let unsafeMutableRawPointer1 = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment)
unsafeMutableRawPointer1.storeBytes(of: 32, as: Int.self) // 32
let unsafeMutableRawPointer2 = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment)
unsafeMutableRawPointer2.storeBytes(of: 40, as: Int.self) // 40
unsafeMutableRawPointer2.copyMemory(from: unsafeMutableRawPointer1, byteCount: size)
unsafeMutableRawPointer2.load(as: Int.self) // 32
Two pointers were created for different memory locations containing the numbers 32 and 40. Thanks to copyMemory
, we were able to copy information from one memory location to another. At the same time, the use of the copyMemory
method allows one-time copying, preserving the further independence of memory sections with different addresses.
let unsafeMutableRawPointer3 = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment)
unsafeMutableRawPointer3.storeBytes(of: 32, as: Int.self)
let unsafeMutableRawPointer4 = unsafeMutableRawPointer3.bindMemory(to: Int.self, capacity: size)
unsafeMutableRawPointer4.pointee // 32
unsafeMutableRawPointer3.storeBytes(of: 40, as: Int.self)
unsafeMutableRawPointer4.pointee // 40
Thanks to bindMemory
, both pointers point to the same memory location and will catch all changes, regardless of which pointer they are written to.
To allocate an area of memory for an array using UnsafeMutableBufferPointer
, the area for each element of the array will be allocated first:
let array: [Int] = [5, 6, 7, 8, 9]
let elementPointer = UnsafeMutablePointer<Int>.allocate(capacity: array.count)
let arrayPointer = UnsafeMutableBufferPointer(start: elementPointer, count: array.count)
Let’s fill in the previously allocated area. The offset between memory locations of different elements will be provided by the advanced(by: Int)
method.
for (index, value) in array.enumerated() {
elementPointer.advanced(by: index).pointee = value
}
In addition to writing, the advanced(by: Int)
method also helps when reading a specific array element by ordinal index:
elementPointer.advanced(by: 4).pointee // 9
UnsafeMutableBufferPointer
supports subscript[index]
to read and write array elements by ordinal index:
arrayPointer[4] = 5
elementPointer.advanced(by: 4).pointee // 5
arrayPointer.deallocate()
On this, the article comes to an end. I will talk about other types of manual memory management, such as Unmanaged objects
, in the next article.
Don’t hesitate to contact me on
Also published .