Search code examples
objective-cmacoscocoainitializer

Is there a good way to init an ObjC object on a background dispatch_queue?


I have an ObjC class RuleSet that needs quite heavy initialization (parses a big JSON file, does lots of querying into several OS services, applies heuristics, and last, it builds a set of complicated NSPredicates, that can later be used for fast evaluation.

My program (except for this object) receives high-priority events from MacOS (EndpointSecurity framework) and has to respond to these within very narrow deadlines - based on the fast evaluation of my current RuleSet instance.

I handle those events in a special (highest priority) concurrent dispatch_queue I created.

However... if I receive some external trigger, to change my RuleSet - and I need to build the new RuleSet instance before applying it, I want the code creating it not to know about the internal structure of dispatch queues, and simply call

RuleSet *newRules = [[RuleSet alloc] initWithJSON:newJSONPath];
[myEventHandlingEngine setRuleSet:newRuleSet];

I'd like the RuleSet init to be able to somehow defer the work to another, low-priority serial dispatch_queue, maybe in parts. I don't mind blocking the caller until I'm done (this heavy init shouldn't take more than a second...). still, it's important that I do it on a queue that has lower priority - so that incoming events will be handled first.

Now I tried to write things like

- (instancetype) initWithJSON:newJSONPath:(NSString *)jsonFilePath
{
   dispatch_sync([RuleSet setupQueue], ^{
     if ( nil == (self = [super init]) ) {
        self = nil;
          return self;
     }

        // heavy work here
        });
    return self;
}        

but that of course won't even compile because 'self' isn't a __block variable, and I'm not sure it can be...

Any advice on how to go about this?


Solution

  • You should try using a dispatchGroup to handle that situation :

    - (instancetype) initWithJSON:newJSONPath:(NSString *)jsonFilePath
    {
        if ( nil == (self = [super init]) ) {
            self = nil; //This seems useless since self is already nil ?
            return self;
        }
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, [OITPreventionPolicy policySetupQueue], ^{
        
          // Execute your heavy work here
    
         });
         // Wait for the group to finish its work (you could replace 'forever' by a timeout)
         dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
         return self;
    }
    

    The call to dispatch_group_wait will block the thread in which the object is created (so it cannot be the main thread).