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.
Related
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 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.
I wrote a POST method that needs to return a jSON to the viewcontroller that called her.
If i add in the success block return _jsonDictionary; I will get this error:
Incompatible block pointer types sending 'id (^)(NSURLRequest *__strong, NSHTTPURLResponse *__strong, __strong id)' to parameter of type 'void (^)(NSURLRequest *__strong, NSHTTPURLResponse *__strong, __strong id)'
I guessing that because it's asynchronous, adding a return will force it to be synchronous but, I want all my POST methods for my app to be in one class so getting the data out of the JSON to variables that are declared across my app using something like valueForKey makes things a bit complicated for me.
Is that bad design?
-(NSDictionary *)getData
{
_jsonDictionary = [[NSDictionary alloc]init];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/getSomething",MainURL ]];
[AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:#"text/html"]];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:nil parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
_jsonDictionary = JSON;
NSLog(#"jsonDictionary: %#",_jsonDictionary);
}
failure:^(NSURLRequest *request , NSURLResponse *response , NSError *error , id JSON)
{
NSLog(#"request: %#",request);
NSLog(#"Failed: %#",[error localizedDescription]);
}];
[httpClient enqueueHTTPRequestOperation:operation];
}
Another question, why do I get this warning at the end of the above code: Control reaches end of non-void function even if i change the name of the method in .m and in .h to -(void )getData ??
If your main concern is to "get back" your data, you have three ways (maybe more, but I can only things of those three):
in your getData method, you can post a NSNotification that your viewController subscribed to before calling getData
if you are using (or plan to use) a dataManager as a singleton, your viewController can KVO on the #property of the dataManager
my favorite: in your calling viewController, you construct and pass a block to your getData method that will be called (with or w/o the result). This is exactly what you are doing when building the AFJSONRequestOperation in your example.
The error you are getting is correct. The block is not supposed to return anything, and your return _jsonDictionary; is trying just that.
What you need to do is update _jsonDictionary inside the success block (like you already do), and then invoke another function to fire up events that refresh you UI (something like calling [self.tableView refreshData]).
you're totally mixing asyn and sync ... there IS valid no _jsonDictionary that getData could return. the _jsonDictionary is only filled asynchronously when the completion blocks are called.
you need to proceed from there.... call another method for example
as for the error/warning you see ... getData is supposed to return something (NSDictionary*) or a void* (almost eqaul to an id)
to not return stuff, it is void only.
I've tried several different approaches to pull content from a google doc with AFNetworking.
I've modified the acceptable content types.
Used AFKiss functionality
Used regular AFHttp operation
Still can't get any content from the following piece of code. Should be reproducible if anyone has some thoughts.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://spreadsheets.google.com/feeds/worksheets/0AhNgJb3GRiT3dEFkYmJVQ1pUUXBpdWlOYjFBMUtpOEE/private/full"]];
AFXMLRequestOperation *operation = [AFXMLRequestOperation XMLParserRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser) {
NSLog(#"Finished");
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParse) {
NSLog(#"Failed");
}];
[operation start];
View that link in a browser without being logged in to a Google account. It doesn't have any content, so you won't be able to load xml regardless of how you try do so.
You'll need to add Google authentication in your app to view the spreadsheet it seems. Information can be found in the Authenticating Users in Mobile Apps guide.
I'm trying to use the AFNetworking UIImageView call to load images from a URL as shown below:
[self.image setImageWithURL:[NSURL URLWithString:feed.imageURL] placeholderImage: [UIImage imageNamed:#"logo"]];
The placeholder image always shows up, but the actual image from "feed.imageURL" never does. I've verified that the URL is actually correct. I even hardcoded it to make sure, and still nothing.
My basic app setup is a tab controller...and in viewDidLoad, I call a method "fetchFeed" which performs the HTTP request to gather my JSON data.
My request block looks like:
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
[self parseDictionary:JSON];
isLoading = NO;
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Error: %#", error);
[self showNetworkError];
isLoading = NO;
[self.tableView reloadData];
}];
operation.acceptableContentTypes = [NSSet setWithObjects:#"application/json", #"text/json", #"text/javascript", #"text/html", nil];
[queue addOperation:operation];
Turns out the server I was requesting the image from was sending content-type "image/jpg" and by default AFNetworking does not support this file type.
I changed the class method in AFImageRequestOperation to look like:
+ (NSSet *)defaultAcceptableContentTypes {
return [NSSet setWithObjects:#"image/tiff", #"image/jpeg", #"image/gif", #"image/png", #"image/ico", #"image/x-icon" #"image/bmp", #"image/x-bmp", #"image/x-xbitmap", #"image/x-win-bitmap", #"image/jpg", nil];
}
and it fixed my problem.
You can manage to accept what content-type you want with this library simply changing the request like this:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:yourURL];
[request addValue:#"image/*" forHTTPHeaderField:#"Accept"];
And call the AFNetworking method:
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
}];
This way you will be able to override the content-type without changing the library.
AFNetworking doesn't support image/jpg MIME TYPE by default.
You can support it without modifying the AFNetworking Library
[AFImageRequestOperation addAcceptableContentType:#"image/jpg"];
All operations that manipulate the UI must be performed on the main thread. So you may need to use 'performSelectorOnMainThread:' when reloading your tableview data in the completion block.
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO]
I had a similar problem but it turned out that I was passing a URL which contained spaces in it. When I properly encoded the URL using stringByAddingPercentEscapesUsingEncoding: the images now load.