Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

abcdefg12345

macrumors 6502
Original poster
Jul 10, 2013
281
86
Hi

I'm trying to make a currency converter and came across this code which does the conversion i just have to import it to my project, the code is ~4 years old and some things have been deprecated so i had to modify it. can someone check if I've done it correctly and if its updating currencies correctly.

and for the sandboxing i enabled it and checked the incoming and outgoing connections, will that be allowed on the App Store

i attached all the .h and .m files that i modified and the AppDelegate.h and AppDelegate.m files which i used it to convert from Australian to US dollars

View attachment Archive.zip
 

JohnsonK

macrumors regular
Mar 6, 2014
142
0
Ask specific questions, not many of us are willing to download files randomly, for security reasons, or going through entire projects checking for mistakes :p
 

abcdefg12345

macrumors 6502
Original poster
Jul 10, 2013
281
86
Ask specific questions, not many of us are willing to download files randomly, for security reasons, or going through entire projects checking for mistakes :p

most of the things i changed wore the
Code:
release
and
Code:
autorelease
in the codes i swapped them with
Code:
init
and
Code:
hash
it is converting but the numbers are not completely the same as the numbers i get from other currency converters for example 1 AUD is giving me 0.82 USD which is the same as the one on the apple calculator while if i put 50 AUD i get 40.93 USD and the apple calculator shows 40.75.

heres the one i had to edit, you don't have to download it you can just read it if ur scared ill give you a virus:D


original
Code:
#import "DDCurrencyUnitConverter.h"
#import <dispatch/dispatch.h>

@interface DDCurrencyUnitConverterConnectionDelegate : NSObject {
@private
    NSError *error;
    NSMutableData *data;
    NSStringEncoding encoding;
    BOOL finished;
}
@property (nonatomic, getter=isFinished) BOOL finished;
@property (nonatomic, retain) NSError *error;
@property (nonatomic, readonly) NSString *string;
@end

@implementation DDCurrencyUnitConverterConnectionDelegate
@synthesize finished;
@synthesize error;

- (id)init {
    self = [super init];
    if (self) {
        encoding = NSMacOSRomanStringEncoding;
        [self setFinished:NO];
    }
    return self;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
    NSString *contentLength = [[response allHeaderFields] objectForKey:@"Content-Length"];
    NSUInteger length = [contentLength intValue];
    data = [[NSMutableData alloc] initWithLength:length];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)chunk {
    [data appendData:chunk];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self setError:nil];
    [self setFinished:YES];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)anError {
    [self setError:anError];
    [self setFinished:YES];
}

- (NSString *)string {
    return [[[NSString alloc] initWithData:data encoding:encoding] autorelease];
}

- (void)dealloc {
    [error release];
    [data release];
    [super dealloc];
}

@end

static NSString *_DDCurrencyNames[] = {
    @"Euro",
    @"Japanese Yen",
    @"U.K. Pound Sterling",
    @"U.S. Dollar",
    @"Algerian Dinar",
    @"Argentine Peso",
    @"Australian Dollar",
    @"Bahrain Dinar",
    @"Botswana Pula",
    @"Brazilian Real",
    @"Brunei Dollar",
    @"Canadian Dollar",
    @"Chilean Peso",
    @"Chinese Yuan",
    @"Colombian Peso",
    @"Czech Koruna",
    @"Danish Krone",
    @"Hungarian Forint",
    @"Icelandic Krona",
    @"Indian Rupee",
    @"Indonesian Rupiah",
    @"Iranian Rial",
    @"Israeli New Sheqel",
    @"Kazakhstani Tenge",
    @"Korean Won",
    @"Kuwaiti Dinar",
    @"Libyan Dinar",
    @"Malaysian Ringgit",
    @"Mauritian Rupee",
    @"Mexican Peso",
    @"Nepalese Rupee",
    @"New Zealand Dollar",
    @"Norwegian Krone",
    @"Rial Omani",
    @"Pakistani Rupee",
    @"Nuevo Sol",
    @"Philippine Peso",
    @"Polish Zloty",
    @"Qatar Riyal",
    @"Russian Ruble",
    @"Saudi Arabian Riyal",
    @"Singapore Dollar",
    @"South African Rand",
    @"Sri Lanka Rupee",
    @"Swedish Krona",
    @"Swiss Franc",
    @"Thai Baht",
    @"Trinidad And Tobago Dollar",
    @"Tunisian Dinar",
    @"U.A.E. Dirham",
    @"Peso Uruguayo",
    @"Bolivar Fuerte",
    @"SDR"
};
static NSMutableDictionary *_DDCurrencyExchangeRates = nil;
static dispatch_queue_t updateQueue = nil;

@implementation DDUnitConverter (DDCurrencyUnitConverter)

+ (id) currencyUnitConverter {
	return [[[DDCurrencyUnitConverter alloc] init] autorelease];
}

@end


@implementation DDCurrencyUnitConverter

+ (NSString *)nameOfCurrencyUnit:(DDCurrencyUnit)unit {
    if (unit > DDCurrencyUnitSDR) { return nil; }
    return _DDCurrencyNames[unit];
}

+ (NSError *) refreshExchangeRatesInBackground {
	if ([NSThread currentThread] == [NSThread mainThread]) { return nil; }
    
	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	NSURL * imfURL = [NSURL URLWithString:@"http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y"];
    NSURLRequest *imfRequest = [NSURLRequest requestWithURL:imfURL];
    DDCurrencyUnitConverterConnectionDelegate *tmpDelegate = [[DDCurrencyUnitConverterConnectionDelegate alloc] init];
    
    NSURLConnection *imfConnection = [[NSURLConnection alloc] initWithRequest:imfRequest delegate:tmpDelegate];
    while ([tmpDelegate isFinished] == NO) {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
    }
    [imfConnection release];
    
    NSString *raw = [tmpDelegate string];
    NSError *error = [tmpDelegate error];
	
	if (error == nil) {
		NSArray * rows = [raw componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
		BOOL hasStartedCulling = NO;
		short rowIndex = 0;
		for (NSString * row in rows) {
			if ([row length] == 0) { continue; }
			if (hasStartedCulling == NO) {
				if ([row hasPrefix:@"Currency"]) {
					hasStartedCulling = YES;
				}
			} else {
				if ([row hasPrefix:@"Currency"]) {
					break;
				} else {
					NSArray * fields = [row componentsSeparatedByString:@"\t"];
                    NSString *currencyName = [fields objectAtIndex:0];
                    
					double conversionValue = 0.0f;
					for (int i = 1; i < [fields count]; ++i) {
						NSString * field = [fields objectAtIndex:i];
						if ([field length] > 0) {
							conversionValue = [field doubleValue];
							break;
						}
					}
                    [_DDCurrencyExchangeRates setObject:[NSDecimalNumber numberWithDouble:conversionValue] forKey:currencyName];
					rowIndex++;
				}
			}
		}
	}
	
    [error retain];
    [tmpDelegate release];
    
	[pool drain];
    
    return [error autorelease];
}

+ (void) initialize {
	if (self == [DDCurrencyUnitConverter class]) {
        updateQueue = dispatch_queue_create("com.davedelong.ddunitconverter", 0);
        _DDCurrencyExchangeRates = [[NSMutableDictionary alloc] init];
        
        dispatch_async(updateQueue, ^{
            [self refreshExchangeRatesInBackground];
        });
	}
}

+ (NSDecimalNumber *) multiplierForUnit:(DDUnit)unit {
	NSDecimalNumber * multiplier = [NSDecimalNumber one];
	if (unit < DDCurrencyUnitSDR) {
        NSString *name = _DDCurrencyNames[unit];
        multiplier = [_DDCurrencyExchangeRates objectForKey:name];
        if (multiplier == nil) {
            NSLog(@"unknown currency: %@ (%lu)", name, unit);
            multiplier = [NSDecimalNumber one];
        }
	}
	return multiplier;
}

- (NSNumber *) convertNumber:(NSNumber *)number fromUnit:(DDUnit)from toUnit:(DDUnit)to {
    dispatch_sync(updateQueue, ^{ });
    return [super convertNumber:number fromUnit:from toUnit:to];
}

- (void) refreshExchangeRates {
    [self refreshExchangeRatesWithCompletion:NULL];
}

- (void)refreshExchangeRatesWithCompletion:(void (^)(NSError *))completionHandler {
    
    dispatch_queue_t currentQueue = dispatch_get_main_queue();
    completionHandler = [completionHandler copy];
    dispatch_async(updateQueue, ^{
        NSError *error = [[self class] refreshExchangeRatesInBackground];
        if (completionHandler != NULL) {
            //wrap the completion handler in another block so we can capture the error
            dispatch_block_t block = ^{
                completionHandler(error);
            };
            dispatch_async(currentQueue, block);
        }
    });
    [completionHandler release];
}


mine
Code:
#import "DDCurrencyUnitConverter.h"
#import <dispatch/dispatch.h>

@interface DDCurrencyUnitConverterConnectionDelegate : NSObject {
@private
    NSError *error;
    NSMutableData *data;
    NSStringEncoding encoding;
    BOOL finished;
}
@property (nonatomic, getter=isFinished) BOOL finished;
@property (nonatomic, retain) NSError *error;
@property (nonatomic, readonly) NSString *string;
@end

@implementation DDCurrencyUnitConverterConnectionDelegate
@synthesize finished;
@synthesize error;

- (id)init {
    self = [super init];
    if (self) {
        encoding = NSMacOSRomanStringEncoding;
        [self setFinished:NO];
    }
    return self;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
    NSString *contentLength = [[response allHeaderFields] objectForKey:@"Content-Length"];
    NSUInteger length = [contentLength intValue];
    data = [[NSMutableData alloc] initWithLength:length];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)chunk {
    [data appendData:chunk];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self setError:nil];
    [self setFinished:YES];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)anError {
    [self setError:anError];
    [self setFinished:YES];
}

- (NSString *)string {
    return [[[NSString alloc] initWithData:data encoding:encoding] init];
}

- (void)dealloc {
    [error hash];
    [data hash];
    [super hash];
}

@end

static NSString *_DDCurrencyNames[] = {
    @"Euro",
    @"Japanese Yen",
    @"U.K. Pound Sterling",
    @"U.S. Dollar",
    @"Algerian Dinar",
    @"Argentine Peso",
    @"Australian Dollar",
    @"Bahrain Dinar",
    @"Botswana Pula",
    @"Brazilian Real",
    @"Brunei Dollar",
    @"Canadian Dollar",
    @"Chilean Peso",
    @"Chinese Yuan",
    @"Colombian Peso",
    @"Czech Koruna",
    @"Danish Krone",
    @"Hungarian Forint",
    @"Icelandic Krona",
    @"Indian Rupee",
    @"Indonesian Rupiah",
    @"Iranian Rial",
    @"Israeli New Sheqel",
    @"Kazakhstani Tenge",
    @"Korean Won",
    @"Kuwaiti Dinar",
    @"Libyan Dinar",
    @"Malaysian Ringgit",
    @"Mauritian Rupee",
    @"Mexican Peso",
    @"Nepalese Rupee",
    @"New Zealand Dollar",
    @"Norwegian Krone",
    @"Rial Omani",
    @"Pakistani Rupee",
    @"Nuevo Sol",
    @"Philippine Peso",
    @"Polish Zloty",
    @"Qatar Riyal",
    @"Russian Ruble",
    @"Saudi Arabian Riyal",
    @"Singapore Dollar",
    @"South African Rand",
    @"Sri Lanka Rupee",
    @"Swedish Krona",
    @"Swiss Franc",
    @"Thai Baht",
    @"Trinidad And Tobago Dollar",
    @"Tunisian Dinar",
    @"U.A.E. Dirham",
    @"Peso Uruguayo",
    @"Bolivar Fuerte",
    @"SDR"
};
static NSMutableDictionary *_DDCurrencyExchangeRates = nil;
static dispatch_queue_t updateQueue = nil;

@implementation DDUnitConverter (DDCurrencyUnitConverter)

+ (id) currencyUnitConverter {
	return [[[DDCurrencyUnitConverter alloc] init] init];
}

@end


@implementation DDCurrencyUnitConverter

+ (NSString *)nameOfCurrencyUnit:(DDCurrencyUnit)unit {
    if (unit > DDCurrencyUnitSDR) { return nil; }
    return _DDCurrencyNames[unit];
}

+ (NSError *) refreshExchangeRatesInBackground {
	if ([NSThread currentThread] == [NSThread mainThread]) { return nil; }
    
	NSString * pool = [[NSString alloc] init];
	
	NSURL * imfURL = [NSURL URLWithString:@"http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y"];
    NSURLRequest *imfRequest = [NSURLRequest requestWithURL:imfURL];
    DDCurrencyUnitConverterConnectionDelegate *tmpDelegate = [[DDCurrencyUnitConverterConnectionDelegate alloc] init];
    
    NSURLConnection *imfConnection = [[NSURLConnection alloc] initWithRequest:imfRequest delegate:tmpDelegate];
    while ([tmpDelegate isFinished] == NO) {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
    }
    [imfConnection hash];
    
    NSString *raw = [tmpDelegate string];
    NSError *error = [tmpDelegate error];
	
	if (error == nil) {
		NSArray * rows = [raw componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
		BOOL hasStartedCulling = NO;
		short rowIndex = 0;
		for (NSString * row in rows) {
			if ([row length] == 0) { continue; }
			if (hasStartedCulling == NO) {
				if ([row hasPrefix:@"Currency"]) {
					hasStartedCulling = YES;
				}
			} else {
				if ([row hasPrefix:@"Currency"]) {
					break;
				} else {
					NSArray * fields = [row componentsSeparatedByString:@"\t"];
                    NSString *currencyName = [fields objectAtIndex:0];
                    
					double conversionValue = 0.0f;
					for (int i = 1; i < [fields count]; ++i) {
						NSString * field = [fields objectAtIndex:i];
						if ([field length] > 0) {
							conversionValue = [field doubleValue];
							break;
						}
					}
                    [_DDCurrencyExchangeRates setObject:[NSDecimalNumber numberWithDouble:conversionValue] forKey:currencyName];
					rowIndex++;
				}
			}
		}
	}
	
    [error hash];
    [tmpDelegate hash];
	[pool hash];
    
    return [error init];
}

+ (void) initialize {
	if (self == [DDCurrencyUnitConverter class]) {
        updateQueue = dispatch_queue_create("com.davedelong.ddunitconverter", 0);
        _DDCurrencyExchangeRates = [[NSMutableDictionary alloc] init];
        
        dispatch_async(updateQueue, ^{
            [self refreshExchangeRatesInBackground];
        });
	}
}

+ (NSDecimalNumber *) multiplierForUnit:(DDUnit)unit {
	NSDecimalNumber * multiplier = [NSDecimalNumber one];
	if (unit < DDCurrencyUnitSDR) {
        NSString *name = _DDCurrencyNames[unit];
        multiplier = [_DDCurrencyExchangeRates objectForKey:name];
        if (multiplier == nil) {
            NSLog(@"unknown currency: %@ (%lu)", name, unit);
            multiplier = [NSDecimalNumber one];
        }
	}
	return multiplier;
}

- (NSNumber *) convertNumber:(NSNumber *)number fromUnit:(DDUnit)from toUnit:(DDUnit)to {
    dispatch_sync(updateQueue, ^{ });
    return [super convertNumber:number fromUnit:from toUnit:to];
}

- (void) refreshExchangeRates {
    [self refreshExchangeRatesWithCompletion:NULL];
}

- (void)refreshExchangeRatesWithCompletion:(void (^)(NSError *))completionHandler {
    
    dispatch_queue_t currentQueue = dispatch_get_main_queue();
    completionHandler = [completionHandler copy];
    dispatch_async(updateQueue, ^{
        NSError *error = [[self class] refreshExchangeRatesInBackground];
        if (completionHandler != NULL) {
            //wrap the completion handler in another block so we can capture the error
            dispatch_block_t block = ^{
                completionHandler(error);
            };
            dispatch_async(currentQueue, block);
        }
    });
    [completionHandler hash];
}
 

hokan

macrumors member
Mar 18, 2014
40
3
Sweden
Replacing release and autorelease with init and hash doesn't make any sense at all. Retain/release/autorelease are manual memory management commands that should be replaced by the usage of strong/weak/... when using ARC (Automated Reference Counting) see:

Transitioning to ARC Release Notes

This is also useful: http://amattn.com/p/arc_best_practices.html

init and hash on the other hand do completely different things, init is used to set the @property fields of a allocated object to its initial values, while a hash is only really useful as a internal key/index in certain data collections.

Also note that ARC will create dealloc methods for you so you will only rarely need to write one yourself.
 

chown33

Moderator
Staff member
Aug 9, 2009
10,706
8,347
A sea of green
The init method (or any of its variants) should only be called once on any object. Calling it multiple times may lead to unexpected results.

I understand what you've tried to do, but you need something other than init as a replacement. There are less than a dozen places in the code where autorelease is used in the original. I recommend simply changing the code to not use autorelease, instead of doing a replacement with init.

Another alternative is to disable ARC for this file.


EDIT
... it is converting but the numbers are not completely the same as the numbers i get from other currency converters for example 1 AUD is giving me 0.82 USD which is the same as the one on the apple calculator while if i put 50 AUD i get 40.93 USD and the apple calculator shows 40.75.
Please be specific. Exactly what do you mean by "the apple calculator"? Be specific about exactly which app, which OS version you're using, etc.

Accuracy is important. Maybe "the apple calculator" is using a different currency-rate service than the one in the code you posted. Or maybe one of them is rounding differently than the other.


You need to find out exactly what numbers are being used as the currency conversion multipliers in the posted code. Then carry out the calculations manually, and see if the results from the sample code are the same. This will tell you whether the calculation itself is being done correctly.

If the calculation is being done correctly, then you need to confirm that the data source (i.e. the URLs providing conversion data) is the same for both cases. If not, then you need to look at what data each source is returning, and see if there are any differences, and then whether those differences result in the discrepancy in the results.

Welcome to real-world debugging.

It works the same way as debugging simpler toy programs. That is, you Break Things Down into smaller parts, Confirm Expectations that each part has the expected input and output values, and systematically work your way down through all the smaller parts.
 
Last edited:

abcdefg12345

macrumors 6502
Original poster
Jul 10, 2013
281
86
The init method (or any of its variants) should only be called once on any object. Calling it multiple times may lead to unexpected results.

I understand what you've tried to do, but you need something other than init as a replacement. There are less than a dozen places in the code where autorelease is used in the original. I recommend simply changing the code to not use autorelease, instead of doing a replacement with init.

Another alternative is to disable ARC for this file.


EDIT

Please be specific. Exactly what do you mean by "the apple calculator"? Be specific about exactly which app, which OS version you're using, etc.

Accuracy is important. Maybe "the apple calculator" is using a different currency-rate service than the one in the code you posted. Or maybe one of them is rounding differently than the other.


You need to find out exactly what numbers are being used as the currency conversion multipliers in the posted code. Then carry out the calculations manually, and see if the results from the sample code are the same. This will tell you whether the calculation itself is being done correctly.

If the calculation is being done correctly, then you need to confirm that the data source (i.e. the URLs providing conversion data) is the same for both cases. If not, then you need to look at what data each source is returning, and see if there are any differences, and then whether those differences result in the discrepancy in the results.

Welcome to real-world debugging.

It works the same way as debugging simpler toy programs. That is, you Break Things Down into smaller parts, Confirm Expectations that each part has the expected input and output values, and systematically work your way down through all the smaller parts.

by the apple calculator i meant the calculator app that is preinstalled on OS X, I'm running 10.10.1 its pulling currencies off yahoo and my code is pulling currencies off IMF

http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y

if i disable ARC will it still get accepted for the App Store and will there be any downside to disabling ARC
 

hokan

macrumors member
Mar 18, 2014
40
3
Sweden
if i disable ARC will it still get accepted for the App Store and will there be any downside to disabling ARC

To my knowledge its only GC (Garbage Collection) that was introduced in 10.5 and deprecated 10.8 that's not allowed.

Note that without ARC you will have to add -retain and -release calls manually in the right places. Which is likely to result in more error prone - more memory leaks and over-releases.

While it doesn't hurt to understand manual memory management (as it will help to understand ARC) the trend is definitely towards ARC as Apple has this as the default for Xcode projects and as the way to manage memory in Swift.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.