Skip to content

Instantly share code, notes, and snippets.

@kcmoffat
Last active March 27, 2017 14:22
Show Gist options
  • Save kcmoffat/af856ab4b605a00216d3b5f627e50a84 to your computer and use it in GitHub Desktop.
Save kcmoffat/af856ab4b605a00216d3b5f627e50a84 to your computer and use it in GitHub Desktop.
/**
* Based on https://gist.github.com/mikelehen/3596a30bd69384624c11#file-generate-pushid-js
*
* Fancy ID generator that creates 20-character string identifiers with the following properties:
*
* 1. They're based on timestamp so that they sort *after* any existing ids.
* 2. They contain 72-bits of random data after the timestamp so that IDs won't collide with other clients' IDs.
* 3. They sort *lexicographically* (so the timestamp is converted to characters that will sort properly).
* 4. They're monotonically increasing. Even if you generate more than one in the same timestamp, the
* latter ones will sort after the former ones. We do this by using the previous random bits
* but "incrementing" them by 1 (only in the case of a timestamp collision).
*/
+(NSString *)generatePushID
{
static uint64_t lastPushTime = 0; // uint64_t instead of NSUInteger to work correctly on 32-bit systems like Apple Watch.
static NSString *PUSH_CHARS = @"-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
static NSMutableArray *lastRandChars = nil;
if (!lastRandChars) {
lastRandChars = [NSMutableArray array];
for (int i = 0; i < 12; i++) [lastRandChars addObject:@0];
}
uint64_t now = round([[NSDate date] timeIntervalSince1970] * 1000);
BOOL duplicateTime = (now == lastPushTime);
lastPushTime = now;
NSMutableString *timeStampChars = [[NSMutableString alloc] initWithCapacity:8];
int i;
for (i = 7; i >= 0; i--) {
[timeStampChars insertString:[PUSH_CHARS substringWithRange:NSMakeRange(now % 64, 1)] atIndex:0];
now >>=6;
}
NSAssert(now == 0, @"We should have converted the entire timestamp");
NSMutableString *pushId = [NSMutableString stringWithString:timeStampChars];
if (!duplicateTime) {
for (i = 0; i < 12; i++) {
lastRandChars[i] = [NSNumber numberWithUnsignedInteger:arc4random_uniform(64)];
}
} else {
// If the timestamp hasn't changed since last push, use the same random number, except incremented by 1.
for (i = 11; i >= 0 && [lastRandChars[i] isEqual: @63]; i--) {
lastRandChars[i] = @0;
}
i = i == -1 ? 11 : i;
lastRandChars[i] = @([(NSNumber *)lastRandChars[i] unsignedIntegerValue]+1);
}
for (i = 0; i < 12; i++) {
[pushId appendString:[PUSH_CHARS substringWithRange:NSMakeRange([lastRandChars[i] unsignedIntegerValue], 1)]];
}
NSAssert([pushId length] == 20, @"Length should be 20.");
return pushId;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment