I'm writing a gallery with images which can be loaded by url with AFNetworking.
In Init method of the ImageView object I call a function that send a request. Here:
- (void) loadWithUrl:(NSURL *)url
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:TimeOut];
[request setHTTPShouldHandleCookies:NO];
[request setHTTPShouldUsePipelining:YES];
__weak AOWImageView *safeSelf = self;
m_operation = [AFImageRequestOperation imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:^
(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
{
[safeSelf setImage:image];
}
failure:^
(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
{
[safeSelf setNoImageLabelOpaque];
}];
[m_operation start];
}
If the ImageView is outside the visible part of the screen - (void) dealloc is called. I cancel operation loading image in this method so: [m_operation cancel];. I guess that the operations are not canceled because the memory is increasing and isn't released.
I think that there is retain cycle. I want to understand how to write it right. Thanks.
I don't see a retain cycle in the code snippet you posted, so if you are leaking memory, it may be due to another part of your app's code...
Regarding "best practices" for AFNetworking and loading images-
AFNetworking has a built in category on UIImageView that has convenience methods for setting an an image view's image via a URL.
See UIImageView+AFNetworking, specifically setImageWithURL: method and related. This also has the advantage of keeping a cache (so you don't have to fetch images again if requested multiple times), which AFAIK, doesn't appear to done by AFImageRequestOperation.
Related
I am developing an app which use UITableView,which contains cell having UIImageView of size 320 X 200.
This images comes from the web url and store to my app folder.Then i am showing this image to user.
For that i am using "UIImageView+AFNetworking.h" class's below method.
- (void)setImageWithURL:(NSString *)url
placeholderImageName:(NSString *)placeholderImage
saveFilePath:(NSString *) filePath
IndexPath:(NSIndexPath *)indexPath
success:(void (^)(NSIndexPath *indexPath, UIImage *image))success
failure:(void (^)(NSIndexPath *indexPath, NSError *error))failure {
NSLog(#"DOWNLOAD IMG URL :: %#", url);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
[request setHTTPShouldHandleCookies:NO];
[request setHTTPShouldUsePipelining:YES];
[request addValue:#"image/*" forHTTPHeaderField:#"Accept"];
[self setImageWithURLRequest:request
placeholderImage:placeholderImage == nil? nil : [UIImage imageNamed:placeholderImage]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[Storage SaveImage:image WithFileName:filePath];
if (success)
success(indexPath, image);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
if (failure)
failure(indexPath, error);
}
];
}
when my all images downloaded to my local folder after that my tableview stucks while showing images.
It might be possible that the downloading happens in sync with the main thread and is blocking it until the download has finished.
I use SDWebImage in my projects for this. It work's really really well.
EDIT:
OK, AFNetworking is doing all the work asynchronously and also caches all the images. See the documentation.
If you don't need your success and failure blocks, you could go away with this.
- (void)setImageWithURL:(NSString *)url
placeholderImageName:(NSString *)placeholderImage {
NSURL *url = [NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
UIImage *pImage = placeholderImage == nil? nil : [UIImage imageNamed:placeholderImage];
[self setImageWithURL:url placeholderImage:pImage];
}
Or maybe the image is extremely large (that's independent of the UIImageView size).
Can we see your code?
Do you load them manualy from the file system? Probably is that, because you do that in the main thread.
Try this in the code that loads the image:
NSLog(#"Is this the main thread: %d", [NSThread isMainThread]);
If you see 1, that's the problem. You can solve it using GCD (remember that once the image is loaded, move it to the main thread)
Use NSOperationQueue for downloading image. While adding new item in NSOperationQueue, first check if it already added or not.
After NSOperation complete, that image will save into local.
Always load image from locally. If image not present in local then display some place holder.
Hope it helps.
Regards,
Punita
I'm setting in a ViewController a NSString property by fetching a JSON table and then in a different ViewController I want to get that same property.
What is happening is when I'm trying to get the property this is nil.
I know what is the problem, I'm accessing the property in the main thread while the JSON fetching is still in progress in another thread.
I'm using the AFNETWORKING 2.0 framework to access the JSON table.
How can I wait for the property set and then use it?
I really appreciate any help you can provide.
You can do this in different ways, you can post notification from AFnetworkingJSON operation success callback like this. And observer that notification where you want to access that property. You can also pass a completionHandler to the method which can be call from success or failure callbacks.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"link"]];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// Post notification from here
// call completion handler if you have any
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response,
}
];
[operation start];
I'm using AFNetworking, and I encountered a problem when I was calling a POST with JSON. I'm uploading several images in base64, and I noticed that even if I uploaded everything, _convertJSONString, or something related, is still in memory. Should be the JSON conversion applied by AFNetworking when I created the NSURLRequest, that actually should be released. I don't know if I'm missing something, but it's a weird behavior.
This is an example of how I'm implementing the request inside my client:
NSMutableURLRequest *request = [self requestWithMethod:#"POST" path:path parameters:params];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
completionBlock(JSON, response, nil);
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
completionBlock(nil, response, error);
}];
[self enqueueHTTPRequestOperation:operation];
And this is the line where Instrument says the allocation comes from:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wassign-enum"
[request setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:0 error:&error]];
#pragma clang diagnostic pop
that's part of:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
path:(NSString *)path
parameters:(NSDictionary *)parameters
in AFHTTPClient.m
Thank you in advance for any help, or solution.
FIRST WORKAROUND
I've found that the NSURLRequest's content inside AFURLConnectionOperation is not completely released when the operation is finished, and this causes a leak.
Setting self.request = nil inside - (void)finish method solves the problem.
This is just a workaround, but I cannot currently find another way.
This is not an issue. Objects may be relatively long-lived in memory, without being a leak.
Don't worry about it.
When I start a request with the NSURLRequestReturnCacheDataElseLoad policy I expect to get the result from the NSURLCache if any, no matter how old it is. However, the system always tries to reach the server the request points to and returns an error if the server doesn't answer.
I use the UIImageView category of AFNetworking for the request.
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:60.0];
__weak __typeof(self) weakSelf = self;
NSLog(#"[%#] %#", request.URL, [[NSURLCache sharedURLCache] cachedResponseForRequest:request]); // this returns an instance of NSCachedURLResponse!
[self setImageWithURLRequest:request placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
__typeof(weakSelf) self = weakSelf;
self.image = image;
} failure:NULL];
This will not set the image even if asking the NSURLCache directly will return a valid NSCachedURLResponse.
The app is running on iOS6 only, so there should be no problems with on-disk cache as far as I know?!
Any help is appreciated! Thanks!
This is a know issue. Please refer this discussion on AFNetworking github page for a workaround.
I'm currently doing this when populating core data from a JSON file:
NSString *urlString = [value objectForKey:#"url"];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *dataResponse = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
[managedObject setValue:dataResponse forKey:#"image"];
Is there a better (asynchronous) way to do this with AFNetworking? What is the best method for this case? Does it have to be synchronous because we're dealing with CoreData?
UPDATE: Trying this now:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
[managedObject setValue:data forKey:#"image"];
}];
For some reason when I access the managed object later, the image attribute is always null, even though *data above is not null in the completion handler. The image gets saved fine in the synchronous method. What am I missing?
NSURLConnection can deal with async too.
The method that you can use is (iOS >= 5) is
+ sendAsynchronousRequest:queue:completionHandler:
If you need to target iOS < 5 then use the delegate pattern for NSURLConnection. A good wrapper for this can be found in NSURLConnection and grand central dispatch.
About Core Data, I would say it depends. If data you need to store is cheap, do it in the main thread. On the contrary you have three different ways to do it:
(1) use new Core Data queue-based API (iOS >= 5)
(2) kick off a NSOperation within a NSOperationQueue and do the long work in background
(3) use GDC
Pay attention to Core Data constraints (threads constraints) when you deal with (2) or (3).
Hope that helps.
P.S. If you want to know something else let me know.
There's a sendAsynchronousRequest:queue:completionHandler: message of NSURLConnection.