简体   繁体   中英

Objective-C: How to give a completion block to a method that uses a result from the method execution

Object X needs to download an image. It has URL of the image (could be file of network). I have a common imageHandler class that can take a URL and get the imageData. I want this method, to be able to take a completion block to customize what to do with the downloaded image.

I am familiar with how to do it is using delegate pattern. Eg

@protocol ImageHandler: NSObject
-(void) getImageFromURL:(NSURL *)url forDelegate:(id <ImageRequestor>)delegate;
@end

@protocol ImageRequestor: NSObject
-(void) image:(UIImage *) image RetrievedForURL:(NSURL *)url withError:(NSError *)error;
@end

So, basically objectX class getImageFromURL:delegate method with delegate as self. It conforms to the ImageRequestor protocol.

The ImageHandler object, stores url to delegate mapping in a hash table. After it is done, calls image:RetrievedForURL:withError: on the delegate. And in this method I act on the image and error.

How do I achieve the same effect using completion blocks where I pass on the "what i want to do with the retrieved image" as a piece of code.

One approach I see is as below. But looks like it requires the ImageHandler method implementation call the completion with specific arguments. Is that the accepted way to implement this (ie the completion handler relies on the method receiving it to call it with correct arguments)?

@protocol ImageHandler: NSObject
-(void) getImageFromURL:(NSURL *)url withCompletionHandler:(void (^)(UIImage *image, NSError * err))completionBlock;
@end

The implementation of getImageFromURL:(NSURL *)url withCompletionHandler:(void (^)(UIImage *image, NSError * err))completionBlock looks like

getImageFromURL:(NSURL *)url withCompletionHandler:(void (^)(UIImage *image, NSError * err))completionBlock
{
 //Get UIImage from URL, store it UIIMage ObjectX
 //if error happens, store it in NSError objectY, else it is nil
 //call completion handler 
 completionBlock(<UIIMage objectX>,<NSError objectY>);
}

Yes, what you've shown is the typical way of doing this. Look at Apple's own APIs to see that they do it the same way. For example, -[NSURLSession dataTaskWithURL:completionHandler:] , whose declaration is:

- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url 
                        completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;

Write the following inside your ImageRequestor.h file:

#import <Foundation/Foundation.h>

// Here you define your custom response block with the desired parameters
// That in this case are UIImage and NSError
typedef void (^ResponseBlock)(UIImage *image, NSError *error);

@interface ImageRequestor : NSObject

// Here you define the method that use your custom response block
- (void)getImageFromURL:(NSURL *)url withCompletionHandler:(ResponseBlock)completionBlock;

@end

Almost done, now open your ImageRequestor.m file and add the following:

#import "ImageRequestor.h"

@implementation ImageRequestor

- (void)getImageFromURL:(NSURL *)url withCompletionHandler:(ResponseBlock)completionBlock {
  // Do all your stuff to get the image and the error
  // And set them into your completion block
  // if there is no error that should be nil

  // completionBlock(YourFetchedImage, YourFetchedError);
  // completionBlock(nil, YourFetchedError);
  completionBlock(YourFetchedImage, nil);
}

@end

And finally you can call it with something like this:

ImageRequestor *imageRequestor = [[ImageRequestor alloc] init];
[imageRequestor getImageFromURL:yourURL withCompletionHandler:^(UIImage *image, NSError *error) {
  // Your implementation in here
}];

This kind of stuffs looks a lot way better in a singleton class :]

Hope it helps, Good Luck!!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM