Getting data out of the NSURLResponse completion block - objective-c

It looks like I didn't get the concept of blocks completely yet...
In my code I have to get out the JSON data from the asychronous block to be returned to from the 'outer' method. I googled and found that if defining a variable with __block, the v̶i̶s̶i̶b̶i̶l̶i̶t̶y̶ _mutability_ of that variable is extended to the block.
But for some reason returned json object is nil.I wonder why?
- (NSMutableDictionary *)executeRequestUrlString:(NSString *)urlString
{
__block NSMutableDictionary *json = nil;
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPShouldHandleCookies:YES];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-type"];
NSString *cookieString = [self.userDefaults objectForKey:SAVED_COOKIE];
[request addValue:cookieString forHTTPHeaderField:#"Cookie"];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"dataAsString %#", [NSString stringWithUTF8String:[data bytes]]);
NSError *error1;
NSMutableDictionary * innerJson = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error1];
json = innerJson;
}];
return json;
}

First, to answer your question:
But for some reason returned json object is nil. I wonder why?
The variable that you are returning has not been set at the time when you return it. You cannot harvest the results immediately after the sendAsynchronousRequest:queue:completionHandler: method has returned: the call has to finish the roundtrip before calling back your block and setting json variable.
Now a quick note on what to do about it: your method is attempting to convert an asynchronous call into a synchronous one. Try to keep it asynchronous if you can. Rather than expecting a method that returns a NSMutableDictionary*, make a method that takes a block of its own, and pass the dictionary to that block when the sendAsynchronousRequest: method completes:
- (void)executeRequestUrlString:(NSString *)urlString withBlock:(void (^)(NSDictionary *jsonData))block {
// Prepare for the call
...
// Make the call
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSLog(#"dataAsString %#", [NSString stringWithUTF8String:[data bytes]]);
NSError *error1;
NSMutableDictionary * innerJson = [NSJSONSerialization
JSONObjectWithData:data options:kNilOptions error:&error1
];
block(innerJson); // Call back the block passed into your method
}];
}

When you call sendAsynchronousRequest:queue:completionHandler:, you've requested an asynchronous request. So it queues the request and the block and returns immediately. At some point in the future the request is made, and some point after that the completion block is run. But by that time, return json has long since run.
If you want to be able to return the data synchronously, then you must make a synchronous request. That will hang this thread until it completes, so it must not be the main thread.

Check the string when converting data coming from server using below code:
NSLog(#"dataAsString %#", [NSString stringWithUTF8String:[data bytes]]);
if the string is in a proper JSON format, ONLY then your JSON object will be correct.
Hope this hepls!!

Related

Instagram API json data is truncated via app but ok in browser

Trying to parse simple JSON data from Instagram but stuck with this problem.
JSON data returns truncated in application, but everything is ok via browser on my mac.
Tried to do that many different ways, but all the same.
First way:
NSURL *instaGetRecentOwnerPhotosURL = [NSURL URLWithString:#"https://api.instagram.com/v1/users/self/media/recent/?access_token=MY_PROPER_TOKEN"];
NSData *jsonData = [NSData dataWithContentsOfURL:instaGetRecentOwnerPhotosURL];
Another way, assync:
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://api.instagram.com/v1/users/self/media/recent/?access_token=MY_PROPER_TOKEN"]];
__block NSDictionary *json;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
NSLog(#"Async JSON: %#", json);
}];
JSON data returns like that:
screenshot of truncated json
Absolutely have no idea what is wrong.
It's not truncated. The log simply only shows part of the output. If it was really truncated it either wouldn't have parsed at all or it would just have fewer entries. But the data did parse. There is nothing wrong with json.
BTW - do proper error checking:
NSError *error = nil;
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (json) {
// Data is good. Work with 'json'
} else {
NSLog(#"Unable to parse JSON. Error: %#", error);
}

Code execution with sendAsynchronousRequest

A variable is being set to (null) due to the sendAsynchronousRequest not completing before the request is complete. See code:
main.m:
GlobalSettings *globalsettings = [[GlobalSettings alloc] init];
NSString *url = [globalsettings facebookLink];
NSLog(#"URL: %#", url);
So, inside GlobalSettings:
-(NSString *)facebookLink
{
__block NSString *strReturn;
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://urlEditedOut/"]];
__block NSDictionary *json;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
strReturn = json[#"FB"];
}];
return strReturn;
}
So this works fine, has been tested inside the completion block. However back in main.m the variable url is being set to (null) due to (i assume) the async request still connecting / processing request.
How do you combat this so that the variable is saved as the correct value?
The url is set to null because the method returns immediately due to the asynchronous request. The way to avoid this is a delegate. make main the delegate of the GobalSettings and call the delegate method from the completion block. This SO-Post isnt an exact duplicate, but its close enough to get you started.
How to return the ouput if method is using NSURLConnection Asynchronous calls with blocks
Avt's answer is what i would suggest, but returning a block works, too.

getting http headers by NSURLConnection sendAsynchronousRequest

How do I get http headers by using sendAsynchronousRequest? Notice that the target URL of the URLRequest only sets headers. When I use sendSynchronousRequest, I can get the http headers by using the NSURLHTTPResponse object. However, in sendAsynchronousRequest, there is only a NSURLResponse object. I have used the following cast strategy to convert NSURLResponse to NSURLHTTPResponse in sendAsynchronousRequest method. This however does not work, blockinkg the main thread without generating any error or exception.\
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *hResponse = (NSHTTPURLResponse*) response;
if ([hResponse respondsToSelector:#selector(allHeaderFields)]) {
NSDictionary *dictionary = [hResponse allHeaderFields];
_myHeader = [dictionary objectForKey:#"myHeader"];
}
}
If I don't use the above code, there is no blocking. I have also notice that the NSData returned by sendAsynchronousRequest hás zero length. Please help

Objective C Update RESTful Webservice

I have a web service that allows me to update records on in our database.
The columns in the table are as follows:
allowsActions
assetID
inventoryObjectID
objectDescription
quantity
retired
serialNumber
action
I'm using the following to GET data from the webservice.
NSString *urlString = [NSString stringWithFormat:#"%#", inventoryAndActionsWebservice];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
Then shoving into a dictionary like so:
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSLog(#"WE HAS THE DATAS");
NSDictionary *inventory = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
// Then storing the values in CoreData here
}
}
What would be the syntax for updating the webservice? It expects an object in the body of the service call (POST).
NSMutableURLRequest let's you setHTTPBody: and setHTTPMethod:.#"POST"is the way to do a post. Most services need to know the body length and encoding set in headers. (seeaddValue:forHTTPHeaderField:`) for that.
The only reason this topic is tricky is because the developer is forced to grapple with two problems at once: what constitutes a valid request for my server, and (2) how do I form that request with iOS? Part (2) is actually pretty easy once you get a valid request.
The best way to proceed is to get an example working using curl (or something equivalent). Then move on to producing that request in iOS. If you have trouble, ask a question here of the form: "I know my server needs X, here's my code to produce X, but I'm getting this error Y".
So the syntax that I was looking for was ultimately this:
NSString *jSONString = [NSString stringWithFormat:#"{\"MediaInventoryObjectsId\":%d,\"AssetId\":%d,\"Quantity\":%d,\"SerialNumber\":\"%#\",\"Description\":\"%#\",\"AllowActions\":%d,\"Retired\":%d}",inventoryObjectId, assetID, quantity, serialNumber, description, allowActions, retired];
// Convert jSON string to data
NSData *putData = [jSONString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
// Instantiate a url request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
// Set the request url format
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#/%d", inventoryAndActionsWebservice, inventoryObjectId]]];
[request setHTTPMethod:#"PUT"];
[request setHTTPBody:putData];
[request setValue:#"application/json" forHTTPHeaderField:#"content-type"];
// Send data to the webservice
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];

Getting data from the object, that uses NSURLConnection

I have a class, that gets some data using NSURLConnection. It's method getData creates a request to a server and when some data recieved, method connection:didRecieveData: updates some properties.
- (void)getData
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:dataURL];
NSURLConnection *connectionWithRequest = [NSURLConnection connectionWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Processing data
dataProperty = processedData;
}
The problem is, when I create an instance of this class and call method getData, I can't immediately get object's properties, because data is not received yet. I've read Apple reference about delegates and protocols, but I don't understand how to implement delegate method for this class, that would work like connection:didRecieveData: for NSURLConnection.
Can you explain me how to do this? I would be very glad, if you just post a link to an example. Thank you.
I don't understand how to implement delegate method for this class, that would work like connection:didRecieveData: for NSURLConnection.
The same way NSURLConnection does:
Give this object a property named delegate.
Set that property to another object.
In connectionDidReceiveData:, send a message to the delegate.
In the delegate, implement the method that the other object will be calling.
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"YOUR API URL"]];
NSString *email = #"username#gmail.com";
NSString *password = #"123456";
NSString *deviceToken = #"simulator";
NSString *deviceType = #"1";
NSString *post = [NSString stringWithFormat:#"email=%#&password=%#&deviceToken=%#&deviceType=%#",email,password,deviceToken,deviceType];
NSData *requestBodyData = [post dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPMethod = #"POST";
request.HTTPBody = requestBodyData;
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse response, NSData responseData, NSError *error)
{
NSLog(#"%#",responseData);
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:nil];
NSLog(#"%#",dic[#"data"]);
}];