I am currently struggeling why the following code doesnt log my used NSString.
-(BOOL)login:(NSString *)email withPassword:(NSString *)password error:(NSError *__autoreleasing *)error
{
__block BOOL success = NO;
if (*error)
return success;
__block NSString *domain = #"de.FranzBusch.Searchlight.ErrorDomain";
__block NSError *localerror = nil;
//HTTP Request
NSURL *url = [NSURL URLWithString:#"Wrong URL to test the failure block"];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:url];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
email, #"email",
password, #"password", nil];
NSMutableURLRequest *request = [client requestWithMethod:#"POST" path:#"" parameters:params];
AFKissXMLRequestOperation *operation = [AFKissXMLRequestOperation XMLDocumentRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, DDXMLDocument *XMLDocument)
{
}failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *HTTPError, DDXMLDocument *XMLDocument)
{
localerror = [NSError errorWithDomain:domain code:-101 userInfo:[self generateErrorDictionary:#"HTTPError"]];
success = NO;
}];
[operation start];
NSLog(#"test %#", [localerror domain]);
return success;
}
If I try to log the domain inside the failure block I get the right one. But outside the scope the block variable isnt modified. Ans hints on what I understood wrong are appreciated.
The problem is that the success and failure blocks are called long after your `login:withPassword:error: method has completed and returned because the operation is done in the background.
When your NSLog statement is reached, the operation isn't finished yet and neither block has been executed yet. So localerror is still nil and success is still NO.
NSLog domain called out of block will be called earlier because it will be on the main thread, and NSLog in failure block fire in the new thread, and after the response from server. So return success also return not set success (No in your case).
Related
I'm starting to learn Objective-C for iOS Development, and a got a issue that is driving me crazy.
All that I want is to do a request, retrieve e JSON and then set this JSON into an instance property.
-(NSArray *) retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Communication Error: %#", error);
}];
[operation start];
return _JSONObjectsCollection;
}
-(void) setJSONOBjectsCollectionAttribute: (NSArray *) arrayWithCollection
{
NSLog(#"Outside Method %#", arrayWithCollection);
self.JSONObjectsCollection = arrayWithCollection;
}
However, my self.JSONObjectsCollection property are ok inside the block, but outside is always null.
Can you help me guys ?
It's because the setting of JSONObjectsCollection happens asynchronously. So your method is returning JSONObjectsCollection before it is set.
Thus, it might look like:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
// do here whatever you want to do now that you have your array, e.g.
//
// [self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Communication Error: %#", error);
}];
[operation start];
}
Note, retrieveAtEndpoint now has a void return type, but in the completion block, I'm invoking whatever code you want to perform once the JSON objects collection has been updated.
If this is a method inside your model object, but you want to provide an interface by which the view controller can supply a block of code that should be executed upon successful retrieval of the JSON, use a completion block:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(void (^)(NSError *error))completion
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
if (completion)
{
completion(nil);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
if (completion)
{
completion(error);
}
}];
[operation start];
}
Or, if you want to simplify your use of a block parameter, you can define a type for the completion block at the start of your model object's .h file (before the #interface block):
typedef void (^RetrievalCompleteBlock)(NSError *);
And then the method is simplified a bit:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(RetrievalCompleteBlock)completion
{
// the code here is like it is above
}
Anyway, regardless of whether you use the typedef or not, the view controller could do something like:
ModelObject *object = ...
NSString *rootNode = ...
[object retrieveAtEndpoint:url withRootNode:rootNode completion:^(NSError *error) {
if (error)
{
// handle the error any way you want, such as
NSLog(#"%s: retrieveAtEndPoint error: %#", __FUNCTION__, error);
}
else
{
// do whatever you want upon successful retrieval of the JSON here
}
}];
The details here will vary based upon how your view controller is accessing the model object, knows that the root node should be, etc. I often will include another parameter to my completion block which is the data being retrieved, but given that you updated your model object and can access it that way, perhaps that's not necessary. I simply don't have enough details about your implementation to know what is right here, so I kept my implementation as minimalist as possible.
But hopefully this illustrates the idea. Give your retrieveAtEndpoint method a completion block parameter, which lets the view controller specify what it wants to do upon completion (or failure) of the communication with the server.
Before someone spams the thread telling me its not best practice, there is valid reason for my not using an async method in this case.
I am trying to force my AFNetworking library to run in a synchronous mode for certain method calls (its been factoried).
Here is my (hacked) snippet:
NSURL *url = [NSURL URLWithString:_apiBaseUrl];
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:url];
NSMutableURLRequest *request = [client requestWithMethod:#"POST" path:path parameters:nil];
NSDictionary *requestDict = [NSDictionary dictionaryWithObjects:#[#"2.0", path, params, [NSNumber numberWithInt:(int)[[NSDate date] timeIntervalSince1970]]] forKeys:#[#"jsonrpc", #"method", #"params", #"id"]];
NSString *requestBody = [requestDict JSONString];
NSLog(#"requestBody: %#", requestBody);
// Set the request body
[request setHTTPBody:[requestBody dataUsingEncoding:NSASCIIStringEncoding]];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// Do something with the success (never reached)
successBlock(request, response, JSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
failureBlock(request, response, error, JSON);
}];
if (queue == 1) {
[self.operationArray addObject:operation];
}
else {
[operation waitUntilFinished];
successBlock(nil, nil, nil);
//[operation start];
}
When the method is ran and the queue param forces the method to waitUntilFinished the success block is never reached nor is the next line. If I use the start method it works fine.
I have some code that makes a http call to a json based webservice. That's working fine but I tried to move the code to it's own class and I have a slight hang up. When I call the method, the main thread just continues to the next command without wait for a response from my method.
Here's some code from the main part of the program
[newcall run];
NSLog(#"%#",[newcall status]);
NSArray *resultarray= [newcall returndata];
for (NSString *element in resultarray) {
NSLog(#"%#",element);
}
My Header
#import "AFHTTPClient.h"
#interface jsoncall : AFHTTPClient
{
NSString* Date;
NSString* apps;
NSString* data1;
NSURL* url;
NSString* Path;
NSArray* returndata;
NSString* status;
}
-(void) setApp: (NSString *)input;
-(void) setData: (NSString *)input;
-(void) setURL: (NSString *)input;
-(void) setPath: (NSString *)input;
-(int) run;
-(NSArray *) returndata;
-(NSString *) status;
#end
My run method
-(int) run
{
__block int success;
NSDictionary* jsonDictionary=[NSDictionary dictionaryWithObject: data1 forKey:#"data"];
NSString* jsonString = [jsonDictionary JSONRepresentation];
AFHTTPClient *httpClient=[[AFHTTPClient alloc] initWithBaseURL:url];
NSDictionary *params =[NSDictionary dictionaryWithObjectsAndKeys:
apps,#"app",
jsonString,#"smpdata",nil];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:Path parameters:params];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSArray *dataarray=[JSON valueForKey:#"Data"];
status= [NSString stringWithFormat:#"%#",[JSON valueForKeyPath:#"Status"]];
NSLog(#"%#",status);
returndata= dataarray;
success=1;
NSLog(#"Success: Made it here");
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error,id JSON)
{
success=0;
NSLog(#"Error: Made it here");
}
];
[operation start];
[operation waitUntilFinished];
return success;
}
The method I used was Asychronous and as such I would have to have chained the next set of processing by calling it from the completion block. When the final app is done, we may do that but there's also this solution.
Waiting for completion block to complete in an AFNetworking request
By switching AFJSONRequestOperation to NSURLConnection, I am able to use Sychronous Mode which means the request has to complete before the thread moves on.
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:Path parameters:params];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error) {
NSLog(#"Error: %#",error);
}
else {
id JSON = AFJSONDecode(data, &error);
NSArray *dataarray=[JSON valueForKey:#"Data"];
status= [NSString stringWithFormat:#"%#",[JSON valueForKeyPath:#"Status"]];
NSLog(#"%#",status);
returndata= dataarray;
}
For our needs Synchronous mode should work fine for now.
However
You should not run a Synchronous web call on an UI thread because it will block and make your UI unresponsive. That means you need to put your call to this class in it's own thread with any other code that relies on it. I believe you want to check out use of NSOperation but I'm a Objective-c noob so I won't be adding an example here.
I have this function that will get xml through a request operation:
-(id)xmlRequest:(NSString *)xmlurl
{
AFKissXMLRequestOperation *operation = [AFKissXMLRequestOperation XMLDocumentRequestOperationWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:xmlurl]] success:^(NSURLRequest *request, NSHTTPURLResponse *response, DDXMLDocument *XMLDocument) {
NSLog(#"XMLDocument: %#", XMLDocument);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, DDXMLDocument *XMLDocument) {
NSLog(#"Failure!");
}];
[operation start];
return operation;
}
This is my code that calls this function:
Request *http=[[Request alloc] init];
NSString *data=[http xmlRequest:#"http://legalindexes.indoff.com/sitemap.xml"];
NSError *error;
DDXMLDocument *ddDoc=[[DDXMLDocument alloc] initWithXMLString:data options:0 error:&error];
NSArray *xmlItems=[ddDoc nodesForXPath:#"//url" error:&error];
NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:[xmlItems count]];
for(DDXMLElement* itemElement in xmlItems){
DDXMLElement *element = [[itemElement nodesForXPath:#"loc" error:&error] objectAtIndex:0];
NSLog(#"valueasstring %#", element);
[returnArray addObject:element];
}
I need the xmlRequest to return a string so I can get the XML but the [operation start] creates correct output but I can't put it in a string. How can I direct the output into a string?
In that code, the network request happens asynchronously – there’s no way for you to return its result from that method.
The line NSLog(#"XMLDocument: %#", XMLDocument); is inside the success handler block – that will be called when the request actually finishes. You should replace the log statement with code to save your string somewhere, and only then call the remainder of your code.
There’s a few ways you could do this:
Create a property on the class like #property (strong) DDXMLDocument *XMLDocument;
You can then replace the log statement with self.XMLDocument = XMLDocument;
Then, make another method that does the rest of your processing.
Alternatively, just make another method like -processWithXMLDocument:(DDXMLDocument *)XMLDocument; that you can call from the block, simply passing it as an argument.
I can’t remember what dispatch queue the success handler will be called on, so you may have to be careful to run your code back on the main thread dispatch_async(dispatch_get_main_queue(), ^(){…
I am making a JSON request with AFNetworking and then call [operation waitUntilFinished] to wait on the operation and the success or failure blocks. But, it seems to fall right though - in terms of the log messages, I get "0", "3", "1" instead of "0", "1", "3"
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://google.com"]];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:#"query", #"q", nil];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
NSLog(#"0");
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *innerRequest, NSHTTPURLResponse *response, id JSON) {
NSLog(#"1");
gotResponse = YES;
} failure:^(NSURLRequest *innerRequest, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"2");
gotResponse = YES;
}];
NSLog(#"Starting request");
[operation start];
[operation waitUntilFinished];
NSLog(#"3");
This works by using AFNetworking to set up the requests, but making a synchronous call then handling the completion blocks manually. Very simple. AFNetworking doesn't seem to support this https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-FAQ, though the work around is simple enough.
#import "SimpleClient.h"
#import "AFHTTPClient.h"
#import "AFJSONRequestOperation.h"
#import "AFJSONUtilities.h"
#implementation SimpleClient
+ (void) makeRequestTo:(NSString *) urlStr
parameters:(NSDictionary *) params
successCallback:(void (^)(id jsonResponse)) successCallback
errorCallback:(void (^)(NSError * error, NSString *errorMsg)) errorCallback {
NSURLResponse *response = nil;
NSError *error = nil;
NSURL *url = [NSURL URLWithString:urlStr];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:[url path] parameters:params];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(error) {
errorCallback(error, nil);
} else {
id JSON = AFJSONDecode(data, &error);
successCallback(JSON);
}
}
#end
That should (almost) work. Your call to
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
should probably not pass [url path] to the path: parameter. In AFNetworking land, that path is everything after the base url (for example the base url could be "http://google.com" and the path "/gmail" or whatever).
That being said, it's probably not a good idea to make the asynchronous operation into a thread-blocking synchronous operation with waitUntilFinished, but I'm sure you have your reasons... ;)
I just had the same problem and found a different solution. I had two operations that depend on each other, but can load in parallel. However, the completion block of the second operation can not be executed before the completion block of the first one has finished.
As Colin pointed out, it might be a bad choice to make a web request block. This was essential to me, so I did it asynchronously.
This is my solution:
// This is our lock
#interface SomeController () {
NSLock *_dataLock;
}
#end
#implementation
// This is just an example, you might as well trigger both operations in separate
// places if you get the locking right
// This might be called e.g. in awakeFromNib
- (void)someStartpoint {
AFJSONRequestOperation *operation1 = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url1]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id data) {
// We're done, we unlock so the next operation can continue its
// completion block
[_dataLock unlock];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id data) {
// The request has failed, so we need to unlock for the next try
[_dataLock unlock];
}];
AFJSONRequestOperation *operation2 = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url2]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id data) {
// The completion block (or at least the blocking part must be run in a
// separate thread
[NSThread detachNewThreadSelector:#selector(completionBlockOfOperation2:) toTarget:self withObject:data];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id data) {
// This second operation may fail without affecting the lock
}];
// We need to lock before both operations are started
[_dataLock lock];
// Order does not really matter here
[operation2 start];
[operation1 start];
}
- (void)completionBlockOfOperation2:(id)data {
// We wait for the first operation to finish its completion block
[_dataLock lock];
// It's done, so we can continue
// We need to unlock afterwards, so a next call to one of the operations
// wouldn't deadlock
[_dataLock unlock];
}
#end
Use Delegate method call
Put the method inside block which will call itself when downloading/uploading completes.