My code:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager] ;
manager.requestSerializer = [AFJSONRequestSerializer serializerWithWritingOptions:NSJSONReadingAllowFragments];
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager POST:[_urlBase stringByAppendingPathComponent:_urlRequest]
parameters:paramDictionary
success:^(NSURLSessionDataTask *task, id responseObject){
dispatch_async(dispatch_get_main_queue(),^{
[self AFRequestFinished:responseObject];
});
}
failure:^(NSURLSessionDataTask *task, NSError *error){
NSLog(#"JSON ERROR PARAMETERS: %#", error);
}
];
I am using this POST request to send several types of data up to a server along with pictures. I am using something very similar for the GET request and it works fine. Whenever I run this code I get a EXC_BAD_ACCESS CODE=1 error on the following line of AFNetworking 2.0. The responseObject is 0x0:
responseObject = [self.responseSerializer responseObjectForResponse:task.response data:[NSData dataWithData:self.mutableData] error:&serializationError];
The above line of code is within the if/else method in:
- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
UPDATE
I ran instruments on the code, and it there is a zombie present. AFNetworking is trying to make a call to the NSError, but it has been deallocated. I believe this has arisen because the POST call initially succeeds, but there is still an error that is flagged. So it initially thinks there is no error and sets it to nil, but then tries to call for it in the error block of the POST.
If you're using the most recent version, you may be experiencing this known issue when the JSON serializer returns an error. You can work around this until a new release is made by:
removing the #autoreleasepool in the serializer, or
changing the scope of the error to outside the autorelease pool
(Both solutions are outlined in the issue linked above.)
On a side note, there's no need to dispatch to the main queue in the completion handler. AFNetworking guarantees that completion blocks are called on the main thread.
Related
In my app I use following methods to POST/GET data from a remote server.
postData = [self sendSynchronousRequest:request returningResponse:&response error:&error];
- (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
{
NSError __block *err = NULL;
NSData __block *data;
BOOL __block reqProcessed = false;
NSURLResponse __block *resp;
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable _data, NSURLResponse * _Nullable _response, NSError * _Nullable _error) {
resp = _response;
err = _error;
data = _data;
reqProcessed = true;
}] resume];
while (!reqProcessed) {
[NSThread sleepForTimeInterval:0];
}
*response = resp;
*error = err;
return data;
}
I have basic error handling for no network connectivity, and app directs users to no network connectivity viewController. But I would like to account for situations when for instance server is down or data format of api has changed, Just wondering how would I catch such errors and prevent the app from crashing.
A couple of general points:
Don't do requests synchronously. Pass in a block with code that should run when the request completes. There's no reason to tie up a thread waiting for a completion handler to run, and it is even worse if that thread is basically continuously running as yours is.
If you absolutely must do that (why?), at least use a semaphore and wait on the semaphore. That way, you aren't burning a CPU core continuously while you wait.
The way you avoid crashing is to sanity check the data you get back, e.g.
Check the length of the data and make sure you didn't get nil/empty response data.
Assuming the server sends a JSON response, wrap your NSJSONSerialization parsing in an #try block, and if it fails or throws an exception, handle the error in some useful way.
Generally assume that all data is invalid until proven otherwise. Check to make sure the data makes sense.
If you're in charge of the server side, consider passing an API version as an argument, and as you modify things in the future, make sure that incompatible changes occur only when responding to clients that request a newer API version (or, alternatively, send a response that tells the client that it needs to be upgraded, and then refuse to provide data).
I'm trying to save an object inside the AFNetworking block, but SOMETIMES it crashes with error: CoreData could not fulfill a fault for...
I've read that it's related when I try to manage an object data when it was deleted from the persistent store.
(https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdTroubleshooting.html)
How can I avoid this?
Example:
Line *line = [Line MR_createEntity];
[line setSync:#(SYNC_PROCESSING)];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://test.com/request" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
// CRASH
[line setSync:#(SYNC_SUCCESS)];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
} failure:nil];
You're trying to perform a background operation. The defaultContext is for the main thread only (it's not clearly documented, we're working on that). But what you want to do is perform your save in a context that will handle the background thread operations for you. That is, you want to use a NSManagedObjectContext that works on the background. In general try this:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
id myObject = [line MR_inContext:localContext];
[myObject setSync:#(value)];
}];
This will let MagicalRecord create the proper background context for your data to operate on when it tries to save data. MagicalRecord will set up much of the cruft needed in order to start using Core Data. Just let it do much of the work and you'll be on your way.
I have been asking and trying to understand how completion handlers work. Ive used quite a few and I've read many tutorials. i will post the one I use here, but I want to be able to create my own without using someone else's code as a reference.
I understand this completion handler where this caller method:
-(void)viewDidLoad{
[newSimpleCounter countToTenThousandAndReturnCompletionBLock:^(BOOL completed){
if(completed){
NSLog(#"Ten Thousands Counts Finished");
}
}];
}
and then in the called method:
-(void)countToTenThousandAndReturnCompletionBLock:(void (^)(BOOL))completed{
int x = 1;
while (x < 10001) {
NSLog(#"%i", x);
x++;
}
completed(YES);
}
Then I sorta came up with this one based on many SO posts:
- (void)viewDidLoad{
[self.spinner startAnimating];
[SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
self.usersArray = users;
[self.tableView reloadData];
}];
}
which will reload the tableview with the received data users after calling this method:
typedef void (^Handler)(NSArray *users);
+(void)fetchUsersWithCompletionHandler:(Handler)handler {
NSURL *url = [NSURL URLWithString:#"http://www.somewebservice.com"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[request setHTTPMethod: #"GET"];
**// We dispatch a queue to the background to execute the synchronous NSURLRequest**
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Perform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) { **// If an error returns, log it, otherwise log the response**
// Deal with your error
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"HTTP Error: %d %#", httpResponse.statusCode, error);
return;
}
NSLog(#"Error %#", error);
return;
}
**// So this line won't get processed until the response from the server is returned?**
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
NSArray *usersArray = [[NSArray alloc] init];
usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
// Finally when a response is received and this line is reached, handler refers to the block passed into this called method...so it dispatches back to the main queue and returns the usersArray
if (handler){
dispatch_sync(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
});
}
I can see it in the counter example, that the called method (with the passed block) will never exit the loop until it is done. Thus the 'completion' part actually depends on the code inside the called method, not the block passed into it?
In this case the 'completion' part depends on the fact that the call to NSURLRequest is synchronous. What if it was asynchronous? How would I be able to hold off calling the block until my data was populated by the NSURLResponse?
Your first example is correct and complete and the best way to understand completion blocks. There is no further magic to them. They do not automatically get executed ever. They are executed when some piece of code calls them.
As you note, in the latter example, it is easy to call the completion block at the right time because everything is synchronous. If it were asynchronous, then you need to store the block in an instance variable, and call it when the asynchronous operation completed. It is up to you to arrange to be informed when the operation completes (possibly using its completion handler).
Do be careful when you store a block in an ivar. One of your examples includes:
self.usersArray = users;
The call to self will cause the block to retain self (the calling object). This can easily create a retain loop. Typically, you need to take a weak reference to self like this:
- (void)viewDidLoad{
[self.spinner startAnimating];
__weak typeof(self) weakSelf = self;
[SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf setUsersArray:users];
[[strongSelf tableView] reloadData];
}
}];
}
This is a fairly pedantic version of the weakSelf/strongSelf pattern, and it could be done a little simpler in this case, but it demonstrates all the pieces you might need. You take a weak reference to self so that you don't create a retain loop. Then, in the completely block, you take a strong reference so that self so that it can't vanish on you in the middle of your block. Then you make sure that self actually still exists, and only then proceed. (Since messaging nil is legal, you could have skipped the strongSelf step in this particular case, and it would be the same.)
Your first example (countToTenThousandAndReturnCompletionBLock) is actually a synchronous method. A completion handler doesn't make much sense here: Alternatively, you could call that block immediately after the hypothetical method countToTenThousand (which is basically the same, just without the completion handler).
Your second example fetchUsersWithCompletionHandler: is an asynchronous method. However, it's actually quite suboptimal:
It should somehow signal the call-site that the request may have failed. That is, either provide an additional parameter to the completion handler, e.g. " NSError* error or us a single parameter id result. In the first case, either error or array is not nil, and in the second case, the single parameter result can be either an error object (is kind of NSError) or the actual result (is kind of NSArray).
In case your request fails, you miss to signal the error to the call-site.
There are code smells:
As a matter of fact, the underlying network code implemented by the system is asynchronous. However, the utilized convenient class method sendSynchronousRequest: is synchronous. That means, as an implementation detail of sendSynchronousRequest:, the calling thread is blocked until after the result of the network response is available. And this_blocking_ occupies a whole thread just for waiting. Creating a thread is quite costly, and just for this purpose is a waste. This is the first code smell. Yes, just using the convenient class method sendSynchronousRequest: is by itself bad programming praxis!
Then in your code, you make this synchronous request again asynchronous through dispatching it to a queue.
So, you are better off using an asynchronous method (e.g. sendAsynchronous...) for the network request, which presumable signals the completion via a completion handler. This completion handler then may invoke your completion handler parameter, taking care of whether you got an actual result or an error.
I attempting to query a hosted instance of elasticsearch (Searchbox.io) using AFNetworking. Unfortunately I can't seem to get the syntax working properly. For example, the following statement using curl works just fine and returns exactly what I would expect.
curl -XGET 'http://api.searchbox.io/api-key/<MYAPIKEY>/<MYINDEX/_search' -d '{"query" : {"query_string" : {"query" : "searchTerm"}}}'
I get back the docs from Searchbox.io that contain "searchTerm". Super. However, when I try to implement the same in AFNetworking, the Searchbox.io API returns a 500 error, noting "Parse Failure [Failed to parse source [na]]]; nested" Below is the call in my subclass of the AFHTTPClient.
NSDictionary *searchDict = #{#"query":#{#"query_string":#{#"query":#"searchTerm"}}};
[self getPath:[NSString stringWithFormat:#"_search"]
parameters:searchDict
success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([responseObject isKindOfClass:[NSDictionary class]]) {
NSLog(#"Search Results: \n\n %#", responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Search Error: %#", error);
}
}];
FYI - the subclassed AFHTTPClient is set with the following statements prior to this call. The subclassed AFHTTPClient client is working properly and the baseURL is set correctly.
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"application/json"];
To my newbie eyes, it looks like AFNetworking is handling the searchDict differently than the literal in the curl statement. To summarize, what would be the equivalent of the curl statement above, as implemented in AFNetworking? Thanks in advance
After digging around a bit more, I was able to get it to work by:
Pulling the latest rev of AFNetworking (1.3.1)
Changing the initWithBaseURL method of my AFHTTPClient subclass to the code below
Importing "AFJSONRequestOperation.h" into my AFHTTPClient subclass
Using the postPath:parameters:success:failure method to hit the API
[self setParameterEncoding:AFJSONParameterEncoding];
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"application/json"];
It is beyond me as to why the curl statement would be -XGET and the AFNetworking method would be a POST. Surely my misunderstanding.
Using iOS 5's new TWRequest API, I've ran into a brick wall related with block usage.
What I need to do is upon receiving a successful response to a first request, immediately fire another one. On the completion block of the second request, I then notify success or failure of the multi-step operation.
Here's roughly what I'm doing:
- (void)doRequests
{
TWRequest* firstRequest = [self createFirstRequest];
[firstRequest performRequestWithHandler:^(NSData* responseData,
NSHTTPURLResponse* response,
NSError* error) {
// Error handling hidden for the sake of brevity...
TWRequest* secondRequest = [self createSecondRequest];
[secondRequest performRequestWithHandler:^(NSData* a,
NSHTTPURLResponse* b,
NSError* c) {
// Notify of success or failure - never reaches this far
}];
}];
}
I am not retaining either of the requests or keeping a reference to them anywhere; it's just fire-and-forget.
However, when I run the app, it crashes with EXC_BAD_ACCESS on:
[secondRequest performRequestWithHandler:...];
It executes the first request just fine, but when I try to launch a second one with a handler, it crashes. What's wrong with that code?
The methods to create the requests are as simple as:
- (TWRequest*)createFirstRequest
{
NSString* target = #"https://api.twitter.com/1/statuses/home_timeline.json";
NSURL* url = [NSURL URLWithString:target];
TWRequest* request = [[TWRequest alloc]
initWithURL:url parameters:params
requestMethod:TWRequestMethodGET];
// _twitterAccount is the backing ivar for property 'twitterAccount',
// a strong & nonatomic property of type ACAccount*
request.account = _twitterAccount;
return request;
}
Make sure you're keeping a reference/retaining the ACAccountStore that owns the ACAccount you are using to sign the TWRequests.
If you don't, the ACAccount will become invalid and then you'll get EXC_BAD_ACCESS when trying to fire a TWRequest signed with it.
I'm not familiar with TW*, so consider this a wild guess ... try sending a heap-allocated block:
[firstRequest performRequestWithHandler:[^ (NSData *responseData, ...) {
...
} copy]];
To clarify, I think the block you're sending is heap-allocated, so while TW* might be retaining it, it won't make any difference if it has already gone out of scope.