Nesting methods with completion blocks - objective-c

I have several methods that have the following structure:
- (void) doSomethingWithCompletion: (void (^)(NSError *error)) completion {
__block NSError *fetchError = nil;
dispatch_group_t dispatchGroup = dispatch_group_create();
for (Item* item in self.items)
{
dispatch_group_enter(dispatchGroup);
// fetchError = fetch online data
}
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(),^{
if (completion)
completion(fetchError);
});
}
My goal is to run several doSomethings after each other, so I could so something like this:
[self doSomethingAWithCompletion: ^(NSArray *results NSError *error) {
if (error == nil) {
[self doSomethingBWithArray: results withCompletion: ^(NSError *error) {
if (error == nil) {
[self doSomethingCWithCompletion: ^(NSError *error) {
if (error == nil) {
// done!!
}
}];
}];
}];
What I am struggling with is the second code block (no pun); is nesting all the methods the way to go, or are there other solutions?
The important thing is, is that doSomethingBWithCompletion cannot begin before doSomethingAWithCompletion is done, and doSomethingCWithCompletion needs to wait until doSomethingBWithCompletion is complete, etc.
Also, doSomethingBWithCompletion uses data that is generated in doSomethingAWithCompletion, etc.
EDIT: After a lot of thinking, refactoring, and simplifying my code, I was able to end up with only two functions, using the nested approach as I outlined above and with a #property for the results array.

The important thing is, is that doSomethingBWithCompletion cannot begin before doSomethingAWithCompletion is done, and doSomethingCWithCompletion needs to wait until doSomethingBWithCompletion is complete, etc.
According to the comments:
The Results of the block are not depending on the result of the first aren't they?
And
Yes they are. For instance, in the first doSomething I determine which items are outdated, in the second doSomething I download and parse the updated items, and in the third doSomething I save them to the store.
(BTW: You should really add this information to your Q.)
If an action depends on the result (not only execution) of a previous action, you have to nest the blocks. Your code does not look like this, because there is no data passed to the completion blocks.
If you do not have such a dependency, you could use a private serial dispatch queue. However, this is a solution in your case, too, if you have akin of a manager class holding the data passed from block to block. But this seems to be highly anticonceptual.

There may be community attempt to add promises to objective-c, and it would be nice to have, because that's just what's needed here. Without committing to a whole new library, you can handle the nesting (which I agree is a bummer) by doing the async tasks recursively... something like this for your example code:
Start with an operation that takes no params and results in an array...
- (void)firstOpWithCompletion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSArray *components = [#"this is an array of strings from the FIRST op" componentsSeparatedByString:#" "];
if (completion) {
completion(components, nil);
}
});
}
Here are a couple that take an array param and result in an array...
- (void)secondOpWithParam:(NSArray *)array completion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if (completion) {
NSArray *components = [#"these strings are from the SECOND op" componentsSeparatedByString:#" "];
NSArray *result = [array arrayByAddingObjectsFromArray:components];
if (completion) {
completion(result, nil);
}
}
});
}
- (void)thirdOpWithParam:(NSArray *)array completion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if (completion) {
NSArray *components = [#"these strings are from the THIRD op" componentsSeparatedByString:#" "];
NSArray *result = [array arrayByAddingObjectsFromArray:components];
if (completion) {
NSLog(#"we did it. returning %#", result);
completion(result, nil);
}
}
});
}
// ...as many as these as you need
Now, as in my answer prior to this edit, we just add a param pass initially and in the intermediate calls...
- (void)doSeveralThingsInSequence:(NSArray *)todo param:(NSArray *)param {
if (todo.count == 0) return;
// you could generalize further here, by passing a "final" block and run that before the return
NSString *nextTodo = todo[0];
SEL sel = NSSelectorFromString(nextTodo);
IMP imp = [self methodForSelector:sel];
void (*func)(id, SEL, NSArray *, void (^)(NSArray *, NSError *)) = (void *)imp;
func(self, sel, param, ^(NSArray *result, NSError *error) {
if (!error) {
NSArray *remainingTodo = [todo subarrayWithRange:NSMakeRange(1, todo.count-1)];
[self doSeveralThingsInSequence:remainingTodo param:result];
}
});
}
Stepping through the code: this method bails if there's nothing to do, otherwise it takes the next selector name from the passed array, gets the C function implementation for it and invokes it, placing a completion block on the call stack that starts the process over for the remaining selectors.
Finally, doEverything calls the first operation to get started, then starts running a list of operations (which can be an arbitrarily long list) passing the array output from one as the array input to the next. (You could generalize this further by passing id's along the chain
- (void)doEverything {
[self firstOpWithCompletion:^(NSArray *array, NSError *error) {
NSArray *todo = #[ #"secondOpWithParam:completion:", #"thirdOpWithParam:completion:" ];
[self doSeveralThingsInSequence:todo param:array];
}];
}
I tested this exactly as posted and saw the expected output:
(
this,
is,
an,
array,
of,
strings,
from,
the,
FIRST,
op,
these,
strings,
are,
from,
the,
SECOND,
op,
these,
strings,
are,
from,
the,
THIRD,
op
)

Related

What happens with unmatched dispatch_group_enter and dispatch_group_leave?

I have an async NSOperation to download data for multiple ImageFile objects. Since this is all happening asynchronously I am using a dispatch group to track the requests and then dispatch_group_notify to complete the operation when they are all done.
The question I have is what happen when the operation is ended prematurely, either by cancelation or by some other error. The dispatch group will be left with unmatched dispatch_group_enter and dispatch_group_leave so dispatch_group_notify will never be called. Is it the block retained somewhere by the system forever waiting, or will it get released when the NSOperation gets released?
Or is my approach not ideal, how else should I do this?
- (void)main
{
if (self.cancelled) {
[self completeOperation];
return;
}
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.persistentStoreCoordinator = self.persistentStoreCoordinator;
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
[context performBlock:^{
NSFetchRequest *request = [ImageFile fetchRequest];
request.predicate = ....
request.sortDescriptors = ...
NSError *error;
NSArray *imageFiles = [context executeFetchRequest:request error:&error];
if (!imageFiles) {
// Error handling...
[self completeOperation];
return;
}
dispatch_group_t group = dispatch_group_create();
for (ImageFile *imageFile in imageFiles) {
dispatch_group_enter(group);
#autoreleasepool {
[self.webService requestImageWithId:imageFile.id completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (self.cancelled) {
[self completeOperation];
return;
}
[context performBlock:^{
if (data && !error) {
imageFile.data = data;
NSError *error;
if (![context save:&error]) {
// Error handling...
[self completeOperation];
return;
}
}
dispatch_group_leave(group);
}];
}];
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self completeOperation];
});
}];
}
From the docs for dispatch_group_enter():
A call to this function must be balanced with a call to dispatch_group_leave.
From the docs for dispatch_group_t:
The dispatch group keeps track of how many blocks are outstanding, and GCD retains the group until all its associated blocks complete execution.
It talks about outstanding blocks, but what it really means is unmatched calls to dispatch_group_enter().
So, the answer to your question about what happens is that the dispatch group object effectively leaks. The block object passed to dispatch_group_notify() and any objects it has strong references to also leak. In your case, that includes self.
The answer to your question of whether your approach is "ideal" is: no, it's not ideal. It's not even valid by the design contract of GCD. You must balance all calls to dispatch_group_enter() with calls to dispatch_group_leave().
If you want to somehow distinguish between success and failure or normal completion and cancellation, you should set some state that's available to the notify block and then code the notify block to consult that state to decide what to do.
In your case, though, the code paths where you fail to call dispatch_group_leave() just do the same thing the notify block would do. So I'm not even sure why you're not just calling dispatch_group_leave() rather than calling [self completeOperation] in those cases.

Obj-C class method results from block

I understand that this function first return "images" then "findObjectsInBackgroundWithBlock" retrieve data that's why results is nil.
1 - how to return array from block?
2 - how to put this block not in main thread?
+(NSMutableArray *)fetchAllImages{
__block NSMutableArray *images = [NSMutableArray array];
PFQuery *query = [PFQuery queryWithClassName:#"Photo"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *object in objects) {
PFFile *applicantResume = object[#"imageFile"];
NSData *imageData = [applicantResume getData];
NSString *imageName = [ImageFetcher saveImageLocalyWithData:imageData FileName:object.objectId AndExtention:#"png"];
[images addObject:imageName];
// here images is not empty
}
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
// here images is empty
return images;
}
The method performs its work asynchronously, and the caller needs to know that. So,
Do not:
+(NSMutableArray *)fetchAllImages{
return an array, because the array is not ready at the time of return.
Do:
+ (void)fetchAllImages {
return nothing, because that's what you have when the method finishes execution.
But how to give the images to the caller? The same way that findObjectsInBackgroundWithBlock does, with a block of code that runs later....
Do:
+ (void)fetchAllImagesWithBlock:(void (^)(NSArray *, NSError *)block {
Then, using your code from within the findBlock:
[images addObject:imageName];
// here images is not empty
// good, so give the images to our caller
block(images, nil);
// and from your code, if there's an error, let the caller know that too
NSLog(#"Error: %# %#", error, [error userInfo]);
block(nil, error);
Now your internal caller calls this method just like your fetch code calls parse:
[MyClassThatFetches fetchAllImagesWithBlock:^(NSArray *images, NSError *error) {
// you can update your UI here
}];
Regarding your question about the main thread: you want the network request to run off the main, and it does. You want the code that runs after it finishes to run ON the main, so you can safely update the UI.
It doesn't work that way.
You are calling an asynchronous method. You can't wait for the result of an asynchronous method and return the result (well, you can, but not if you are asking how to do it on stackoverflow). With an asynchronous block, you trigger an action, and it is up to the completion block to deliver the results where they are needed.
There are gazillions of examples how to do this on stackoverflow. Looking for them is your job.

Using GCD and Core Data causes crash

I retrieve data back from our server and I need to process it.
For each key, I create a NSManagedObject. Each object is created in the same context. I am using Magical Record.
-(id)init {
if (self = [super init]){
self.context = [NSManagedObjectContext MR_contextForCurrentThread];
}
return self;
}
Threading:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
for (id key in boundariesDictionary) {
dispatch_group_async(group, queue, ^{
DLog(#"nsthread: %#", [NSThread currentThread]);
NSString *boundaryIDString;
if ([key isKindOfClass:[NSString class]]) {
boundaryIDString = key;
}
else if ([key isKindOfClass:[NSNumber class]]) {
boundaryIDString = [key stringValue];
}
if (boundaryIDString) {
DLog(#"boundaryIDString: %#", boundaryIDString)
NSDictionary *boundaryDictionary = [boundariesDictionary objectForKey:key];
Boundary *boundary = [Boundary MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:#"boundaryID == %# AND api == %#", [NSNumber numberWithInteger:[boundaryIDString integerValue]], self.serverCall.API] inContext:self.context];
if ([boundaryDictionary objectForKey:AVI_NAME]) {
if (boundary == nil) {
DLog(#"creating boundary %#", boundaryIDString);
boundary = [Boundary MR_createInContext:self.context];
boundary.boundaryID = [NSNumber numberWithInteger:[boundaryIDString integerValue]];
}
}
boundary = [self processBoundary:boundary fromBoundaryDictionary:boundaryDictionary];
}
}
}
[self processBoundary] just takes the dictionary and sets it to the managed object's attributes.
if ([boundaryDictionary objectForKey:#"name"]) {
boundary.name = [boundaryDictionary objectForKey:#"name"];
}
//more data processing
This is causing an error though:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x1776f7f0> was mutated while being enumerated.'
It runs fine if I don't use the same context for each thread.
I don't understand what set other then the NSDictionary boundariesDictionary that i'm enumerating through. I am not mutating boundariesDictionary at all, only copying the data into core data.
When I PO the object (0x1776f7f0 in this case), I get a list of Boundary objects in a set. Those Boundary objects would only exist in the NSManagedObjectContext "set", I don't add them to an NSArray, NSDictionary, or NSSet. But I don't believe I enumerate over that set. I do mutate it by creating new boundary objects to it.
I think there is something going on that I don't understand or quite grasp yet.
Any ideas?
UPDATE:
for (id key in boundariesDictionary) {
NSString *boundaryIDString;
if ([key isKindOfClass:[NSString class]]) {
boundaryIDString = key;
}
else if ([key isKindOfClass:[NSNumber class]]) {
boundaryIDString = [key stringValue];
}
if (boundaryIDString) {
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DLog(#"saveWithBlock thread: %#", [NSThread currentThread]);
NSDictionary *boundaryDictionary = [boundariesDictionary objectForKey:key];
Boundary *boundary = [Boundary MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:#"boundaryID == %# AND api == %#", [NSNumber numberWithInteger:[boundaryIDString integerValue]], self.serverCall.API] inContext:localContext];
if ([boundaryDictionary objectForKey:AVI_NAME]) {
if (boundary == nil) {
boundary = [Boundary MR_createInContext:localContext];
boundary.boundaryID = [NSNumber numberWithInteger:[boundaryIDString integerValue]];
}
boundary = [self processBoundary:boundary fromBoundaryDictionary:boundaryDictionary];
if (boundary == nil) {
//Prompt Error
}
else {
for (NSNumber *groupID in groupIDs) {
if ([groupID isKindOfClass:[NSNumber class]]) {
Group *group = [Group MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:#"groupID == %# OR groupID == 0 AND api == %#", groupID, self.serverCall.API]];
if (group != nil) {
group.lastUpdated = [NSDate date];
[group addBoundariesObject:boundary];
}
else {
DLog(#"group %# DNE", groupID);
}
}
}
}
}
} completion:^(BOOL success, NSError *error) {
DLog(#"saveWithBlock completion Block | time: %f", [[NSDate date] timeIntervalSinceDate:startTime]);
}];
}
}
So for my Group 29 should see all the boundaries I'm creating, but its not. Its inconsistent. Sometimes sees all, sometimes some, and sometimes none.
Also, I often see
NO CHANGES IN ** BACKGROUND SAVING (ROOT) ** CONTEXT - NOT SAVING
in the log. Its also inconsistent on how many of these messages I see, while the context that do save will insert more then 1 object.
Not sure if that is how it should be behaving, seems like it should be a 1-to-1 ratio if each block has its own context and each block only creates 1 object.
I log each thread ID, and it is creating a new thread for each block. No thread ID is being logged twice, so the threads shouldn't be being reused.
Managed object contexts are not thread safe, you must not use them or any managed objects/sets/data structures/whatever they might return from different queues or threads.
I don't know if magical record supports it, but you should definitely be using queue containment for your contexts, and then you have to do everything within the context of the MOC's private queue.
If you use thread containment, you must guarantee that the context and any managed objects created by the context are always serially accessed, which you're absolutely not doing in the code above.
You're issue is that gcd queues a threads are not a one to one mapping. GCD reuses threads, and thus your are likely crossing thread boundaries unknowingly here. My suggestion is to simply create a new context and stop using contextForCurrentThread. I wrote more details about the issues on my blog. ContextForCurrentThread will be deleted in an upcoming release.

Parsing asynchronously using NSOperationQueue or GCD

I've got this parsing operation that currently works fine, but I've started to notice that it is freezing up my UI slightly so I'm trying to refactor and get this done asynchronously. I'm having some issues however and was hoping someone could point me in the right direction. Here's my current (synchronous) code:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
return nil;
}
NSArray *events = [eventsData valueForKeyPath:#"resultsPage.results"];
if (events == nil) {
//parsing error
return nil;
}
NSLog(#"events looks like %#", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:#"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:#"location.lat"];
event.longitude = [object valueForKeyPath:#"location.lng"];
event.title = [object valueForKeyPath:#"displayName"];
event.venue = [object valueForKeyPath:#"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:#"uri"]];
event.artist = [object valueForKeyPath:#"performance.artist.displayName"];
event.date = [object valueForKeyPath:#"start.datetime"];
[formattedEvents addObject:event];
}
return [NSArray arrayWithArray:formattedEvents];
}
I've been looking into NSOperationQueue's and I'm struggling to find a solution as I'd like to return an array from this method and operation queues are not meant to have return values. I'm also looking at GCD and i've got somethinbg like this:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
dispatch_queue_t backgroundQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block NSMutableArray *mutable = [NSMutableArray array];
dispatch_async(backgroundQueue, ^{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
mutable = nil;
}
NSArray *events = [eventsData valueForKeyPath:#"resultsPage.results"];
if (events == nil) {
//parsing error
mutable = nil;
}
NSLog(#"events looks like %#", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:#"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:#"location.lat"];
event.longitude = [object valueForKeyPath:#"location.lng"];
event.title = [object valueForKeyPath:#"displayName"];
event.venue = [object valueForKeyPath:#"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:#"uri"]];
event.artist = [object valueForKeyPath:#"performance.artist.displayName"];
event.date = [object valueForKeyPath:#"start.datetime"];
[formattedEvents addObject:event];
}
mutable = [NSMutableArray arrayWithArray:formattedEvents];
});
return [mutable copy];
}
For some reason, this seems to be returning the object before the parsing has finished however, as I'm gettting no data out of that mutable object, but I'm noticing that the parsing is indeed occurring (i'm logging out the results). can anyone give me an idea about how to get this asynch stuff going?
Thanks!!
You primary problem is that by their very nature asynchronous operations can't synchronously return a result. Instead of returning an array from -eventsFromJSON:, you should provide a way for the caller to receive a callback when the results are finished. There are two common approaches to this in Cocoa.
You can create a delegate with an associated delegate protocol including a method like -parser:(Parser *)parser didFinishParsingEvents:(NSArray *)events, then have your parser call this method on its delegate when parsing is finished.
Another solution is to allow the caller to provide a completion block to be executed when parsing is complete. So, you might do something like this:
- (void)eventsFromJSON:(NSString *)objectNotation completionHandler:(void (^)(NSArray *events))completionHandler)
{
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
NSMutableArray *mutable = [NSMutableArray array];
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
// Snip...
mutable = [NSMutableArray arrayWithArray:formattedEvents];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler([mutable copy]);
});
});
}
Then you can call this code some thing like this:
- (void)parseJSONAndUpdateUI // Or whatever you're doing
{
NSString *jsonString = ...;
Parser *parser = [[Parser alloc] init];
[parser parseEventsFromJSON:jsonString completionHandler:^(NSArray *events){
// Update UI with parsed events here
}];
}
I like the second, block-based approach better. It makes for less code in most cases. The code also reads closer to the synchronous approach where the method just returns an array, since the code that uses the resultant array simply follows the method call (albeit indented since it's in the completion block's scope).
I would recommend using a completion block that you pass into your parse method. This way you don't have to return a value, but can do what you need to with the information once it is parsed. You just have to make sure you use GCD again to put the completion block on the main thread.
You could also post a notification on the main thread once the operation is complete that contains the array in userInfo.
Returning a value will not work however for asynchronous operations.
You are getting a returned object before the parsing has finished because your return [mutable copy] is outside of the dispatch_async block. Since dispatch_async functions asynchronously, it will return immediately, and then calls your return [mutable copy] (which is empty because it's not done parsing).

In Objective-C, I'm trying to encapsulate multiple error-able calls and "return" the most useful error

I put "return" in quotes because I don't want to literally return it. I want to do it similar to how you pass a pointer-to-a-pointer for [NSString stringWithContentsOfFile:usedEncoding:error:].
I would like to make parseFiles:error return nil and have the error reference that was passed in contain the first or second error, depending on which one failed. It seems like a Cocoa way to do it?
EDIT: Sorry, I should've been more clear about where I was having the problem. If the first path is bogus, it functions as I want. (I get the error instance outside and it prints.) If the first path is legit, as it filler string below implies, I get EXC_BAD_ACCESS.
But now I fixed it. I need to refer to it as *error inside the parseFiles:error: method and use == nil when checking if it failed. I thought I could just to if (error)...
EDIT 2 Ok, it doesn't work. I'm getting EXC_BAD_ACCESS. I'm not sure what I'm doing wrong with the conditions that check for the errors.
#implementation PassingError
- (id)init {
self = [super init];
NSError *error;
[self parseFiles:#"/untitled.py" error:&error];
if (error != nil) {
NSLog(#"I failed because: %#", error);
}
return self;
}
// Wraps with reading errors.
- (NSString *)parseFiles:(NSString *)path error:(NSError **)error {
NSStringEncoding enc1;
NSString *contents1 = [NSString stringWithContentsOfFile:path
usedEncoding:&enc1 error:*&error];
// there was a read error
// I need an asterisk here...
if (*error != nil) {
// ...and also one here
NSLog(#"FIRST ERROR: %#", *error);
return nil;
}
// here is where you'd do something that might cause another error,
// I'll just try and read a second file for simplicity
NSStringEncoding enc2;
NSString *contents2 = [NSString stringWithContentsOfFile:#"/untitled.py"
usedEncoding:&enc2 error:*&error];
// there was a SECOND error
if (*error != nil) {
NSLog(#"SECOND ERROR: %#", *error);
return nil;
}
// return both or whatever
return [NSArray arrayWithObjects:contents1, contents2, nil];
}
#end
Passing pointers around in Objective-C can get confusing. I remember having trouble grasping what needed to be done. When you have a method like this:
- (BOOL) saveValuesAndReturnError:(NSError **) error
{
BOOL success = [self doSomethingImportant];
if (!success && error)
{
// Unsuccessful and error is a valid ptr-to-ptr-to-NSError.
// Basically, someone has given us the address of a (NSError *).
// We can modify what that pointer points to here.
*error = [NSError errorWithDomain:#"myDomain" code:100 userInfo:nil];
}
return success;
}
This is intended to be invoked like this:
// If the caller doesn't care that it failed:
[someObject saveValuesAndReturnError:NULL];
// Or, if the caller wants to get error information on failure
NSError *anError = nil;
BOOL success;
// pass address of our (NSError *)
success = [someObject saveValuesAndReturnError:&anError];
if (!success)
{
// anError now points to an NSError object, despite being initialised to nil,
// because we passed the address of our NSError ptr, the method was able to
// change where `anError` points to.
NSLog (#"An error occurred while saving values: %#", anError);
}
Perhaps a very relevant read in this case is a CIMGF blog post covering exactly this topic.
However...
I remember reading a while ago that methods that return errors via method arguments such as stringWithContentsOfFile:usedEncoding:error: make no guarantee not to modify the error argument for success. In other words, you cannot rely on the value of the error parameter if the method succeeded. In your particular case, it may be better to do:
- (NSString *)parseFiles:(NSString *)path error:(NSError **)error {
NSStringEncoding enc1, enc2;
NSError *innerError;
NSString *contents1 = [NSString stringWithContentsOfFile:path
usedEncoding:&enc1
error:&innerError];
if (contents1 == nil)
{
if (error) *error = innerError;
return nil;
}
NSString *contents2 = [NSString stringWithContentsOfFile:#"/untitled.py"
usedEncoding:&enc2
error:&innerError];
if (contents2 == nil)
{
if (error) *error = innerError;
return nil;
}
// do whatever with contents1 and contents2
}