In this article, we explore a custom array implementation in Swift using UnsafeMutablePointer. This implementation offers insights into manual memory management, dynamic resizing, and conformance to protocols such as CustomDebugStringConvertible and Sequence. The goal is to provide a detailed overview of the internal workings of Swift arrays.
Overview
The custom array MyArray allows storage and manipulation of elements of any type T. It supports dynamic resizing, appending, insertion, and removal of elements, while ensuring memory safety through proper allocation and deallocation.
Key Features
Dynamic Resizing: Automatically adjusts the capacity of the array when it reaches its limit.
Memory Management: Uses UnsafeMutablePointer for low-level memory operations.
Sequence Conformance: Implements the Sequence protocol for iteration.
Debug Description: Provides a custom debug description for easier debugging.
Implementation Details
Properties
struct MyArray<T> : CustomDebugStringConvertible, Sequence { private var capacity: Int private var storage: UnsafeMutablePointer<T> private var size: Int var count: Int { return size } var isEmpty: Bool { return size == 0 }
capacity: The maximum number of elements the array can hold without resizing.
storage: A pointer to the array’s memory storage.
size: The current number of elements in the array.
count: Returns the number of elements in the array.
isEmpty: Checks if the array is empty.
Initialiser
init(initialCapacity: Int = 2) { self.capacity = initialCapacity self.size = 0 self.storage = UnsafeMutablePointer<T>.allocate(capacity: initialCapacity) }
Resizing
private mutating func resize() { if size >= capacity { /* Double the capacity */ let newCapacity = capacity * 2 /* Allocating new storage with new capacity */ let newStorage = UnsafeMutablePointer<T>.allocate(capacity: newCapacity) /* Copying the existing elements to the new storage */ for i in 0..<count { newStorage[i] = storage[I] } /* Deallocating old storage */ storage.deallocate() storage = newStorage capacity = newCapacity }
}
The resize method doubles the capacity when needed and reallocates memory, copying existing elements to the new storage.
Adding Elements
public mutating func append(_ item: T) { resize() storage[size] = item size += 1
}
The append method adds a new element to the end of the array, resizing if necessary.
Inserting Elements
public mutating func insert(_ item: T, at index: Int) { guard index >= 0 && index <= size else { fatalError("Index out of bounds") } resize() for i in stride(from: count, to: index, by: -1) { storage[i] = storage[i - 1] } storage[index] = item size += 1
}
The insert method inserts an element at a specified index, shifting elements as needed.
Removing Elements
@discardableResult
public mutating func remove(at index: Int) -> T { guard index >= 0 && index < size else { fatalError("Index out of bounds") } let removedElement = storage[index] for i in index..<size - 1 { storage[i] = storage[i + 1] } size -= 1 return removedElement
}
The remove method removes an element at a specified index and returns it.
Clearing the Array
public mutating func removeAll() { // Deallocate the existing elements storage.deallocate() capacity = 2 // Reinitialise the storage storage = UnsafeMutablePointer<T>.allocate(capacity: capacity) size = 0
}
The removeAll method deallocates all elements and resets the array.
Subscript
subscript(index: Int) -> T { get { guard index >= 0 && index < size else { fatalError("Index out of bounds") } return storage[index] } set { guard index >= 0 && index < size else { fatalError("Index out of bounds") } storage[index] = newValue }
}
The subscript allows getting and setting elements at a specified index.
Sequence Conformance
func makeIterator() -> AnyIterator<T> { var index = 0 return AnyIterator { guard index < self.size else { return nil } let element = self.storage[index] index += 1 return element }
}
The makeIterator method provides an iterator for the array.
Debug Description
var debugDescription: String { var result = "[" for i in 0..<size { result += "\(storage[I])" if i < size - 1 { result += ", " } } result += "]" return result
}
The debugDescription property returns a string representation of the array.
Conclusion
This custom array implementation demonstrates how to manage memory manually and provides basic array functionalities. While UnsafeMutablePointer offers powerful capabilities, it requires careful handling to avoid memory leaks and ensure safety. This implementation serves as an educational example and can be extended for more advanced use cases.
Please find the complete source code Here
Discover more from Coursity
Subscribe to get the latest posts sent to your email.