Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2016-06-26

Creating a unique number for a NSManagedObject | A simple algorithm in Swift

Sometimes I want to create a unique number. There are many ways to do just that, and we probably all have our preferences for one way or the other.

Recently I needed to identify an NSManagedObject in a unique way that would persist across application invocations and that should offer high performance when used in comparisons.

That pretty much ruled out a UUID. A UUID is almost guaranteed unique, but since it is string based, comparing two UUIDs also could be slow.

A 64 bit integer compare is probably the fastest way to compare anything, so how to create a unique 64 bit integer?

Specific for the purpose in question (a NSManagedObject subclass) the question is: How many unique identifiers are necessary? In my case, a million per app run should be more than enough. Even though the app is designed to run months at a time, it is extremely unlikely that more than a couple of hundred -or maybe even a few thousand- objects will be created. So a million should be (much) more than enough.

An easy way to create a unique instance number is to create a static counter. And each time an instance is created the static counter is increased and its value copied to the unique id. However that would not persist well. To persist this number and still be unique on the next App start, we would need some extra code to read the number of the already present objects in the core data store. If there are a lot, then timing is an issue. And of course the (un)necessary code.

This can be done easier if we were to start the static counter from a value that is derived from the current moment in time. Since all those moments are different from each other, we only need to ensure that there won't be an overlap due to the number of objects created.

The following algorithm does that just nicely:

class CDCounter: NSManagedObject {

    private static var queue = dispatch_queue_create("nl.balancingrock.swiftfire.cdcounter", DISPATCH_QUEUE_SERIAL)

    private static var instanceCounter: Int64 = {
        return Int64(NSDate().timeIntervalSince1970) * 1_000_000
    }()
    
    override func awakeFromInsert() {
        
        dispatch_sync(CDCounter.queue, { [unowned self] in
            self.instanceId = CDCounter.instanceCounter
            CDCounter.instanceCounter += 1
        })
    }
}

The instance counter is created after program start once the first object is created. (It's a lazy variable) After that the value is incremented by 1 for each object created.

Btw: "instanceId" is a property defined in the core data model.

As long as less than 1 million objects are instantiated in a single app run, this approach is both efficient and easy to create.

Note that the initialisation of the property is done in "awakeFromInsert()" which is called only when an object is inserted into a context. Thus only once for each object.

Also note that the assignment and increment of the static variable is done in a dispatch call to ensure that multi-threading will not create duplicates. (Using Darwin.OSAtomicIncrement64 is not enough, we also must protect the time between the assignment and the increment.)

The queue that is used is used only for the assignment/increment operation. It is of course possible to use a different queue in your app to do this, as long as you are aware that the "sync" operation called on the queue can potentially lead to deadlocks. Keeping the queue private avoids this.

Can the Int64 handle the huge numbers? Yes. For the year 3000 the initialization value of the instanceCounter is 3.2*10^16. An Int64 can contain positive numbers up to 2^63 = 9.2*10^18. Thus there is even room to up the ante on the million objects per app-run.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment