Last active
October 2, 2022 20:12
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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