I have three tasks:
-
A fetch task. It fetches objects (around 2000 of them) from
CoreDataand passes theNSManagedObjectIDsto the main thread where they’re converted back toNSManagedObjectsand stored in anNSArray. This task is of least priority. -
A computation task. This task iterates through those objects and calculates some values for the object. The values are stored as transient attributes of the
NSManagedObjectsubclass. This task is the 2nd highest priority. -
An OpenGL ES Drawing Task. This is the drawing loop that updates the UI based on the result of the computation task. This task is highest priority as any slow down will decrease the frame rate and be very apparent when zooming or panning.
My issue has been creating a design pattern that allows the first two tasks to do their work while still allowing the OpenGL task to run at maximum frame rate. What inevitably happens if I use GCD queues is that the OpenGL task will be blocked by one of the other two and it will stutter.
I’m thinking that I need to have the OpenGL thread operate on it’s own copy of the data, but I’m not sure what that looks like or even if it’s the correct solution. Has anyone dealt with this type of issue before? If so, how did you achieve both concurrency and performance?
For those who find this thread at a later point, here’s how I eventually solved the issue:
The fetch task runs in it’s own GCD queue and fetches the NSManagedObjects from CoreData. I created a new class that contains the computed properties for each of my objects. Using the fetched data, I instantiated an NSArray of these objects and passed them on to the compute task by dispatching a block operation in the compute queue.
The compute task runs in it’s own GCD queue and computes the values for each of these objects every second or two. After it’s finished, it passes on the NSArray to the drawing queue by dispatching a block in the drawing queue.
The drawing task runs in it’s own GCD queue and is asynchronous. All it does is loop through the objects and draws them.
Basically, when one queue is finished operating on it’s data, it passes the data onto the next queue by dispatching a block in that queue. This prevents race conditions and solved the design pattern issues I was having. The only downside to this approach is that it does require a bit more memory. In my tests however, after a few optimizations, the memory usage is not all that bloated. Thanks to the various community inputs as I stumbled through this.