Reputation: 811
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. E.g
@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 (i.e 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>);
}
Upvotes: 1
Views: 7229
Reputation: 1614
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!!
Upvotes: 2
Reputation: 90521
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;
Upvotes: 4