I'm trying to get an image asynchronously from an url with AFNetworking 2.0
My problem is that neither success nor failure are called
I've checked my URL in a browser so the problem is not here
UIImageView *imgv = [[UIImageView alloc] init];
[imgv setImageWithURLRequest:myURL
placeholderImage:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
/*some code*/
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
/*some code*/
];
When I look deeper inside setImageWithUrlRequest:placeholderImage:success:failure
__weak __typeof(self)weakSelf = self;
self.af_imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
self.af_imageRequestOperation.responseSerializer = self.imageResponseSerializer;
[self.af_imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[urlRequest URL] isEqual:[strongSelf.af_imageRequestOperation.request URL]]) {
if (success) {
success(urlRequest, operation.response, responseObject);
} else if (responseObject) {
strongSelf.image = responseObject;
}
if (operation == strongSelf.af_imageRequestOperation){
strongSelf.af_imageRequestOperation = nil;
}
}
[[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[urlRequest URL] isEqual:[strongSelf.af_imageRequestOperation.request URL]]) {
if (failure) {
failure(urlRequest, operation.response, error);
}
if (operation == strongSelf.af_imageRequestOperation){
strongSelf.af_imageRequestOperation = nil;
}
}
}];
My code goes trought [self.af_imageRequestOperation setCompletionBlockWithSuccess:^( AFHTTPRequestOperation *operation, id responseObject) {
with success but i've noticed that __strong __typeof(weakSelf)strongSelf = weakSelf; are both nil
Any idea?
Your imgv is never retained so it will be released after current scope so in your block weakSelf will be release when image is successfuly downloaded so thats why you are getting nil.
Related
NSItemProviderCompletionHandler urlHandler = ^(NSURL *url, NSError *error) {
my_url = url.absoluteString;
};
if ([itemProvider hasItemConformingToTypeIdentifier:#"public.url"]) {
[itemProvider loadItemForTypeIdentifier:#"public.url" options:nil completionHandler:urlHandler];
}
[self finishSelectingPost:itemProvider data:data];
how do I run the last line after my_url value changed ? thanks ~!
It is not clear what is my_url and where is data, but supposing they are valid in the context of provided code snapshot, the handler could be as following
__weak __typeof(self) weakSelf = self;
NSItemProviderCompletionHandler urlHandler = ^(NSURL *url, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
my_url = url.absoluteString;
[weakSelf finishSelectingPost:itemProvider data:data];
}
};
I have a loop set up that downloads a series a images which I will later use for to animate using the animationImages property of UIImageView. I would like to know when all the blocks inside my loops have finished executing so I could begin the animation, and was wondering how I may be able to tell when they are finished completing? Thanks!
for (PFObject *pictureObject in objects){
PFFile *imageFile = [pictureObject objectForKey:#"image"];
NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
[tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Error %#", error);
}];
}
//When I know all the blocks have finished downloading, I will then to animate the downloaded images.
Edit: having issue with Error -999
I am encountering the following issue when executing the code in the provided answer: Domain=NSURLErrorDomain Code=-999 "The operation couldn’t be completed. (NSURLErrorDomain error -999.)"
A quick search reveals that Error -999 means "another request is made before the previous request is completed" ... which is certainly the case here since I am making several requests in quick succession. The recommended fix suggested here didn't work for me as it will only successfully download one UIImage (the last one requested) , with the previous ones failing. I was wondering if there is workaround here or in AFNetworking that I ought to consider? Thanks!
Edit 2: working code based on #David's solution
for (PFObject *pictureObject in objects){
PFFile *imageFile = [pictureObject objectForKey:#"image"];
NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:imageRequest];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
dispatch_group_enter(group);
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Response: %#", responseObject);
UIImage *retrivedImage = (UIImage *)responseObject;
[self.downloadedUIImages addObject:retrivedImage];
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Image error: %#", error);
dispatch_group_leave(group);
}];
[requestOperation start];
counter ++;
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"Horray everything has completed");
NSLog(#"What is here %#", self.downloadedUIImages);
NSLog(#"Done");
});
Create a dispatch group, in the for loop enter the group, in the completion block leave the group. Then you can use dispatch_group_notify to find out when all blocks have completed:
dispatch_group_t group = dispatch_group_create();
for (PFObject *pictureObject in objects){
PFFile *imageFile = [pictureObject objectForKey:#"image"];
NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
dispatch_group_enter(group);
[tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
dispatch_group_leave(group);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Error %#", error);
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// do your completion stuff here
});
Count how many you've completed. The challenging part is making it thread safe. I recommend creating an atomic counter class for that.
Generic solution!
+ (void)runBlocksInParallel:(NSArray *)blocks completion:(CompletionBlock)completion {
AtomicCounter *completionCounter = [[AtomicCounter alloc] initWithValue:blocks.count];
for (AsyncBlock block in blocks) {
block(^{
if ([completionCounter decrementAndGet] == 0) {
if (completion) completion();
}
});
}
if (blocks.count == 0) {
if (completion) completion();
}
}
NSMutableArray *asyncBlocks = [NSMutableArray array];
for (PFObject *pictureObject in objects){
[asyncBlocks addObject:^(CompletionBlock completion) {
PFFile *imageFile = [pictureObject objectForKey:#"image"];
NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
[tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Error %#", error);
} completion:completion];
}];
}
[BlockRunner runBlocksInParallel:[asyncBlocks copy] completion:^{
//Do your final completion here!
}];
Set up a property and initialize it to the number of cycles - objects.count. In the completion of the block, lower the number down. When you reach zero, you are done.
for (PFObject *pictureObject in objects){
PFFile *imageFile = [pictureObject objectForKey:#"image"];
NSURL *imageFileURL = [[NSURL alloc] initWithString:imageFile.url];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageFileURL];
[tokenImageView setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[self.downloadedUIImages addObject:image]; //This is a mutableArray that will later be set to an UIImageView's animnationImages
if([[objects lastObject] isEqual:pictureObject]) {
[self animateImages];
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Error %#", error);
if([[objects lastObject] isEqual:pictureObject]) {
[self animateImages];
}
}];
}
- (void)animateImages {
//do animation here.
}
I am trying to retrieve data using from a JSON request using AFJSONRequestOperation.
On success I am able to successfully retrieve the data but unable to complete the request and forward the data further for processing.
Following is my code
-(void) retrieveBrandList:(void (^)(NSArray *brandList))success failure:(void (^)(NSError *error))failure
{
//__block NSArray *brandList =[[NSArray alloc] init];
NSString *BrandListURL= http://127.0.0.1:8888/know/rest/brand
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSLog(#"Brand List URL = %#", BrandListURL);
AFJSONRequestOperation *operation =[AFJSONRequestOperation
JSONRequestOperationWithRequest: request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id responseObject)
{
NSLog(#"%#", responseObject);
brandList = [self successBandList:responseObject]; // parsing the JSON response in separate method (success block code)
if (success)
success(brandList);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id responseObject)
{
message:[NSString stringWithFormat:#"%#",error];
if (failure)
failure(error);
}];
[operation start];
[operation waitUntilFinished];
}
Following is the data manager to retrieve data.
- (NSArray *)getBrandList
{
#try
{
[brand retrieveBrandList:^(NSArray *brandList)
{
brands = brandList;
}
failure:^(NSError *error) {
}];
NSLog(#"Retriving Brand list completed");
return brands;
}
#catch (NSException * e) {
NSLog(#"Exception: %# , Error while getting the brand list", e);
}
return NULL;
}
How do i complete the operation and use or store the results for further processing in some other method?
id jsonObject = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:&error];
if ([jsonObject isKindOfClass:[NSDictionary class]]) {
self.jsonDictionary = jsonObject;
}
and you can check for other option too but this worked for me
How do you download images in order with AFNetworking? An by "in order", I also mean executing the success blocks in order.
Initially I thought it would be enough to use a NSOperationQueue and set each AFImageRequestOperation as a dependency of the next one. Like this:
- (void) downloadImages
{
{ // Reset
[_downloadQueue cancelAllOperations];
_downloadQueue = [[NSOperationQueue alloc] init];
_images = [NSMutableArray array];
}
AFImageRequestOperation *previousOperation = nil;
for (NSInteger i = 0; i < _imageURLs.count; i++) {
NSURL *URL = [_imageURLs objectAtIndex:i];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
AFImageRequestOperation *operation = [AFImageRequestOperation
imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[_images addObject:image];
NSLog(#"%d", i);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];
if (previousOperation) {
[operation addDependency:previousOperation];
}
previousOperation = operation;
[_downloadQueue addOperation:operation];
}
}
This prints i in order when the images are downloaded. However, when the requests are already cached, the success blocks are processed out of order. I suspect this is a NSOperation limitation, not AFNetworking.
Am I missing something?
As a workaround, I store the images in a dictionary and process them in order each time a request succeeds. Like this:
- (void) downloadImages
{
{ // Reset
[_downloadQueue cancelAllOperations];
_downloadQueue = [[NSOperationQueue alloc] init];
_images = [NSMutableArray array];
_imageDictionary = [NSMutableDictionary dictionary];
}
AFImageRequestOperation *previousOperation = nil;
for (NSInteger i = 0; i < _imageURLs.count; i++) {
NSURL *URL = [_imageURLs objectAtIndex:i];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
AFImageRequestOperation *operation = [AFImageRequestOperation
imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[_imageDictionary setObject:image forKey:#(i)];
[self processImages];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];
if (previousOperation) {
[operation addDependency:previousOperation];
}
previousOperation = operation;
[_downloadQueue addOperation:operation];
}
}
- (void) processImages
{
for (NSInteger i = _images.count; i < _imageURLs.count; i++) {
UIImage *image = [_imageDictionary objectForKey:#(i)];
if (!image) break;
[_images addObject:image];
NSLog(#"%d", i);
}
}
This always prints i in order.
Your solution will work fine, here is another way to do it:
For the "perfect" UX you should issue all operations in parallel and process images by order as they come (don't wait if you don't have to).
(error handling is done differently here)
You could try this (untested, and you can better design the model [don't just use arrays like this]):
- (void) processImage:(UIImage*)image
{
//do something with the image or just [_images addObject:image]
}
- (void) downloadImages
{
{ // Reset
[_downloadQueue cancelAllOperations];
_downloadQueue = [[NSOperationQueue alloc] init];
}
__block NSMutableArray* queue = [[NSMutableArray alloc] initWithCapacity:[_imageURLs count]];
for (NSURL* url in _imageURLs) {
__block NSLock* lock = [[NSLock alloc] init];
__block NSMutableArray* container = [NSMutableArray new];
[lock lock];
[queue addObject:#[lock,container,url]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
void(^compBlock)(NSURLRequest *request,
NSHTTPURLResponse *response,
UIImage *image) = ^(NSURLRequest *request,
NSHTTPURLResponse *response,
UIImage *image)
{
[container addObject:image];
[lock unlock];
};
NSOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:compBlock
failure:compBlock];
[_downloadQueue addOperation:operation];
}
__block __weak id weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSArray* arr in queue) {
NSLock* lock = arr[0];
[lock lock];
NSMutableArray* container = arr[1];
if ([container count]) {
[weakSelf processImage:container[0]]; //might want to call this on main thread
} else {
//error on url = arr[2]
}
[lock unlock];
}
});
}
Neither the success or failure handler appears to be called when setImageWithURLRequest: is called on my UIImage object. SetImageWithURL does not set my image either. If I use setImageWithURL: placeholder: the placeholder is set but the image from the url never gets set.
Pardon my not posting code sooner. I have tried to use two methods (with AFNetworking) to asynchronously set my image views:
[_imageView setImageWithURL:[NSURL URLWithString:#"path/to/file"]];
[_imageView setImageWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.example.com/path/to/image.jpg"]] placeholderImage:[UIImage imageNamed:#"your_image_here.jpg"] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
NSLog(#"Your image request succeeded!");
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Your image request failed...");
}];
I have tried having these triggered in viewDidLoad but the image view never loads.
After looking through the actual function declaration of setImageWithURLRequest: and it appears that neither the success or failure block is executed when I call it. I've placed NSLogs in the code to give you guys an idea of what happens:
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
{
NSLog(#"Log shows");
[self cancelImageRequestOperation];
NSLog(#"Log shows");
UIImage *cachedImage = [[[self class] af_sharedImageCache] cachedImageForRequest:urlRequest];
NSLog(#"Log shows");
if (cachedImage) {
NSLog(#"Log does not show");
self.image = cachedImage;
self.af_imageRequestOperation = nil;
if (success) {
success(nil, nil, cachedImage);
NSLog(#"Log does not show");
}
} else {
NSLog(#"Log shows");
self.image = placeholderImage;
AFImageRequestOperation *requestOperation = [[AFImageRequestOperation alloc] initWithRequest:urlRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([[urlRequest URL] isEqual:[[self.af_imageRequestOperation request] URL]]) {
self.image = responseObject;
self.af_imageRequestOperation = nil;
NSLog(#"Log does not show");
}
if (success) {
success(operation.request, operation.response, responseObject);
NSLog(#"Log does not show");
}
[[[self class] af_sharedImageCache] cacheImage:responseObject forRequest:urlRequest];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if ([[urlRequest URL] isEqual:[[self.af_imageRequestOperation request] URL]]) {
self.af_imageRequestOperation = nil;
NSLog(#"Log does not show");
}
if (failure) {
failure(operation.request, operation.response, error);
NSLog(#"Log does not show");
}
}];
self.af_imageRequestOperation = requestOperation;
[[[self class] af_sharedImageRequestOperationQueue] addOperation:self.af_imageRequestOperation];
setImageWithURL: is a UIImageView category and cannot be used for setting a UIImage only used on a UIImageView.
[imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:#"placeHolderImage"]];
Here's the documentation: http://engineering.gowalla.com/AFNetworking/Categories/UIImageView(AFNetworking).html
Thanks for the help guys but like everyone on here noticed, there was no issue with my code. The issue was actually with regard to ARC. I had it turned off in my AFNetworking files. After turning it on, everything worked just fine.