I have an iOS app using RestKit in order to communicate with a RESTful Rails server. The server uses a basic session token for authentication of users. I want to append this token to every request sent using the RKObjectManager's methods.
I've tried creating a Category overload of NSManagedObject in order to use the following method:
- (void)willSendWithObjectLoader:(RKObjectLoader *)objectLoader
That works fine, however I see no way of appending to the object loader's params. I've even gone as far as to reserialize the object but there is no way to do that without it being sent using escape characters. For example, the following code will give me the object in JSON serialized form
RKObjectMapping *serializationMapping = [[[RKObjectManager sharedManager] mappingProvider] serializationMappingForClass:[self class]];
RKObjectSerializer *ser = [RKObjectSerializer serializerWithObject:self mapping:serializationMapping];
NSObject<RKRequestSerializable> *obj = [ser serializationForMIMEType:RKMIMETypeJSON error:nil];
But the object returned there is intend to be used as a param right away so the following code does not work
[params setValue:[LCSingletonLoggedInUser sharedLoggedInUser].sessionToken forParam:#"token"];
[params setData:obj.HTTPBody forParam:#"data"];
I've also tried various combinations of setObject and setData and obj.HTTPBody as well as just obj right away.
Actually appending obj to params in any way will always result in it adding escape characters which the server can't handle. Setting params = obj will give the correct values to the server but won't include the token.
How about adding it to queryParams?
NSString *resourcePath = [#"/products" stringByAppendingQueryParameters:_queryParams];
Where queryParams is a NSMutableDictionary where you add your token.
Related
I'm working on an iOS client for App.net, and I'd like to use AFIncrementalStore to sync the web service with my Core Data implementation in the app.
I've gotten the two to work for API requests that don't require an auth token, but I can't figure out how to use the auth token with AFIncrementalStore.
In other words, I'm able to pull down the global feed, since it doesn't require auth:
https://alpha-api.app.net/stream/0/posts/stream/global
...however, to get a user's stream, you need auth (you'll note this link gives an error):
https://alpha-api.app.net/stream/0/posts/stream
I understand that I need to append an auth_token to the end of the API call, but I'm not sure how to do that using AFIncrementalStore.
Update: Here's the chunk of code that currently gets the global stream:
This is a method pulled directly from the example App.net project included in AFIncrementalStore.
-(NSURLRequest *)requestForFetchRequest:(NSFetchRequest *)fetchRequest withContext:(NSManagedObjectContext *)context {
NSMutableURLRequest *mutableURLRequest = nil;
if ([fetchRequest.entityName isEqualToString:#"Post"]) {
mutableURLRequest = [self requestWithMethod:#"GET" path:#"stream/0/posts/stream/global" parameters:nil];
}
return mutableURLRequest;
}
All you have to do with the case above to alter it to correctly use an access token is add parameters to the request instead of passing nil.
To build the parameters simply make an NSDictionary with the keys and values you need. For example in some of my App.net code I have this
NSDictionary *params = #{#"access_token": token};
Using the new compiler directives this builds an NSDictionary with a single key (in this case 'access_token') with the value in the NSString token.
After you have this just make your request something like:
[self requestWithMethod:#"GET" path:#"stream/0/posts/stream" parameters:params];
To pass the parameters in the request.
Let me know if this isn't clear enough!
Quick brief of what I'm doing: I have two arrays, they both contain 50% of the information for a table view. Why? Because one of the arrays pulls current information from the internet, while the other array has saved user data. I had no idea as to how to get current information from the internet in a non messy way, as I'm an amateur to objective-C let alone networking in Objective-C. Anyway, so the internet Array is pulling information that corresponds to the objects in the saved array (saved in Core Data) using AFNetworking. It's therefore Asynchronous, which I want. Now here comes the problem, or at least what I can garner from the situation.
I'm doing a for loop that effectively counts through the objects in the saved array and passes the unique ID for each object so that the corresponding information from the internet can be downloaded, parsed and added to the internet array. However, since the networking is Asynchronous, the loop is effectively downloading all the information from the internet at once. Therefore the objects are being written to the internet array in order of which downloaded first. Therefore savedArray[0] does not correspond to the object in internetArray[0]. This is a pretty big flaw as you can imagine, as when I'm inserting the values into my tableView, nothing really matches up/makes sense.
I'm really looking for a way to postpone the downloading of the information, until the previous download has been completed and added to the internetArray, how on earth do I do this? Now for the code. Here is where I get the appropriate key:
for ( int i = 0; i < [mySavedObjects count]; i++) {
MySavedObject* mySavedObject = [mySavedObjects objectAtIndex:i];
[self retrieveMissingInformation: myObject.Id];
[self.tableView reloadData];
}
And here is where I actually get the information (simplified for the sake of space):
- (void)retrieveMissingInformation:(NSString *) Id
{
// create URL here.
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Success");
// Do some safety checks & parse the JSON, returning a searchResult.
[searchResults addObject:searchResult];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
// Do some error messages etc.
}];
[queue addOperation:operation]; //where queue is an ivar NSOperationQueue.
}
Finally, in the cellForRowAtIndexpath, I these use both:
MySavedObject *mySavedObject = [mySavedObjects objectAtIndex:indexPath.row];
&
SearchResult *searchResult = [searchResults objectAtIndex:indexPath.row];
To get the values for the cell.
Sorry for the massively large wall of text. I'm not really good enough to explain things efficiently, often getting tongue-tied on the terminology and having to resort to making up my own exmaples. Any help with ordering this mess and I'd be really grateful.
Regards,
Mike.
Good work mate, you've stumble on a common design pattern.
As you've seen, with asynchronous request, execution happens almost concurrently so you're not guaranteed order when you think of "queues" in terms of a for loop.
The way you need to think of your download queue is in terms of recursion.
In other words, instead of:
// your current method of queue-ing the submission of data
for(int i = 0; i < arrData.count; i++)
{
[self doAsyncDataSendForIndex:i];
}
You need to start doing something like this:
// perform the first submit for the first element in your array
[self doAsyncDataSendForIndex:dataIndex];
// a completion callback method
-(void)asyncFinishDownloading
{
[self parseResult:serverReturnedData];
}
-(void)parseResult:(id)serverData
{
// do all the processing of the data that you need here
...
// update index for next round
dataIndex++;
// -----------------------------------------------------------------
// now at the end of this method, perform a recursive call
// of your async download method again
// -----------------------------------------------------------------
[self doAsyncDataSendForIndex:dataIndex];
}
Using the callback delegate way of queueing your download you are telling the code to only download the next data after it has finished processing the last asynchronous download.
Now you can use a number of libraries to help you with your asynchronous stuff. I myself use ASIHttpRequest and ASIFormData, you can use AFNetworking too which is newer.
-(void)asyncSubmitData:(id)someData
{
NSURL *url = [NSURL urlWithString:#"http://www.yourserver.com/...."];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
// setup your POST request parameters
[request setPostValue:someData forKey:#"someKey"];
...
// setup the completion callback delegate action
[request setCompletionBlock:^{
// process your server returned data here
NSString *serverResponse = [request responseString]; // raw server response
...
// increment your index and call your download method again
dataIndex++;
[self asyncSubmitData:[myArray objectAtIndex:dataIndex]];
}];
// setup fail block in case the server fails
[request setFailedBlock:^{
NSLog(#"Server error: %#", [[request error] localizedDescription];
}];
// start the asynchronous request now
[request startAsynchronous];
}
Hope that helps.
I think I understand your problem and it sounds like you're using the wrong data structures to store the data.
You have one array that is generating requests and the results to those requests are stored in a separate, unrelated array.
As you generate the request for information with an objectId, can you store the results in a NSDictionary instead of an array and use the objectId as the key to this array?
You have the NSURLRequest in the success method so you should be able to retrieve your objectId from the request through the NSURL.query if it is a parameter on the query string
Postponing the downloading of info and effectively making all of your network requests serial (one at at time) would work, but isn't a very nice solution, because it's often handy to have concurrent network requests going on - for example, allowing four requests at once would probably take less time than making all requests serially.
A much better solution is to correctly handle a piece of data when it comes back. To do this you need to handle the returned data in a more structured way. Rather than just appending it to an array, do one of the following:
A. Put returned data in a dictionary. The dictionary can map from an index or appropriate key to the returned data. (Hint: if you want to put an int as a key in a dictionary, construct an NSNumber containing that int.) This option is recommended over B.
B. Insert the returned data into an array. In order for ths to make sense, before you do any fetching, you should fill up your array with placeholder objects that denote "this bit of data hasn't been fetched yet". One possible placeholder object choice would be NSNull - it's just an object to denote null/nil.
So then you can return the correct item in cellForIndexPath: by dipping into your dictionary or array.
These two data structure ideas are just the most obvious ones that come to mind, there are doubtless other ways to crack this nut.
I try to add a Json strem form my odata server.
It work fine with jQuery, but in objective-c ther is only xml returned.
I try this :
DataModel *proxy = [[DataModel alloc] initWithUri:#"http://www.example.com/odata/odata.svc" credential:nil];
[proxy addHeader:#"Accept" headerValue:#"application/json, text/javascript, */*; sq=0.01"];
QueryOperationResponse *response = [proxy execute:#"Example"];
NSMutableArray *array = [response getResult];
It doesn't work.
How can I do?
thanks
Gwennin
xml is what odata will return. you will need to parse the results. Here is a link to a great tutorial for doing that.
http://www.raywenderlich.com/553/how-to-chose-the-best-xml-parser-for-your-iphone-project
If the returned type is a Collection<> of some sort, the OData library does the work for you and converts it to an array of the correct Entity in Objective-C.
If you return a simple type, parse the result. An example is using XPath queries (although for my service, I had to remove from the returned XML a couple of unsupported root element attributes that Odata returned).
If you chose to use Xpath,I love this code: http://cocoawithlove.com/2008/10/using-libxml2-for-parsing-and-xpath.html
I am using FConnect in my app. After the user logs in to his/her Facebook account I am getting his/her information as:
{
"id":"###########",
"name":"Vishnu Gupta",
"first_name":"Vishnu",
"last_name":"Gupta",
"link":"facebook a/c link id=#######",
"education":[{"school":{"id":"######","name":"st.joseph"},"type":"High School"}],
"gender":"male","email":"##########",
"timezone":5.5,
"locale":"en_US",
"verified":true,
"updated_time":"2010-11-27T10:10:25+0000"
}
I want to store user's information in database. To do that, I have to separate the user's information that is id, name, and email.
I tried using componentsSeparatedByString method but I do not understand how to retrieve the information in an array.
Can anyone help me????
The response you are getting is in JSON Format which basically have response in Array & Dictionary.
You will be getting this data in Dictionary..so you can access it by using the key value..
NSLog(#"%#",[NSDictionaryObject valueforkey:#"id"]);
NSLog(#"%#",[NSDictionaryObject valueforkey:#"name"]);
NSLog(#"%#",[NSDictionaryObject valueforkey:#"first_name"]);
Hope this will surely work for you.....
ya.....its corrrect.In detail:
Add JSON SDK then write the below code
SBJSON *json = [[SBJSON alloc] init];
NSDictionary *returnObject = [json objectWithString:InfoName];
NSString #id=[NSString stringWithFormat:#"%#",[returnObject objectForKey:#"id"]];
same for name ,email.
then Add these objects to Array and release the json
JSOn is good to use for "key-value' pair parsing.
I am able to successfully get the SID (SessionID) for my Google Reader account. In order to obtain the feed and do other operations inside Google Reader, you have to obtain an authorization token. I'm having trouble doing this. Can someone shed some light?
//Create a cookie to append to the GET request
NSDictionary *cookieDictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"SID",#"NSHTTPCookieName",self.sessionID,#"NSHTTPCookieValue",#".google.com",#"NSHTTPCookieDomain",#"/",#"NSHTTPCookiePath",nil];
NSHTTPCookie *authCookie = [NSHTTPCookie cookieWithProperties:cookieDictionary];
//The URL to obtain the Token from
NSURL *tokenURL = [NSURL URLWithString:#"http://www.google.com/reader/api/0/token"];
NSMutableURLRequest *tokenRequest = [NSMutableURLRequest requestWithURL:tokenURL];
//Not sure if this is right: add cookie to array, extract the headers from the cookie inside the array...?
[tokenRequest setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:[NSArray arrayWithObjects:authCookie,nil]]];
//This gives me an Error 403 Forbidden in the didReceiveResponse of the delegate
[NSURLConnection connectionWithRequest:tokenRequest delegate:self];
I get a 403 Forbidden error as the response from Google. I'm probably not doing it right. I set the dictionary values according to the documentation for NSHTTPCookie.
The keys of a cookie properties should be the NSHTTPCookie constants, not literal strings as you give them.
NSDictionary *cookieDictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"SID",NSHTTPCookieName,
self.sessionID,NSHTTPCookieValue,
#".google.com",NSHTTPCookieDomain,
#"/",NSHTTPCookiePath,
nil];
Note that the values constants aren't equal to their names; generally speaking, they appear to be the name without the "NSHTTPCookie" (e.g. NSHTTPCookieDomain == #"Domain"), but you shouldn't rely on this. Re-read the documentation.