Skip to content

Instantly share code, notes, and snippets.

@andreweades
Last active October 2, 2022 20:12
Show Gist options
  • Save andreweades/b2f4ea76ef749c08de85eaf3ea01b586 to your computer and use it in GitHub Desktop.
Save andreweades/b2f4ea76ef749c08de85eaf3ea01b586 to your computer and use it in GitHub Desktop.
Generates a fine-grained monotonic Int that can be used as a unique ID (across multiple devices or processes) e.g. as a field in a CKRecord that can be queried
//
// Montonic.swift
// Created by Andrew Eades on 01/04/2020.
// Copyright © 2020 Andrew Eades. All rights reserved.
//
import Foundation
public struct Monotonic {
static private let semaphore = DispatchSemaphore(value: 1)
private init() {}
/**
Generates a fine-grained monotonic Int that can be used as a unique ID (across multiple devices or processes)
e.g. as a field in a CKRecord that can be queried
Although it is possible that 2 devices could generate the exact same number it is very unlikely and would require
the clocks to hit the same nanosecond. Once you have multiple devices colliding at this granularity, you have a bigger
problem than this can solve ;)
On each device, monotonic should always be called on the same thread to prevent multiple threads generating identical
numbers. The test shows this.
- Returns: A unique Int that is bigger than all other values previously generated
*/
private static func monotonic(calendar: Calendar = Calendar.current, now: () -> Date = { Date() }) -> Int {
// calendar can replaced with a mock for debugging purposes
// now can replaced with a mock for debugging purposes
var nanoseconds: Int!
var date: Date!
repeat {
date = now()
let dateComponents = calendar.dateComponents([
.nanosecond
], from: date)
nanoseconds = dateComponents.nanosecond ?? 0
} while nanoseconds > 900_000_000
let epoch = Int(floor(date.timeIntervalSince1970))
let int = (epoch * 1_000_000_000) + nanoseconds
return int
}
public static func next() -> Int {
// the semaphore is used here to queue calls so that we keep to the monotonic order
semaphore.wait()
let int = monotonic()
semaphore.signal()
return int
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment