Handling response with raw data using RestKit - objective-c

I am using RestKit 0.23.3.
For a particular path pattern (say download_data/:dataId):
I can send a request via RestKit without any problem,
I expect a response with arbitrary Content Type (image/png, text/plain, text/xml,... or even application/json),
If 2xx status code is returned by the server, I want to receive the body of HTTP response as NSData instance (regardless of Content Type) in the success handler block.
Is this possible (and how) using RestKit?
Thanks.

With new restkit 0.20.3 you can execute your rest web-service like:
[[AppDelegate appDelegate].rkomForProbeList getObjectsAtPath:[NSString stringWithFormat:kResource_Probe,[[NSUserDefaults standardUserDefaults] valueForKey:kNS_DF_AccessRef]] parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"%#",operation.HTTPRequestOperation.responseString);
// if raw data
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:[operation.HTTPRequestOperation.responseString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:NULL];
// if mapped
arrayForProbe = [[NSMutableArray alloc] initWithArray:mappingResult.array];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"%#",operation.HTTPRequestOperation.responseString);
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:[,operation.HTTPRequestOperation.responseString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:NULL];
NSLog(#"%#",operation.HTTPRequestOperation.response.statusCode);
NSLog(#"%#",error.description);
[self hideViewContentAsProbesNotAvailable];
}];
By mapping you will get your object directly from mappingarray. For parsing raw data you have get json object from response string.
rkomForProbeList is your RKObjectManager's instance.
kResource_Probe is resource path. Like if base URL is http://www.hi.com/ and your rest api required http://www.hi.com/login then "login" will be your resource path.

I'm not familiar with RESTKit, but anyway maybe it will help you.
I suggest you to use NSURLSession for low level requests like this, as RESTKit adds a lot of abstractions, which is unnecessary for this task.
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:#"http://yourSite.com"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode >= 200 && statusCode < 300) {
// that's OK
} else {
// something wrong
}
}
}] resume];

Related

Best memory management practice for blocks and completion handlers

I have the following code in my app, and there is a memory leak with the 'dict' object. So I have a few questions about the best practice for this after the code:
// Convert JSON to dict
NSError *error = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
// If error return nil
if (error)
completion(nil, [self handleSerializationError:error]);
else if (![UsefulFunctions objectContainsData:dict[#"data"]])
completion(nil, NO);
// No error then return dict
else
completion(dict, NO);
});
About the code: The completion handler passes back the dict object which is then used to create core data entities (hence the main thread) based upon the calling function. The data being serialised is from an NSURLConnection. So the questions are as follows:
1) Is this the correct practice for passing back data in a completion handler?
2) Which function should take care of the memory management, should it be the calling class?
3) Is it worth wrapping this in an auto-release pool, or is that not how they are supposed to be used (kind of a separate question).
4) Can anyone see any obvious reasons from this function alone why the dict is retained, or is it purely down to the calling class?
Thanks for any help
EDIT (Whole Function), just to confirm I am using ARC and this function is called from the main thread:
- (void)downloadJSONFromURL:(NSURL *)url withCompletionHandler:(void (^)(id object, BOOL retry))completion
{
// Check URL
if (url)
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *authToken = [NSString stringWithFormat:#"token %#", [UsefulFunctions returnActiveAPIKey]];
[request setValue:authToken forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"GET"];
// Create an asynch request, don't want to hold up main queue
[NSURLConnection sendAsynchronousRequest:request queue:[self operationQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
// If data exists
if (data)
{
// Convert JSON to dict
NSError *error = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
// If error return nil
if (error)
completion(nil, [self handleSerializationError:error]);
else if (![UsefulFunctions objectContainsData:dict[#"data"]])
completion(nil, NO);
// No error then return dict
else
completion(dict, NO);
});
}
// If error
else if (connectionError)
{
//NSLog(#"Connection Error: %#, Code: %lu", connectionError.description, (long)connectionError.code);
dispatch_async(dispatch_get_main_queue(), ^{
// Return nil
completion(nil, [self handleConnectionError:connectionError]);
});
}
}];
}
}

Store NSURLSessionDataTask response to SQLite database?

My code calls HTTP post call to remote server and obtains results in JSON format, I have extracted this JSON, but now I need to store these results to SQLite. Based on my reading NSURLSessionDataTask is background thread, so my confusion is, should I call SQL open and insert inside completionHandler (or) is there any other best practice to handle this type of requirements?
EDIT 1
The point I am struggling more is, is it valid to write SQLite operations inside "completionHandler"? does "completionHandler" will be considered as method on separate thread (which is executing SessionDataTask) or main thread?
EDIT 2
I am open to CoreData related solutions too.
Any help would be appreciated.
NSURL *loginUrl = [NSURL URLWithString:#"myurl"];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:loginUrl];
request.HTTPMethod = #"POST";
NSString *ipData = [NSString stringWithFormat:#"uName=%#&pwd=%#",self.userName.text,self.userPwd.text];
request.HTTPBody = [ipData dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *jsonError) {
NSLog(#"Inside post data task......");
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
if(httpResp.statusCode == 200)
{
NSLog(#"Response succesfull.........");
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
if(!jsonError)
{
//No Json error
NSString *uName = jsonDict[#"userName"];
NSString *uID = jsonDict[#"uID"];
//HOW CAN I INSERT THESE DETAILS TO SQLITE DB BEFORE CALLING SEGUE TO MOVE TO NEXT SCREEN?
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"mysegueID" sender:self];
});
}
}else
{
NSLog(#"Response is not succesfulll...");
}
}];
[postDataTask resume];
A lot of people use FMDB as objective-c wrapper around sqlite.
In case of NSURLSession, the block of the completion handler will be executed on the "delegate queue" (see delegateQueue property of NSURLSession).
It is valid to do SQLite in completion handler as long as you follow SQLite threading rules. I recommend FMDB her again because it has helpers for this. See Using FMDatabaseQueue and Thread Safety.
So your example would look like:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
NSURL *loginUrl = [NSURL URLWithString:#"myurl"];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:loginUrl];
request.HTTPMethod = #"POST";
NSString *ipData = [NSString stringWithFormat:#"uName=%#&pwd=%#",self.userName.text,self.userPwd.text];
request.HTTPBody = [ipData dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *jsonError) {
NSLog(#"Inside post data task......");
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
if(httpResp.statusCode == 200)
{
NSLog(#"Response succesfull.........");
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
if(!jsonError)
{
//No Json error
NSString *uName = jsonDict[#"userName"];
NSString *uID = jsonDict[#"uID"];
//HOW CAN I INSERT THESE DETAILS TO SQLITE DB BEFORE CALLING SEGUE TO MOVE TO NEXT SCREEN?
[queue inDatabase:^(FMDatabase *db) {
NSDictionary *argsDict = #{ #"uName" : uName, #"uID" : uID};
[db executeUpdate:#"INSERT INTO myTable (name) VALUES (:name)" withParameterDictionary:argsDict];
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"mysegueID" sender:self];
});
}
}
else
{
NSLog(#"Response is not succesfulll...");
}
}];
[postDataTask resume];
A SQLite DB can be accessed from any thread in an app. The only restriction is that SQLite does not happily tolerate simultaneous access from multiple threads (and "simultaneous" here applies to the duration of a "transaction", not simply the duration of a call to SQLite methods).
So you must somehow assure that there is never simultaneous access. A simple way to do this is to always use the same thread (eg, the main thread) for access. Or you can implement "soft" protocols such that you know that two actions are not simultaneously trying to use the DB because they are separated in time. Or you can make use of Objective-C lock or other synchronization mechanisms in the software/OS.

Twitter Streaming Endpoint not working with SLRequest object

I am trying to pull data from the Twitter Streaming API using the SLRequest class.
When I use the endpoint and parameters documented in the code below the program "hangs"
and no JSON data is printed. I am using an endpoint based on an example from the twitter dev
website https://dev.twitter.com/docs/streaming-apis/parameters#with I am requesting
tweets at a certain location.
When I use this code to query my timeline using the REST API (the code and request is included but
commented out) the program does not hang and I get a valid response.
Is there something else in the code that I need to implement to access the data using the streaming API? What additional modifications or changes need to be made?
ACAccountStore * accountStore = [[ACAccountStore alloc] init];
ACAccountType * twitterAccountType =
[accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Ask the user permission to access his account
[accountStore requestAccessToAccountsWithType:twitterAccountType options:nil completion:^(BOOL granted, NSError *error) {
if (granted == NO) {
NSLog(#"-- error: %#", [error localizedDescription]);
}
if (granted == YES){
/***************** Create request using REST API*********************
***************** This URL is functional and returns valid data *****
NSURL * url = [NSURL URLWithString:#"https://userstream.twitter.com/1.1/user.json"];
SLRequest * request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:url parameters:#{#"screen_name": #"your_twitter_id"}];
***************************************************************/
// Create request using Streaming API Endpoint
NSURL * url = [NSURL URLWithString:#"https://stream.twitter.com/1.1/statuses/filter.json"];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:#"track" forKey:#"twitter&"];
[params setObject:#"locations" forKey:#"-122.75,36.8,-121.75,37.8"];
SLRequest * request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:url parameters:params];
NSArray * twitterAccounts = [accountStore accountsWithAccountType:twitterAccountType];
if ([twitterAccounts count] == 0) {
(NSLog(#"-- no accounts available"));
} else if ([twitterAccounts count] >0){
[request setAccount:[twitterAccounts lastObject]];
NSLog([request.account description]);
NSLog(#"Twitter handler of user is %#", request.account.username);
// Execute the request
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSError * jsonError = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:&jsonError];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// NSLog(#"-- json Data is %#", json);
NSLog([json description]);
}];
}];
}
}
}];
SLRequest doesn't play well with the streaming API.
Here is how to do with STTwitter:
self.twitter = [STTwitterAPI twitterAPIOSWithAccount:account];
[_twitter verifyCredentialsWithSuccessBlock:^(NSString *username) {
NSLog(#"-- access granted for %#", username);
[_twitter postStatusesFilterUserIDs:nil
keywordsToTrack:#[#"twitter"]
locationBoundingBoxes:#[#"-122.75,36.8,-121.75,37.8"]
delimited:nil
stallWarnings:nil
progressBlock:^(id response) {
NSLog(#"-- %#", response);
} stallWarningBlock:^(NSString *code, NSString *message, NSUInteger percentFull) {
NSLog(#"-- stall warning");
} errorBlock:^(NSError *error) {
NSLog(#"-- %#", [error localizedDescription]);
}];
} errorBlock:^(NSError *error) {
NSLog(#"-- %#", [error localizedDescription]);
}];
Internally, STTwitter builds a NSURLConnection instance with the request from -[SLRequest preparedURLRequest]. You can replicate this trick in your code if you wish.

How to read Java generated JSON data from Objective C?

I have generated JSON data from Java Restful WebServices and I need to put into the Objective C code. How can I use the JSON data and integrate into Objective C? The IDE has generated the local URL, how can I use the generated JSON data in other machine. Thank you
Have a look at NSURLConnection to retrieve the JSON from your web service. Then you can make use of NSJSONSerialization to parse it.
Use any of the many JSON parsers available. This question compares a few of them: Comparison of JSON Parser for Objective-C (JSON Framework, YAJL, TouchJSON, etc)
You can request the NSData from the URL and then use NSJSONSerialization to interpret it. For example:
NSURL *url = [NSURL URLWithString:#"http://www.put.your.url.here/test.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
NSLog(#"%s: sendAsynchronousRequest error: %#", __FUNCTION__, error);
return;
}
NSError *jsonError = nil;
NSArray *results = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError) {
NSLog(#"%s: JSONObjectWithData error: %#", __FUNCTION__, jsonError);
return;
}
// now you can use the array/dictionary you got from JSONObjectWithData; I'll just log it
NSLog(#"results = %#", results);
}];
Clearly, that assumed that the JSON represented an array. If it was a dictionary, you'd replace the NSArray reference with a NSDictionary reference. But hopefully this illustrates the idea.

JSON Response in postPath AFHTTPClient

I just switched to RestKit 0.2 and I am currently using the new "HttpClient" which is basically a AFHTTPClient. I have this line of code:
RKObjectManager* objectManager = [RKObjectManager sharedManager];
NSDictionary* params = [[NSDictionary alloc] initWithObjectsAndKeys: login, #"username", password, #"password", nil];
[[objectManager HTTPClient]postPath:#"users/login/?format=json" parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
//reponseObject vs operation.response
NSLog(#"%#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"ERROR");
}];
This POST calls return a JSON response in the form: {"api_key":"....","username":"...."}. As simple as that.
Before switching to 0.2, I was able to get the api_key key in the response by doing:
[[RKClient sharedClient] post:#"/users/login/?format=json" usingBlock:^(RKRequest *request)
{
request.onDidLoadResponse = ^(RKResponse *response)
{
id parsedResponse = [response parsedBody:NULL];
NSString *apiKey = [parsedResponse valueForKey:#"api_key"];
}
}.....];
http://restkit.org/api/master/Classes/RKResponse.html
But now, I can't do that and if I do a NSLog on the responseObject, I get:
<7b227265 61736f6e 223a2022 41504920 4b657920 666f756e 64222c20 22617069 5f6b6579 223a2022 61356661 65323437 66336264 35316164 39396338 63393734 36386438 34636162 36306537 65386331 222c2022 73756363 65737322 3a207472 75657d>
And the weird thing is that if I do:
NSLog(#"%#", operation.responseString);
I do have the JSON (in NSString) showing up.
So two questions:
1) Why is printing the responseObject showing me HEX code, and not the actually JSON response?
2) Why if I do operation.responseString it is showing the actual Response Object? Is there a way to get the actual data in ResponseObject after being parsed from the JSON?
AFNetworking should instantiate a AFJSONRequestOperation. Probably it creates a basic AFHTTPRequestOperation instead (check [operation class]) resulting in a NSData object as response.
Make sure you register the operation class in the init method of your AFHTTPClient subclass (initWithBaseURL):
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
You could also try to use AFJSONRequestOperation directly like this:
NSURLRequest *request = [[objectManager HTTPClient] requestWithMethod:#"POST" path:#"users/login/?format=json" parameters:params];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"JSON: %#", JSON);
} failure:nil];
[[objectManager HTTPClient] enqueueHTTPRequestOperation:operation];
What you are seeing, if I'm not mistaken, is the raw bytes from the NSData that is given to you when your success block is called.
The hex you posted reads:
{"reason": "API Key found", "api_key": "a5fae247f3bd51ad99c8c97468d84cab60e7e8c1", "success": true}
The reason the second NSLog shows you what you want is that the %# format string calls the description (correct me if I'm wrong here, SO) of the object you pass it and the NSData probably knows it is a string underneath.
So, on to how to get the JSON. It is really rather simple. Once you have your response object, you can do something like this:
NSDictionary* jsonFromData = (NSDictionary*)[NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&error];
What this will do for you is use return an NSDictionary which encodes the root object in the JSON and then each value in the dictionary will be of the type NSString, NSNumber, NSArray, NSDictionary, or NSNull. See NSJSONSserialization for documentation.
The NSJSONReadingMutableContainers makes the dictionaries and arrays mutable. It's just a leftover from my code.
Hopefully you're on iOS 5 or later, or you'll need to find another solution for the parsing.
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSData *responseData = operation.HTTPRequestOperation.responseData;
id parsedResponse = [RKMIMETypeSerialization objectFromData:responseData MIMEType:RKMIMETypeJSON error:nil];
NSString *apiKey = [parsedResponse valueForKey:#"api_key"]
}