Deep Dive • 11 August 2026 • Written by Mansi
Zero-Copy Disk Persistence: Memory-Mapped Files in Swift
Zero-Copy Disk Persistence: Memory-Mapped Files in Swift
When writing telemetry logs, cache indices, or high-frequency event queues to disk on iOS or macOS, standard file I/O operations (like FileHandle or write(to:)) can become a bottleneck. Traditional file writes copy data from user-space memory to kernel-space buffers before the OS commits it to disk.
Memory-mapping (mmap) bypasses this double-copy penalty by mapping a file directly into your process’s virtual address space. This allows your app to write data directly to the operating system’s page cache using raw memory pointers.
Setting up Memory Mapping in Swift
To memory-map a file, you work with low-level POSIX calls. You open a file descriptor, truncate the file to a fixed size, and map the memory space using the mmap system call.
import Foundation
class MemoryMappedLogger {
private var fileDescriptor: Int32 = -1
private var mappedPointer: UnsafeMutableRawPointer?
private let mappedSize: Int
init(path: String, size: Int) throws {
self.mappedSize = size
// Open the file with read/write access and create if it doesn't exist
fileDescriptor = open(path, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)
guard fileDescriptor != -1 else { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) }
// Truncate the file to the target size before mapping
guard ftruncate(fileDescriptor, off_t(size)) == 0 else {
close(fileDescriptor)
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
// Map the file into virtual memory
let pointer = mmap(
nil,
size,
PROT_READ | PROT_WRITE,
MAP_SHARED, // Shared memory changes are reflected on disk
fileDescriptor,
0
)
guard pointer != MAP_FAILED else {
close(fileDescriptor)
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
self.mappedPointer = pointer
}
}
Writing Data Directly to the Page Cache
Once the file is mapped, writing data is as simple as copying bytes into the memory pointer. The operating system handles page loads and synchronization behind the scenes.
extension MemoryMappedLogger {
func write(bytes: [UInt8], offset: Int) {
guard let basePointer = mappedPointer else { return }
// Developer Thoughts: We perform a direct memory copy.
// There is no system call overhead (like write()) during the operation.
let targetPointer = basePointer.advanced(by: offset)
bytes.withUnsafeBytes { rawBuffer in
if let sourceAddress = rawBuffer.baseAddress {
targetPointer.copyMemory(from: sourceAddress, byteCount: bytes.count)
}
}
}
// Developer Thoughts: Changes in MAP_SHARED memory are not guaranteed
// to be written to disk immediately. Use msync to force synchronization.
func flushToDisk() {
guard let pointer = mappedPointer else { return }
msync(pointer, mappedSize, MS_SYNC)
}
}
Summary
Memory-mapped files are highly effective for high-frequency binary writers (like loggers or databases) because they eliminate the CPU cost of copying data between user and kernel memory buffers. The main trade-off is crash safety: if the app crashes or the system loses power before msync is called, the data on disk might be incomplete. Additionally, mapping files on external or network drives can trigger hardware faults if the drive is disconnected.