Currency converter

Discussion in 'Mac Programming' started by abcdefg12345, Dec 21, 2014.

  1. abcdefg12345 macrumors regular

    abcdefg12345

    Joined:
    Jul 10, 2013
    #1
    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
     
  2. JohnsonK macrumors regular

    Joined:
    Mar 6, 2014
    #2
    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
     
  3. abcdefg12345 thread starter macrumors regular

    abcdefg12345

    Joined:
    Jul 10, 2013
    #3
    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];
    }
     
  4. hokan macrumors member

    Joined:
    Mar 18, 2014
    Location:
    Sweden
    #4
    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.
     
  5. chown33, Dec 22, 2014
    Last edited: Dec 22, 2014

    chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #5
    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.
     
  6. abcdefg12345 thread starter macrumors regular

    abcdefg12345

    Joined:
    Jul 10, 2013
    #6
    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
     
  7. hokan macrumors member

    Joined:
    Mar 18, 2014
    Location:
    Sweden
    #7
    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.
     

Share This Page