Swift Custom Array Implementation Using UnsafeMutablePointer

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 } 
Enter fullscreen mode
Exit fullscreen mode

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) }
Enter fullscreen mode
Exit fullscreen mode

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 }
}
Enter fullscreen mode
Exit fullscreen mode

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
}
Enter fullscreen mode
Exit fullscreen mode

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
} 
Enter fullscreen mode
Exit fullscreen mode

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
}
Enter fullscreen mode
Exit fullscreen mode

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
}
Enter fullscreen mode
Exit fullscreen mode

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 }
}
Enter fullscreen mode
Exit fullscreen mode

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 }
}
Enter fullscreen mode
Exit fullscreen mode

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
}
Enter fullscreen mode
Exit fullscreen mode

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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Discover more from Coursity

Subscribe now to keep reading and get access to the full archive.

Continue reading

Scroll to Top