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

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.

Ready for more depth?

Master these concepts with our structured technical roadmap.

View Roadmap