In this post I'll show and explain about a custom class that I wrote to find updated currency rate.
The class will keep a cache of recently fetched rates. Each rate will be valid for 24 hours only.
Apparently Google have their own web service for converting all kinds of stuff (including currencies, metric units etc.)
The web service is at google.com/ig/calculator (Of course you need to pass a few parameters, like From USD To EUR)
The response is in JSON format (Well - almost...) So I used the SBJSON library available Here.
For performing all the HTTP request i used the ASIHTTPRequest Library available Here.
//
// CurrencyRate.h
// currencyConversionTest
//
// Created by Avraham Shukron on 7/17/11.
// Copyright 2011 appSTUDIO. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ASIHTTPRequestDelegate.h"
@protocol CurrecyRateDelegate;
@class ASIHTTPRequest;
@interface CurrencyRateFinder : NSObject
{
NSString *sourceCurrency;
NSString *destCurrency;
id delegate;
}
@property (nonatomic, copy) NSString *sourceCurrency;
@property (nonatomic, copy) NSString *destCurrency;
@property (nonatomic , assign) id delegate;
-(void)fetchRateForCenversionFrom : (NSString*) source to : (NSString*)dest withDelegate : (id) delegate;
@end
@protocol CurrecyRateDelegate
@required
-(void) currencyRate : (CurrencyRateFinder *)currencyRate didFetchRate : (double) rate;
-(void) currencyRateDidFailFethingRate : (CurrencyRateFinder *) currecyRate;
@end
So, what we have is basically a declaration of a class that contains two instance variables:
Those two variables represents the two currencies that the CurrencyRateFinder object will find the conversion rate between them.
We also have a reference to a delegate object that will conform to the CurrencyRateDelegate that we're declaring below.
The main (and only) method that we're exposing is fetchRateForCenversionFrom:to:withDelegate:. This is the method that someone might call to get the conversion rate between two currencies.
The reason we use delegation is because when we'll go to the internet to fetch the rate, we'll do in asynchronously. We do that so we won't block the app while the HTTP request is made.
@interface CurrencyRateFinder() @property (nonatomic , retain) ASIHTTPRequest *request; @end @implementation CurrencyRateFinder @synthesize sourceCurrency; @synthesize destCurrency; @synthesize delegate; @synthesize request;
So the first thing we do is to declare another property of ASIHTTPRequest object. This will be the object that we will use to make all the HTTP request to the web server. The reason that we want to keep a reference to it is so we could cancel ongoing request if we'll need to.
Now let's take a look on the implementation of the most important method that we declared on the .h file:
-(void)fetchRateForCenversionFrom : (NSString*) source to : (NSString*)dest withDelegate : (id) aDelegate;
{
self.delegate = aDelegate;
self.sourceCurrency = source;
self.destCurrency = dest;
if (!self.sourceCurrency)
{
self.sourceCurrency = @"USD";
}
if (!self.destCurrency)
{
self.destCurrency = @"EUR";
}
double rate = [self savedCurrencyRateBetween:self.sourceCurrency and:self.destCurrency];
if (rate > 0)
{
[self.delegate currencyRate:self didFetchRate:rate];
NSLog(@"Returning saved convertion rate - %g",rate);
}
else
{
[self downloadConversionRate];
}
}
First ,we set all the appropriate fields. By default we looking for the ratio between Euro and US Dollar (Completely arbitrary).
Then, we are trying to see if there is a valid saved ratio by calling savedCurrencyRateBetween:and: If the returned ratio is 0 (which is invalid of course) then we're going to download the new ratio by calling downloadConversionRate.
So first, lets see how do we download conversion rate from the internet:
- (void) downloadConversionRate
{
self.request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:
[NSString stringWithFormat:
WEB_SERVICE_URL,
sourceCurrency,destCurrency]]];
self.request.delegate = self;
[request startAsynchronous];
}
This is really simple. First we create the HTTP request (Which is an ASIHTTPRequest) and we set the URL to the web service, and we put in that URL the desired currencies as GET parameters for the request.
Then we set self as the delegate, and calling startAsynchronous to launch the request on the background.
When the request will finish, the following delegate method will be invoked:
-(void) requestFinished:(ASIHTTPRequest *)_request
{
double rate = [self parseResponseToCurrencyRate:_request.responseData];
if (rate < 0)
{
[self.delegate currencyRateDidFailFethingRate:self];
}
else
{
[self saveCurrencyRate:rate between:self.sourceCurrency and:self.destCurrency];
[self.delegate currencyRate:self didFetchRate:rate];
}
NSLog(@"CurrencyRate - connction did finish loading");
}
The firs thing we do is parsing the response and checking if we got a valid result:
-(double)parseResponseToCurrencyRate:(NSData*)data
{
NSString *jsonString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
jsonString = [jsonString stringByReplacingOccurrencesOfString:@"lhs" withString:@"\"lhs\""];
jsonString = [jsonString stringByReplacingOccurrencesOfString:@"rhs" withString:@"\"rhs\""];
jsonString = [jsonString stringByReplacingOccurrencesOfString:@"error" withString:@"\"error\""];
jsonString = [jsonString stringByReplacingOccurrencesOfString:@"icc" withString:@"\"icc\""];
NSDictionary *object = [jsonString JSONValue];
NSString *rate = [object objectForKey:@"rhs"];
if (rate)
{
@try
{
return [rate floatValue];
}
@catch (id e)
{
return -1;
}
}
return -1;
}
The reason for this string replacement mess, is that apparently Google's JSON response are not well-formatted, because all the keys are not enclosed with parenthesis as they should be.
This is an example for a response:
{lhs: "1 U.S. dollar",rhs: "0.707063565 Euros",error: "",icc: true}
And this is how it should be:
{"lhs": "1 U.S. dollar","rhs": "0.707063565 Euros","error": "","icc": true}
So after we fix that problem we can make an NSDictionary out of this JSON string, and then retrieving the currency rate with the key "rhs".
If there is an actual value there - We returning it
If there isn't - We returning -1 to indicate error.
Once we got a valid result for conversion rate between currencies, we want to cache it so we won't need to download it again.
Of course we need to take into consideration that conversion rates are changing daily, so cached result are valid only for 24 hours.
-(void) saveCurrencyRate : (double)rate between : (NSString *) src and : (NSString *)dest
{
NSNumber *drate = [NSNumber numberWithDouble:rate];
NSDictionary *rateData = [NSDictionary dictionaryWithObjectsAndKeys:drate,@"Rate",[NSDate date],@"Date",nil];
[[NSUserDefaults standardUserDefaults] setObject:rateData forKey:KEY_FOR_CONVERSION_RATE(src,dest)];
[[NSUserDefaults standardUserDefaults] synchronize];
}
We can get back saved rate if there is any with this method:
-(double) savedCurrencyRateBetween : (NSString *)src and : (NSString *)dest
{
NSDictionary *rateData = [[NSUserDefaults standardUserDefaults] objectForKey:KEY_FOR_CONVERSION_RATE(src,dest)];
if (rateData)
{
NSDate *rateDate = [rateData objectForKey:@"Date"];
if (rateDate)
{
if ([rateDate timeIntervalSinceNow] < (24*60*60))
{
return [[rateData objectForKey:@"Rate"] doubleValue];
}
}
}
return -1;
}
In order to find conversion rate all you need to do is to create a CurrencyRateFinder object, call "fetchRateForCenversionFrom:to:delegate:" and pass the source and destination currencies and the delegate.
The delegate will be notified when the rate is ready, whether from the internet or from the cache.
Example:
CurrencyRateFinder *crf = [[CurrencyRateFinder alloc] init]; [crf fetchRateForCenversionFrom : @"USD" to : @"ILS" withDelegate : self];
Good luck!