Search code examples
objective-cobjective-c-blocksobjective-c-category

Returning an object from inside block within category class method implementation


I have run into a certain problem with my implementation which I don't really know how to solve. Could You please advise.

I'm trying to implement an NSManagedObject category class Photo+Flickr.m with one class method +(void)photoWithFlickrData:inManagedObjectContext:

What I would like to do is download data from Flickr API using NSURLSessionDownloadTask and then create Photo object and insert this new created object into database (if it's not already there). This part works fine.

And at the end I would like to return new created (or object that was found in db) Photo object. And this is where I run into problem. Since I'm using category I can't use instance variables. I can't really find any good solution to get this Photo object from inside this completionHandler block.

My code:

@implementation Photo (Flickr)

+ (void)photoWithFlickrData:(NSDictionary *)photoDictionary
    inManagedObjectContext:(NSManagedObjectContext *)context

{
NSString *placeId = [photoDictionary valueForKeyPath:FLICKR_PHOTO_PLACE_ID];
NSURL *urlInfoAboutPlace = [FlickrFetcher URLforInformationAboutPlace:placeId];

NSURLRequest *request = [NSURLRequest requestWithURL:urlInfoAboutPlace];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDownloadTask *task =
[session downloadTaskWithRequest:request
               completionHandler:^(NSURL *localfile, NSURLResponse *response, NSError *error) {
                if(!error) {
                    NSData *json = [NSData dataWithContentsOfURL:localfile];
                    NSDictionary *flickrPlaceDictionary = [NSJSONSerialization JSONObjectWithData:json
                                                                                          options:0
                                                                                            error:NULL];
                    dispatch_async(dispatch_get_main_queue(), ^{

                        Photo *photo = nil;

                        // flickr photo unique id
                        NSString *uniqueId = [photoDictionary valueForKeyPath:FLICKR_PHOTO_ID];

                        NSFetchRequest *dbRequest = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
                        dbRequest.predicate = [NSPredicate predicateWithFormat:@"uniqueId = %@", uniqueId];

                        NSError *error;

                        NSArray *reqResults = [context executeFetchRequest:dbRequest error:&error];

                        if (!reqResults || error || [reqResults count] > 1) {
                            //handle error
                        } else if ([reqResults count]) {
                            //object found in db
                            NSLog(@"object found!");
                            photo = [reqResults firstObject];
                        } else {
                            //no object in db so create a new one
                            NSLog(@"object not found, creating new one");
                            photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo"
                                                                  inManagedObjectContext:context];
                            //set its properties
                            photo.uniqueId = uniqueId;
                            photo.title = [photoDictionary valueForKey:FLICKR_PHOTO_TITLE];
                            photo.region = [FlickrFetcher extractRegionNameFromPlaceInformation:flickrPlaceDictionary];

                            NSLog(@"title: %@", photo.title);
                            NSLog(@"ID: %@", photo.uniqueId);
                            NSLog(@"region: %@", photo.region);

                        }

                    });
                }
            }];
[task resume];

//how to get Photo *photo object???
//return photo;
}

I would really appreciate any suggestions on how to implement this.


Solution

  • Since you have async operations happening inside your blocks, you'll need to pass a completion handler (block) to your photoWithFlickrData:inManagedObjectContext: method and call it when you have valid photo data.

    You'll need to add a new parameter to your method so you can pass in the completion handler. I'd do something like this:

    + (void)photoWithFlickrData:(NSDictionary *)photoDictionary
         inManagedObjectContext:(NSManagedObjectContext *)context
          withCompletionHandler:(void(^)(Photo *photo))completionHandler
    

    Then, when you have a valid photo object, call completionHandler like so:

    completionHandler(photo);
    

    It looks like you'd want to put that at the very end of the block you're passing to dispatch_async:

    /* ... */
    dispatch_async(dispatch_get_main_queue(), ^{
        Photo *photo = nil;
    
        /* ... */
    
        completionHandler(photo);
    });
    /* ... */
    

    Then, you can call your method like so:

    [Photo photoWithFlickrData:photoDictionary
        inManagedObjectContext:context
         withCompletionHandler:^(Photo* photo) {
             /* use your valid photo object here */
         }
    ];