Obj-C class method results from block - objective-c

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.

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.

Nesting methods with completion blocks

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
)

NSURLSession uploadTaskWithRequest - use block within completion handler

I have a class which manages all calls to an api.
It has a method to manage this, lets call this callAPIMethod:
This method accepts a success and fail block.
Inside this method, I call uploadTaskWithRequest to make a call to an API.
Within the uploadTaskWithRequest completion handler I'd like to (depending on the result) pass results back through to either the success or fail blocks.
I'm having some issues with this. It works and is keeping everything super tidy but when I call callAPIMethod using the success/fail blocks it's locking up the UI/MainThread rather than being asynchronous as I'd expect.
How should I go about implementing this pattern? Or is there a better way to go about it?
I don't need to support pre-iOS7.
Thanks
Edit: Basic implementation discussed above.
- (void)callApiMethod:(NSString *)method withData:(NSString *)requestData as:(kRequestType)requestType success:(void (^)(id responseData))success failure:(void (^)(NSString *errorDescription))failure {
[redacted]
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session uploadTaskWithRequest:request
fromData:postData
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
failure(error.description);
} else {
NSError *jsonError;
id responseData = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&jsonError];
if (jsonError) {
failure(jsonError.description);
} else {
success(responseData);
}
}
}];
[task resume];
}
CallAPI method, used as follows (from a UITableViewController):
[apiController callApiMethod:#"users.json?action=token"
withData:loginData
as:kRequestPOST
success:^(id responseData) {
if ([responseData isKindOfClass:[NSDictionary class]]) {
if ([responseData objectForKey:#"token"]) {
//Store token/credentials
} else if ([responseData objectForKey:#"error"]) {
//Error
[self displayErrorMessage:[responseData objectForKey:#"error"]];
return;
} else {
//Undefined Error
[self displayErrorMessage:nil];
return;
}
} else {
//Error
[self displayErrorMessage:nil];
return;
}
//If login success
}
failure:^(NSString *errorDescription) {
[self displayErrorMessage:errorDescription];
}];
Your NSURLSession code looks fine. I'd suggest adding some breakpoints so you can identify if it is deadlocking somewhere and if so, where. But nothing in this code sample would suggest any such problem.
I would suggest that you ensure that all UI calls are dispatched back to the main queue. This NSURLSessionUploadTask completion handler may be called on a background queue, but all UI updates (alerts, navigation, updating of UIView controls, etc.) must take place on the main queue.

How to return response object in AFNetworking in class method?

I’m sorry if this question is too basic, but I can’t seem to find a an answer online.
I want to fetch the JSON result and have them returned with the class method below. But as you can see, by fetching the JSON in the block method, I don’t have a way to return them as result.
What is the correct way to to return them as NSDictionary from inside block method, or is there any other way to simplify this?
+ (NSDictionary *) fetchtPostsCount:(NSString *) count
page: (NSString *) page {
NSDictionary *requestParameter = [[NSDictionary alloc] initWithObjectsAndKeys:count, #"count", page, #"page", nil];
[[self sharedClient] GET:#"get_recent_posts"
parameters:requestParameter success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"%#", [responseObject objectForKey:#"posts"]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#", error);
}];
return nil;
}
AFNetworking executes requests on different thread, and calls the success or failure block when its done. Conceptually, you can imagine that your fetchPostsCount method will have already completed and returned its value by the time request is finished.
You almost certainly want it to work that way. Running the request on another thread and NOT waiting for it, allows your main UI thread to continue processing events and rendering screen updates. You don't want to get in the way of those things, or the user (and iOS) will get unhappy.
However, if you insist on waiting for the request to complete before returning, you could set a flag to monitor the status of the request, and then wait on that flag until the request is complete:
BOOL requestComplete = NO;
id requestResponseObject = nil;
[[self sharedClient] GET:#"get_recent_posts"
parameters:requestParameter success:^(NSURLSessionDataTask *task, id responseObject) {
requestResponseObject = responseObject;
requestComplete = YES;
NSLog(#"%#", [responseObject objectForKey:#"posts"]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
requestComplete = YES;
NSLog(#"%#", error);
}];
while (!requestComplete)
{
// Tie up the thread, doing nothing...
}
// Proceed

How do I use this block method?

I am a little bit confused about objective c programming with blocks.
for example Here is a method:
in the .h
- (void)downloadDataWithURLString:(NSString *)urlString
completionHandler:(void(^) (NSArray * response, NSError *error))completionHandler;
in the .m:
- (void)downloadedDataURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler {
// some things get done here. But what!?
}
My main questions is.... how do I implement this completion handler? What variables would be returned with the array and error? it is one area for the code but how do I tell it what to do when it is completed?
It's up to the caller to supply code to be run by the method (the body of the block). It's up to the implementor to invoke that code.
To start with a simple example, say the caller just wanted you to form an array with the urlString and call back, then you would do this:
- (void)downloadedDataURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler {
NSArray *callBackWithThis = #[urlString, #"Look ma, no hands"];
completionHandler(callBackWithThis, nil);
}
The caller would do this:
- (void)someMethodInTheSameClass {
// make an array
[self downloadedDataURLString:#"put me in an array"
completionHandler:^(NSArray *array, NSError *error) {
NSLog(#"called back with %#", array);
}];
}
The caller will log a two item array with #"put me in an array" and #"Look ma, no hands". In a more realistic example, say somebody asked you to call them back when you're finished downloading something:
- (void)downloadedDataURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler {
// imagine your caller wants you to do a GET from a web api
// stripped down, that would look like this
// build a request
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// run it asynch
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// imagine that the api answers a JSON array. parse it
NSError *parseError;
id parse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&parseError];
// here's the part you care about: the completionHandler can be called like a function. the code the caller supplies will be run
if (!parseError) {
completionHandler(parse, nil);
} else {
NSLog(#"json parse error, error is %#", parseError);
completionHandler(nil, parseError);
}
} else {
NSLog(#"error making request %#", error);
completionHandler(nil, error);
}
}];
// remember, this launches the request and returns right away
// you are calling the block later, after the request has finished
}
While I can't be entirely sure without seeing any more details about the method, or its exact implementation I suspect this: this methods creates a new background thread, retrieves the data from the server and converts the JSON/XML to an NSArray response. If an error occurred, the error object contains a pointer to the NSError. After doing that, the completion handler is called on the main thread. The completion handler is the block in which you can specify which code should be executed after attempting to retrieve the data.
Here is some sample code on how to call this method to get you started:
[self downloadDataWithURLString:#"http://www.google.com"
completionHandler:^(NSArray *response, NSError *error) {
if (! error) {
// Do something awesome with the 'response' array
} else {
NSLog(#"An error occured while downloading data: %#", error);
}
}];