Why method executes slow? - ios

There is a JSON file on server, which contain four "key/value" pairs about version of data in app (it is a cook book, so the version is a version of recipes)
When application starts, it download JSON and check version.
Here is my method, but I think it is very slow.
- (void)isUpdatesAvail
{
updatesAvail = NO;
NSInteger iVer = [[NSUserDefaults standardUserDefaults] integerForKey:#"ingredientsVer"];
NSInteger rVer = [[NSUserDefaults standardUserDefaults] integerForKey:#"recipesVer"];
NSInteger iCnt = [[NSUserDefaults standardUserDefaults] integerForKey:#"ingredientsCount"];
NSInteger rCnt = [[NSUserDefaults standardUserDefaults] integerForKey:#"recipesCount"];
NSString *countPath = [downloadPath stringByAppendingString:#"/versioninfo"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:countPath]];
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSError *error = nil;
if (!responseData) {
return;
}
NSDictionary *recipesCount = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
if (!recipesCount) {
return;
}
rCount = [recipesCount objectForKey:#"recipeCount"];
iCount = [recipesCount objectForKey:#"ingredientCount"];
rVersion = [recipesCount objectForKey:#"recipeVersion"];
iVersion = [recipesCount objectForKey:#"ingredientVersion"];
if ([iVersion integerValue] > iVer || [rVersion integerValue] > rVer || [iCount integerValue] > iCnt || [rCount integerValue] > rCnt) {
updatesAvail = YES;
}
}
Can somebody give me advice (or may be example) how to do that?

It's 'slow' because it performs a synchronous URL request. It's probably just waiting for the network transfer to complete for most of the time it executes if it is noticeably slow.
There's not much you can do to make it faster, apart from either caching the data or reducing the size of the data.
In any event, you should just perform an asynchronous request then do your work/update after it's completed.

Related

Dictionary initialized in AppDelegate has nil value in UIViewController

I am using open weather API to get live weather data and displaying it in a UIViewController. However I make the http request in AppDelegate. So I made the API request in AppDelegate in a method called weatherForcast(), converted the JSON response to a NSDictionary object, and printed the object to the console just to make sure everything worked fine, and it did.
NSString *urllink = [NSString stringWithFormat:#"http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%#&units=metric", lat, lng, WEATHERAPIKEY];
NSURL *jsonURL = [NSURL URLWithString:[self urlEncodeValue:urllink]];
NSString *jsonDataString = [[NSString alloc]initWithContentsOfURL:jsonURL];
NSData *jsonData = [jsonDataString dataUsingEncoding:NSUTF16StringEncoding];
NSLog(#"This is jsonURL:%#", jsonURL);
NSError *err = nil;
if(jsonData == nil)
{
NSLog(#"Error laoding jsonData");
}
else
{
self.weatherInfo = [NSJSONSerialization JSONObjectWithData: jsonData options: NSJSONReadingMutableContainers error: &err];
NSLog(#"This is weatherInfo dictionary:%#", self.weatherInfo);
}
The dictionary is perfect.
Then in the UIViewController in viewDidLoad I call the method weatherForecast() and then call a method UpdateTemperature() which sets all the texts of the labels to data in the dictionary. Here is the code in the method UpdateTemperature:
NSLog(#"This is the weatherInfo dictionary: %#", appDel.weatherInfo);
if([appDel.weatherInfo count] > 0 && appDel.isNetworkAvailable)
{
NSLog(#"Went into weatherInfo.count > 0");
lblCondition.text = [NSString stringWithFormat:#"condition:%#", [[[appDel.weatherInfo valueForKey:#"weather"] objectAtIndex:0] valueForKey:#"description"]];
lblHumidity.text = [NSString stringWithFormat:#"humidity:%#", [[appDel.weatherInfo valueForKey:#"main"] valueForKey:#"humidity"]];
lblTemperature.text = [NSString stringWithFormat:#"%# Celsius", [[appDel.weatherInfo valueForKey:#"main"] valueForKey:#"temp"]];
imgWeather.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#%#", WEATHERCONDITIONIMGURL, [appDel.weatherInfo valueForKey:#"icon"]]]]];
lblDegree.hidden = FALSE;
[getTemp stopAnimating];
}
else
{
lblDegree.hidden = TRUE;
}
All the labels will only be set if the dictionary has at least one object within it, which it should. But it turned not not too. So I printed the dictionary, and got nil.
In AppDelegate when I printed the dictionary it was fine, but than in viewDidLoad when I printed the same dictionary it turned out to be nil. What is happening?
It's likely that when viewDidLoad gets called, weatherInfo has not been initialized yet. If it requires an http call the data may have not returned yet and therefore when you access it in viewDidLoad there is no object to access. You might want to try reconfiguring where you make your http request and create weatherInfo.
when you create object of appdelegate then all variable of appdelegate is reinitialized so it return nil. Just put your code into a fuction and simply return a dictionary
plz try this,
-(NSDictionary *) getWeatherInfo
{
NSString *urllink = [NSString stringWithFormat:#"http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%#&units=metric", 10.0, 10.0, #"api"];
NSURL *jsonURL = [NSURL URLWithString:[self urlEncodeValue:urllink]];
NSData *jsonData = [NSData dataWithContentsOfURL:jsonURL];
NSLog(#"This is jsonURL:%#", jsonURL);
NSError *err = nil;
NSDictionary *weather_info=[NSDictionary dictionary];
if(jsonData == nil)
{
NSLog(#"Error laoding jsonData");
}
else
{
weather_info = [NSJSONSerialization JSONObjectWithData: jsonData options: NSJSONReadingMutableContainers error: &err];
NSLog(#"This is weatherInfo dictionary:%#", weather_info);
}
return weather_info;
}

iOS Terminating app due to uncaught exceptions when parsing JSON object

This is a new error since I started learning to develop for iOS a week ago and I totally don't have a clue what is causing it and why the app suddenly crushes. There must be something wrong in my code if you may have a look at it and kindly suggest how to fix this run-time bug.
*** Terminating app due to uncaught exception 'NSRangeException', reason: '-[__NSCFArray objectAtIndex:]: index (1) beyond bounds (1)'
*** First throw call stack:
(0x2514c49f 0x32902c8b 0x2514c3e5 0x25079a6d 0xd01cb 0x2863f9fb 0x2863f9a1 0x2862a613 0x28747781 0x287fdc6b 0x286041ad 0x286390c1 0x2863899d 0x2860f15d 0x28882ab9 0x2860dbb9 0x25112d57 0x25112167 0x251107cd 0x2505e3c1 0x2505e1d3 0x2c45c0a9 0x2866dfa1 0xa3911 0x32e82aaf)
libc++abi.dylib: terminating with uncaught exception of type NSException
Here is my code snippet
- (IBAction)changeIns:(id)sender {
UISegmentedControl *segmentedControl = (UISegmentedControl *) sender;
NSInteger selectedSegment = segmentedControl.selectedSegmentIndex;
if (selectedSegment == 0) {
//toggle the correct view to be visible
NSLog(#"Clicked");
jsonData = [tArray objectAtIndex:0];
self.displayInsurer.text=jsonData[#"insurerName"];
}
else if (selectedSegment == 1) {
//toggle the correct view to be visible
jsonData = [tArray objectAtIndex:1];
self.displayInsurer.text=jsonData[#"insurerName"];
}
else if (selectedSegment == 2) {
//toggle the correct view to be visible
}
else if (selectedSegment == 3) {
//toggle the correct view to be visible
}
else if (selectedSegment == 4) {
//toggle the correct view to be visible
}
else if (selectedSegment == 5) {
//toggle the correct view to be visible
}
else if (selectedSegment == 6) {
//toggle the correct view to be visible
}
else if (selectedSegment == 7) {
//toggle the correct view to be visible
}
else if (selectedSegment == 8) {
//toggle the correct view to be visible
}
}
-(void)setVariableFromNetwork
{
// NSInteger success = 0;
#try {
NSString *post =[[NSString alloc] initWithFormat:#"uid=%#&tag=getVehicleInfo",[[NSUserDefaults standardUserDefaults] stringForKey:#"userID"]];
NSLog(#"PostData: %#",post);
NSURL *url=[NSURL URLWithString:#"URL"];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[postData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
//[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[url host]];
NSError *error = [[NSError alloc] init];
NSHTTPURLResponse *response = nil;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"Response code: %ld", (long)[response statusCode]);
if ([response statusCode] >= 200 && [response statusCode] < 300)
{
NSString *responseData = [[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
NSLog(#"Response ==> %#", responseData);
NSError *error = nil;
tArray = [NSJSONSerialization JSONObjectWithData:urlData options:kNilOptions error:&error];
NSLog(#"objects %#", [tArray description]);
NSLog(#"first object %#", [tArray objectAtIndex:0]);
NSDictionary *jsonData = [tArray objectAtIndex:0];
NSLog(#"dictionary %#", [jsonData description]);
NSLog(#"vehicle %#", jsonData[#"vehicleMake"]);
NSLog(#"Vehicle Number %#", jsonData[#"vehicleRegNo"]);
int count = [tArray count];
NSMutableArray *mySegments = [[NSMutableArray alloc] initWithCapacity:count];
for (int i = 0; i<count; i++) {
jsonData = [tArray objectAtIndex:i];
//NSString * desc = jsonData[#"vehicleMake"],"-", jsonData[#"vehicleMake"];
[mySegments addObject:jsonData[#"vehicleMake"]];
[mySegments addObject:jsonData[#"vehicleRegNo"]];
}
[self.carSegment removeAllSegments];
self.carSegment = [self.carSegment initWithItems:mySegments];
}
}
#catch (NSException * e) {
NSLog(#"Exception: %#", e);
[self alertStatus:#"Sign in Failed." :#"Error!" :0];
}
}
Your array only has 1 item (at index 0) and you're trying to access an item at index 1 here:
jsonData = [tArray objectAtIndex:1];
So, as KerrM's answer points out, this exception is happening because we're trying to access an index which doesn't exist in our array.
jsonData = [tArray objectAtIndex:1];
This is exactly what the bolded part of the exception you put in the question is saying.
So what should we do to fix it? Well, I don't find the other answers that offer solutions to be particularly that great. In all of them, we're still accessing a hard-coded array index. Sure, we're putting a check in to make sure it exists, but very, very rare should it be that you actually need to directly access an array index that you can hardcode.
Instead, there are these solutions:
Any time we need to access the first object in an array, we can safely do so using the firstObject method, which will always return the object at index 0, unless the array is empty, in which case it will return nil. If you access an array's 0th index when it is empty, you still get the index out of bounds exception.
[myArray firstObject];
Any time we need to access the last object in an array, we can safely do so using the lastObject method. This is the same as firstObject, except it looks at the last index of the array. No matter how many objects in the array, this will return the last one. And if it's an empty array, it will safely return nil.
[myArray lastObject];
And finally, the scenario most applicable to your situation, when we need to loop over all the elements in an array, we should do so using some form of fast enumeration. The easiest to use and most common way of doing this which will work in most scenarios is simply to use a forin loop. There are several advantages to using a forin loop. For a start, they're faster than a regular for loop. But there are other advantages too. We're not accessing our array by index (which helps make the loop faster) and that means we can't have an index out of bounds exception. Finally though, it just looks a whole lot cleaner and more readable than a regular for loop.
for (NSDictionary *jsonData in tArray) {
[mySegments addObject:jsonData[#"vehicleMake"]];
[mySegments addObject:jsonData[#"vehicleRegNo"]];
}
In terms of answering the question of how come tArray only has one object in it, you'll have to look at the JSON data itself. In fact, you should have already done this. There's no guarantee that the JSON data is an array of dictionaries. Most JSON or XML data I see typically has a dictionary as the root object. But the point is that the method signature looks like this:
+ (id)JSONObjectWithData:(NSData *)data
options:(NSJSONReadingOptions)opt
error:(NSError **)error
The return type is id. When Apple released Swift and iOS 8, they also went through Objective-C and eliminated nearly all uses of id, replacing most with instancetype. They're moving away from the use of id. But this one remains. Why? Because it must. In this case, we're using id because we can't use instancetype. We're using id because the return value could be an NSArray, or it could be an NSDictionary. (It can probably also be an NSNumber or NSString object, perhaps.)
So we should definitely be clear on what the expected JSON looks like before we write the code to grab data out of it...
You must first check the number of items in NSArray before accessing them:
if (tArray.count > count) {
jsonData = [tArray objectAtIndex:count-1];
self.displayInsurer.text=jsonData[#"insurerName"];
}
like this!!! It will never crash then...
All the best...
You have an array and you have 1 element in it. Now you are trying to extract 2nd element from it, [0] is first, and [1] is second.
Error-->> jsonData = [tArray objectAtIndex:i];
Check First wether it contains items are more than one
if([tArray count]== 1 && selectedSegment == 0){
jsonData = [tArray objectAtIndex:0];
else if ([tArray count]> 1 && selectedSegment == 1) {
//toggle the correct view to be visible
jsonData = [tArray objectAtIndex:1];
self.displayInsurer.text=jsonData[#"insurerName"];
}

iOS autocomplete from JSON instead of array

I want to make an autocomplete with a tableView, for that I have this function :
-(AutocompletionTableView *)autoCompleter
{
if (!_autoCompleter)
{
NSMutableDictionary *options = [NSMutableDictionary dictionaryWithCapacity:2];
[options setValue:[NSNumber numberWithBool:YES] forKey:ACOCaseSensitive];
[options setValue:nil forKey:ACOUseSourceFont];
_autoCompleter = [[AutocompletionTableView alloc] initWithTextField:self.textField inViewController:self withOptions:options];
_autoCompleter.autoCompleteDelegate = self;
_autoCompleter.suggestionsDictionary = [NSArray arrayWithObjects:#"hostel",#"caret",#"carrot",#"house",#"horse", nil];
}
return _autoCompleter;
}
The Problem :
Instead of autocompleting from an Array, I want to autocomplete from a remote JSON file.
Any idea on I how I can do such thing ? A code snippet will be very helpful, as I am a newbie in iOS development.
After making a request to the server using NSURLConnection, you should receive an NSData containing the following data:
["hostel","caret","carrot","house","horse"]
This NSData is something like this:
NSString* data = #"[\"hostel\",\"caret\",\"carrot\",\"house\",\"horse\"]";
NSData* dataReceived = [data dataUsingEncoding:NSUTF8StringEncoding];
So, to convert it into an array, you can call NSJSONSerialization, like this:
NSError *jsonError = nil;
NSArray *responseDictionary = [NSJSONSerialization JSONObjectWithData:dataReceived options:0 error:&jsonError];
if(jsonError == nil)
{
_autoCompleter.suggestionsDictionary = responseArray;
}

NSThread Causing memory Leaks in iPhone

I am uploading images chunk wise, in a background thread, each chunk will be size of 512kb,to the best of my knowledge,i have taken care of memory leaks using release,nsautoreleasepool.
Below is the code for uploading images chunkwise.
- (void)FetchDataFromDB : (NSNumber *) isOffline
{
#autoreleasepool {
#try {
NSLog(#"FetchDatafromDB");
isThreadStarted = YES;
VYukaDBFunctions *vdb = [VYukaDBFunctions getInstance];
NSMutableArray *fileNames = [vdb GetFileNames:[isOffline integerValue]];
for(int j=0 ; j<[fileNames count] ; j++)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString * filename = fileNames [j] ;
int _outgoingMsgId = [[vdb SelectMsgId:filename] intValue];
int _totalchunk =[[vdb SelectTotalChunk:filename]intValue];
int currentChunk = [vdb GetCurrentChunk:filename];
for( int i=currentChunk ; i <= _totalchunk ; i++)
{
NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
NSString *AsyncRequest = [[NSString alloc] init];
AsyncRequest = [vdb SelectAsyncRequest: i : _outgoingMsgId];
if(![AsyncRequest isEqual:#""])
{
BOOL status = [self UploadChunkWise :AsyncRequest : 1 : i : vdb : filename : _outgoingMsgId];
// AsyncRequest = NULL;
// [AsyncRequest release];
if(status){
if(i==_totalchunk)
{
NSLog(#"Deleting from medialist , FileName :%#", filename);
[vdb DeleteFromMediaList : filename];
}
}
else{
[vdb DeleteFromMediaList : filename];
break;
}
}
[innerPool drain];
}
[pool drain];
}
[fileNames removeAllObjects];
// [fileNames release];
//recurssive call to check any pending uploads..
if([[vdb GetFileNames:[isOffline integerValue]] count] > 0)
{
NSLog(#"Calling Recursively..");
[self FetchDataFromDB:[isOffline integerValue]];
}
}
#catch (NSException *exception) {
NSLog(#"Exception caught on Uploading from FetchDataFromDB:%#", exception);
}
#finally {
}
}
NSLog(#"thread quit ");
isThreadStarted = NO;
[NSThread exit];
}
-(BOOL) UploadChunkWise :(NSString *) AsyncRequest : (int) count : (int)currentChunk : (VYukaDBFunctions * ) vdb : (NSString *) currentFileName : (int) outgoingMsgId
{
NSHTTPURLResponse *response ;
NSError *error;
//Yes, http
NSMutableURLRequest *httpRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"Url goes here"]];
NSData* data = [AsyncRequest dataUsingEncoding:NSUTF8StringEncoding];
[httpRequest setHTTPMethod:#"POST"];
[httpRequest setHTTPBody:data];
[httpRequest setValue:#"application/xml" forHTTPHeaderField:#"Content-Type"];
NSData *returnedData = [NSURLConnection sendSynchronousRequest: httpRequest returningResponse:&response error:&error] ;
NSString *result= [[NSString alloc] initWithData:returnedData encoding:NSASCIIStringEncoding];
[httpRequest release];
returnedData= NULL;
[returnedData release];
data = NULL;
[data release];
if ([result rangeOfString:#"success"].location != NSNotFound )
{
NSLog(#" success");
[vdb DeleteCurrentChunkFromOutgoingTable:currentChunk : outgoingMsgId];
[result release];
return YES ;
}
else if ([result rangeOfString:#"fail"].location != NSNotFound )
{
[result release];
if (count < 3) {
return [self UploadChunkWise :AsyncRequest : count+1 : currentChunk: vdb : currentFileName : outgoingMsgId ];
}
else
{
NSLog(#" failed");
[vdb DeleteAllCurrentFileChunksFromOutgoingTable:currentFileName];
return NO ;
}
}
return NO;
}
I am starting thread as below
[NSThread detachNewThreadSelector:#selector(FetchDataFromDB:) toTarget:self withObject:[NSNumber numberWithInt:0]];
The problem is after uploading 9 to 12 chunks, i am getting memory error. i am getting 4 to 5 times memory warning and after that app crashes.in console i am getting memory warning first at app delegate class, followed by 4 classes which are extending UIViewController. why i am getting warning at app delegate, and other classes which is of type UIViewController.Why i have to release object of other class if the separate thread is giving me memory error? what i am doing wrong here? I cannot use ARC, as i have integrated this with old code, which is not using ARC, i tried enabling ARC class wise, but it dint work. Can any one help me to find out if there is any memory leaks in this code. Suggestions are welcomed and appreciated.Thanks in advance..
Two things- first, I see this:
NSString *AsyncRequest = [[NSString alloc] init];
AsyncRequest = [vdb SelectAsyncRequest: i : _outgoingMsgId];
This should be consolidated to this:
NSString *asyncRequest = [vdb SelectAsyncRequest: i : _outgoingMsgId];
You instead are creating a new instance, then immediately either generating or referencing another instance.
Second:
Your code is very hard to read and doesn't follow the Objective-C smalltalk conventions.
Variable names should begin with a lowercase letter. Method names should also start with lowercase letters. Class names and functions should begin with capital letters. It makes it difficult to read because I and many others have been trained to see capital letters and think CLASS NAME instead of POSSIBLE VARIABLE NAME. Just FYI
Finally, some of your methods take multiple parameters, like the one above. You really should add a prefix to each parameter so that it's easy to understand what the parameter is for. This:
[vdb SelectAsyncRequest: PARAMETER : PARAMETER];
would look much better if it was :
[vdb selectAsyncRequestForParameter: PARAMETER withOtherParameter:OTHERPARAM];
EDIT: I also don't think you need so many autorelease pools. The entire thing is wrapped in a big autorelease pool already.
EDIT2: I also see a lot of release calls that aren't necessary. In your UploadChunkWise method you are calling release on *data and *returnedData which are both already implicitly autoreleased. Methods that return objects to you will already have ownership given up and "handed over" to you. Essentially, those methods will do this:
NSData *data = [[NSData alloc] init];
return [data autorelease];
When you get it, if you want to keep it you will have to retain it yourself, otherwise it will be destroyed at the return of your method.
However, it is correct for you to call release on the NSString *result instance you created with -init.

Implementing OAuth 1.0 in an iOS app

I've been breaking my head over this the whole day.
I wish to integrate my iOS app with Withings api. It uses OAuth 1.0 and I can't seem to understand fully how to implement it.
I've been downloading multiple OAuth framworks (MPOAuth,gtm-oauth,ssoauthkit) but couldn't figure out completely what exactly I should do.
I searched a lot, also in stack overflow for good references on how to go about implementing OAuth 1.0 in general & integrating with Withings in particular with no success.
Kindly explain the flow of integrating an iOS app with an api that requires OAuth 1.0. Code examples would be very helpful. Suggested 3rd party frameworks would be nice too.
Just to clarify, I fully understand the OAuth 1.0 principles, I just have problems in actually implementing it in my app.
I think that a thorough answer with code examples and good references would be very helpful for lots of people as I couldn't find one. If anyone has good experience with implementing it, please take the time to share it.
TDOAuth in my opinion was the best solution. it is clean and simple, only one .h and .m file to work with, and no complicated example projects..
This is the OAuth 1.0 flow:
step 1 - get request token
//withings additional params
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:CALL_BACK_URL forKey:#"oauth_callback"];
//init request
NSURLRequest *rq = [TDOAuth URLRequestForPath:#"/request_token" GETParameters:dict scheme:#"https" host:#"oauth.withings.com/account" consumerKey:WITHINGS_OAUTH_KEY consumerSecret:WITHINGS_OAUTH_SECRET accessToken:nil tokenSecret:nil];
//fire request
NSURLResponse* response;
NSError* error = nil;
NSData* result = [NSURLConnection sendSynchronousRequest:rq returningResponse:&response error:&error];
NSString *s = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
//parse result
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSArray *split = [s componentsSeparatedByString:#"&"];
for (NSString *str in split){
NSArray *split2 = [str componentsSeparatedByString:#"="];
[params setObject:split2[1] forKey:split2[0]];
}
token = params[#"oauth_token"];
tokenSecret = params[#"oauth_token_secret"];
step 2 - get authorize token (by loading the request in a UIWebView, the webViewDidFinishLoad delegate method will handle the call back..)
//withings additional params
NSMutableDictionary *dict2 = [NSMutableDictionary dictionary];
[dict setObject:CALL_BACK_URL forKey:#"oauth_callback"];
//init request
NSURLRequest *rq2 = [TDOAuth URLRequestForPath:#"/authorize" GETParameters:dict2 scheme:#"https" host:#"oauth.withings.com/account" consumerKey:WITHINGS_OAUTH_KEY consumerSecret:WITHINGS_OAUTH_SECRET accessToken:token tokenSecret:tokenSecret];
webView.delegate = self;
[DBLoaderHUD showDBLoaderInView:webView];
[webView loadRequest:rq2];
handle the webView as follow to initiate step 3 (I know the isAuthorizeCallBack smells a lot, but it does the job, should refactor it..)
- (void)webViewDidFinishLoad:(UIWebView *)aWebView
{
[DBLoaderHUD hideDBLoaderInView:webView];
NSString *userId = [self isAuthorizeCallBack];
if (userId) {
//step 3 - get access token
[DBLoaderHUD showDBLoaderInView:self.view];
[self getAccessTokenForUserId:userId];
}
//ugly patchup to fix an invalid token bug
if ([webView.request.URL.absoluteString isEqualToString:#"http://oauth.withings.com/account/authorize?"])
[self startOAuthFlow];
}
- (NSString *)isAuthorizeCallBack
{
NSString *fullUrlString = webView.request.URL.absoluteString;
if (!fullUrlString)
return nil;
NSArray *arr = [fullUrlString componentsSeparatedByString:#"?"];
if (!arr || arr.count!=2)
return nil;
if (![arr[0] isEqualToString:CALL_BACK_URL])
return nil;
NSString *resultString = arr[1];
NSArray *arr2 = [resultString componentsSeparatedByString:#"&"];
if (!arr2 || arr2.count!=3)
return nil;
NSString *userCred = arr2[0];
NSArray *arr3 = [userCred componentsSeparatedByString:#"="];
if (!arr3 || arr3.count!=2)
return nil;
if (![arr3[0] isEqualToString:#"userid"])
return nil;
return arr3[1];
}
- (void)startOAuthFlow
{
[self step1];
[self step2];
}
and finally - step 3 - get access token
- (void)getAccessTokenForUserId:(NSString *)userId
{
//step 3 - get access token
//withings additional params
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:CALL_BACK_URL forKey:#"oauth_callback"];
[dict setObject:userId forKey:#"userid"];
//init request
NSURLRequest *rq = [TDOAuth URLRequestForPath:#"/access_token" GETParameters:dict scheme:#"https" host:#"oauth.withings.com/account" consumerKey:WITHINGS_OAUTH_KEY consumerSecret:WITHINGS_OAUTH_SECRET accessToken:token tokenSecret:tokenSecret];
//fire request
NSURLResponse* response;
NSError* error = nil;
NSData* result = [NSURLConnection sendSynchronousRequest:rq returningResponse:&response error:&error];
NSString *s = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
//parse result
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSArray *split = [s componentsSeparatedByString:#"&"];
for (NSString *str in split){
NSArray *split2 = [str componentsSeparatedByString:#"="];
[params setObject:split2[1] forKey:split2[0]];
}
[self finishedAthourizationProcessWithUserId:userId AccessToken:params[#"oauth_token"] AccessTokenSecret:params[#"oauth_token_secret"]];
}
I additionaly save request headers here
NSMutableDictionary *dict2 = [NSMutableDictionary dictionary];
[dict2 setObject:CALL_BACK_URL forKey:#"oauth_callback"];
NSURLRequest *rq2 = [TDOAuth URLRequestForPath:#"/authorize"
GETParameters:dict2
scheme:#"https"
host:#"oauth.withings.com/account"
consumerKey:WITHINGS_OAUTH_KEY
consumerSecret:WITHINGS_OAUTH_SECRET
accessToken:self.token
tokenSecret:self.tokenSecret];
headers = rq2.allHTTPHeaderFields;
And in callback method i will add missing parameters to the request. By doing it this way, i avoid "ugly patchup fix".
- (BOOL)webView:(UIWebView *)wV shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
if (![request.allHTTPHeaderFields objectForKey:#"Authorization"] &&
[request.URL.absoluteString rangeOfString:#"acceptDelegation=true"].location == NSNotFound){
NSMutableURLRequest *mutableCp = [request mutableCopy];
NSLog(#"request :::%#", request);
[mutableCp setAllHTTPHeaderFields:headers];
dispatch_async(dispatch_get_main_queue(), ^{
[webView loadRequest:mutableCp];
});
return NO;
}
return YES;
}
I hope it will help somebody
I would suggest you to check this project both as a reference and as a really working OAuth class. It inherits from another great project, so you you will need to add both in yours.Check if the license will suits your requirements.
https://github.com/rsieiro/RSOAuthEngine

Resources