0

Very strange behaviour...I've used before an NSNumber category called NSNumber+Currencies. Yesterday I've changed everything including core data to NSDecimalNumber so I have now a class NSDecimalNumber+Currencies. There is really no link at all! to NSNumber+Currencies anymore. However if I delete it and try to run my app in the simulator, my app crashes with the message "unrecognized selector sent to instance ..." This instance is a NSDecimalNumber and the methods I use in this category are basically the same as before. And I really import ONLY the NSDecimalNumber+Currencies. But it makes no sense...I've deleted the app from the simulator, cleaned my project, closed Xcode, removed the old category even manually from the build phases but nothing helps..is there another trick? If I leave this old category in it it runs smoothly...

My error log. However after the 2nd run my app doesn't start at all and the error message is a different one:

2014-01-28 11:51:29.458 NetIncome[2341:70b] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 0 beyond bounds for empty array'
*** First throw call stack:
(
    0   CoreFoundation                      0x01ecf5e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x01abe8b6 objc_exception_throw + 44
    2   CoreFoundation                      0x01e839c2 -[__NSArrayI objectAtIndex:] + 210
    3   CoreData                            0x01899c1f -[NSFetchedResultsController objectAtIndexPath:] + 255
    4   NetIncome                           0x00003dee -[MainCategoriesViewController tableView:cellForRowAtIndexPath:] + 206
    5   UIKit                               0x003ac61f -[UITableView _createPreparedCellForGlobalRow:withIndexPath:] + 412
    6   UIKit                               0x003ac6f3 -[UITableView _createPreparedCellForGlobalRow:] + 69
    7   UIKit                               0x00390774 -[UITableView _updateVisibleCellsNow:] + 2378
    8   UIKit                               0x0038eb81 -[UITableView _updateVisibleCellsImmediatelyIfNecessary] + 66
    9   UIKit                               0x0039cb5f -[UITableView _visibleCells] + 35
    10  UIKit                               0x0039cbb4 -[UITableView visibleCells] + 33
    11  UIKit                               0x0039fcc0 -[UITableView setSeparatorStyle:] + 115
    12  NetIncome                           0x000037d4 -[MainCategoriesViewController styleTableView] + 100
    13  NetIncome                           0x0000300a -[MainCategoriesViewController viewDidLoad] + 1194
    14  UIKit                               0x003d1318 -[UIViewController loadViewIfRequired] + 696
    15  UIKit                               0x003f6b15 -[UINavigationController _layoutViewController:] + 39
    16  UIKit                               0x003f702b -[UINavigationController _updateScrollViewFromViewController:toViewController:] + 235
    17  UIKit                               0x003f7123 -[UINavigationController _startTransition:fromViewController:toViewController:] + 78
    18  UIKit                               0x003f809c -[UINavigationController _startDeferredTransitionIfNeeded:] + 645
    19  UIKit                               0x003f8cb9 -[UINavigationController __viewWillLayoutSubviews] + 57
    20  UIKit                               0x00532181 -[UILayoutContainerView layoutSubviews] + 213
    21  UIKit                               0x00328267 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 355
    22  libobjc.A.dylib                     0x01ad081f -[NSObject performSelector:withObject:] + 70
    23  QuartzCore                          0x001972ea -[CALayer layoutSublayers] + 148
    24  QuartzCore                          0x0018b0d4 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 380
    25  QuartzCore                          0x00197235 -[CALayer layoutIfNeeded] + 160
    26  UIKit                               0x003e3613 -[UIViewController window:setupWithInterfaceOrientation:] + 304
    27  UIKit                               0x00302177 -[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:isRotating:] + 5212
    28  UIKit                               0x00300d16 -[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:] + 82
    29  UIKit                               0x00300be8 -[UIWindow _setRotatableViewOrientation:updateStatusBar:duration:force:] + 117
    30  UIKit                               0x00300c70 -[UIWindow _setRotatableViewOrientation:duration:force:] + 67
    31  UIKit                               0x002ffd0a __57-[UIWindow _updateToInterfaceOrientation:duration:force:]_block_invoke + 120
    32  UIKit                               0x002ffc6c -[UIWindow _updateToInterfaceOrientation:duration:force:] + 400
    33  UIKit                               0x003009c3 -[UIWindow setAutorotates:forceUpdateInterfaceOrientation:] + 870
    34  UIKit                               0x00303fb6 -[UIWindow setDelegate:] + 449
    35  UIKit                               0x003d5737 -[UIViewController _tryBecomeRootViewControllerInWindow:] + 180
    36  UIKit                               0x002f9c1c -[UIWindow addRootViewControllerViewIfPossible] + 609
    37  UIKit                               0x002f9d97 -[UIWindow _setHidden:forced:] + 312
    38  UIKit                               0x002fa02d -[UIWindow _orderFrontWithoutMakingKey] + 49
    39  UIKit                               0x0030489a -[UIWindow makeKeyAndVisible] + 65
    40  UIKit                               0x002b7cd0 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1851
    41  UIKit                               0x002bc3a8 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 824
    42  UIKit                               0x002d087c -[UIApplication handleEvent:withNewEvent:] + 3447
    43  UIKit                               0x002d0de9 -[UIApplication sendEvent:] + 85
    44  UIKit                               0x002be025 _UIApplicationHandleEvent + 736
    45  GraphicsServices                    0x035202f6 _PurpleEventCallback + 776
    46  GraphicsServices                    0x0351fe01 PurpleEventCallback + 46
    47  CoreFoundation                      0x01e4ad65 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 53
    48  CoreFoundation                      0x01e4aa9b __CFRunLoopDoSource1 + 523
    49  CoreFoundation                      0x01e7577c __CFRunLoopRun + 2156
    50  CoreFoundation                      0x01e74ac3 CFRunLoopRunSpecific + 467
    51  CoreFoundation                      0x01e748db CFRunLoopRunInMode + 123
    52  UIKit                               0x002bbadd -[UIApplication _run] + 840
    53  UIKit                               0x002bdd3b UIApplicationMain + 1225
    54  NetIncome                           0x000027ad main + 141
    55  libdyld.dylib                       0x031c070d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

EDIT2: And this is the original error message, only shown once and immediately after deleting the relevant files:

2014-01-28 12:00:15.598 NetIncome[2872:70b] -[__NSCFNumber getLocalizedCurrencyString]: unrecognized selector sent to instance 0x8c59720
2014-01-28 12:00:15.602 NetIncome[2872:70b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber getLocalizedCurrencyString]: unrecognized selector sent to instance 0x8c59720'
*** First throw call stack:
(
    0   CoreFoundation                      0x01ecf5e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x01abe8b6 objc_exception_throw + 44
    2   CoreFoundation                      0x01f6c903 -[NSObject(NSObject) doesNotRecognizeSelector:] + 275
    3   CoreFoundation                      0x01ebf90b ___forwarding___ + 1019
    4   CoreFoundation                      0x01ebf4ee _CF_forwarding_prep_0 + 14
    5   NetIncome                           0x000358da -[NetIncomeViewController viewDidLoad] + 954
    6   UIKit                               0x003d1318 -[UIViewController loadViewIfRequired] + 696
    7   UIKit                               0x003f6b15 -[UINavigationController _layoutViewController:] + 39
    8   UIKit                               0x003f702b -[UINavigationController _updateScrollViewFromViewController:toViewController:] + 235
    9   UIKit                               0x003f7123 -[UINavigationController _startTransition:fromViewController:toViewController:] + 78
    10  UIKit                               0x003f809c -[UINavigationController _startDeferredTransitionIfNeeded:] + 645
    11  UIKit                               0x003f8cb9 -[UINavigationController __viewWillLayoutSubviews] + 57
    12  UIKit                               0x00532181 -[UILayoutContainerView layoutSubviews] + 213
    13  UIKit                               0x00328267 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 355
    14  libobjc.A.dylib                     0x01ad081f -[NSObject performSelector:withObject:] + 70
    15  QuartzCore                          0x001972ea -[CALayer layoutSublayers] + 148
    16  QuartzCore                          0x0018b0d4 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 380
    17  QuartzCore                          0x0018af40 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 26
    18  QuartzCore                          0x000f2ae6 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 294
    19  QuartzCore                          0x000f3e71 _ZN2CA11Transaction6commitEv + 393
    20  QuartzCore                          0x001b0430 +[CATransaction flush] + 52
    21  UIKit                               0x002d9dc9 _afterCACommitHandler + 131
    22  CoreFoundation                      0x01e974ce __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
    23  CoreFoundation                      0x01e9741f __CFRunLoopDoObservers + 399
    24  CoreFoundation                      0x01e75344 __CFRunLoopRun + 1076
    25  CoreFoundation                      0x01e74ac3 CFRunLoopRunSpecific + 467
    26  CoreFoundation                      0x01e748db CFRunLoopRunInMode + 123
    27  GraphicsServices                    0x0351e9e2 GSEventRunModal + 192
    28  GraphicsServices                    0x0351e809 GSEventRun + 104
    29  UIKit                               0x002bdd3b UIApplicationMain + 1225
    30  NetIncome                           0x00002b3d main + 141
    31  libdyld.dylib                       0x031c070d start + 1
    32  ???                                 0x00000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

The lines of code my app crashes the first time (the last line is the one):

//Get or set and get the gross income
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDecimalNumber *grossIncome = [defaults objectForKey:@"grossincome"];
    if (!grossIncome) {
        [defaults setObject:[NSDecimalNumber zero] forKey:@"grossincome"];
        [defaults synchronize];
        grossIncome = (NSDecimalNumber *)[defaults objectForKey:@"grossincome"];
    }
    self.incomeValueTextField.enabled = NO;
    self.incomeValueTextField.delegate = self;
    self.incomeValueTextField.keyboardType = UIKeyboardTypeDecimalPad;
    self.incomeValueTextField.text = [grossIncome getLocalizedCurrencyString];

And my complete category:

#import "NSDecimalNumber+Currency.h"
#import "Singletons.h"

@implementation NSDecimalNumber (Currency)

- (NSString *) getLocalizedCurrencyString
{
    NSNumberFormatter *numberFormatter = [[Singletons sharedManager] numberFormatter];

    NSString *numberString = [numberFormatter stringFromNumber:self];

    return numberString;
}

- (NSString *) getLocalizedCurrencyStringWithDigits:(int)digits
{
    if(digits == 2){
        return [self getLocalizedCurrencyString];
    }

    NSNumberFormatter *numberFormatter;

    if (digits == 0){
        numberFormatter =[[Singletons sharedManager] numberFormatterNoDigits];
    } else {
        numberFormatter =[[NSNumberFormatter alloc] init];
        [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
        [numberFormatter setLocale:[NSLocale currentLocale]];
        [numberFormatter setMinimumFractionDigits:digits];
        [numberFormatter setMaximumFractionDigits:digits];
    }

    NSString *numberString = [numberFormatter stringFromNumber:self];

    return numberString;
}

+ (NSDecimalNumber *) getUnLocalizedDecimalNumberWithString:(NSString *)currencyString
{
    NSNumberFormatter *numberFormatter = [[Singletons sharedManager] numberFormatter];

    NSNumber *formatedCurrency = [numberFormatter numberFromString:currencyString];

    BOOL isDecimal = formatedCurrency != nil;
    if(isDecimal){
        return [NSDecimalNumber decimalNumberWithDecimal:[formatedCurrency decimalValue]];
    } else {
        return [NSDecimalNumber zero];
    }
}

- (NSDecimalNumber *)abs {
    if ([self compare:[NSDecimalNumber zero]] == NSOrderedAscending) {
        // Number is negative. Multiply by -1
        NSDecimalNumber * negativeOne = [NSDecimalNumber decimalNumberWithMantissa:1
                                                                          exponent:0
                                                                        isNegative:YES];
        return [self decimalNumberByMultiplyingBy:negativeOne];
    } else {
        return self;
    }
}
MichiZH
  • 5,587
  • 12
  • 41
  • 81
  • 1
    show your error log fully... this is not enough `unrecognized selector sent to instance`? – Mani Jan 28 '14 at 10:50
  • 1
    that isn't an unrecognised selector crash. That's an array out of bounds crash. – jrturton Jan 28 '14 at 10:54
  • 1
    use exception breaking point to find where and which line is exception caught – codercat Jan 28 '14 at 10:55
  • I don't even know where to look, because before I delete this class everything works perfect! I can give you the lines of code my app crashes first and the relevant category and NSDecimalNumber in a further edit – MichiZH Jan 28 '14 at 11:09

4 Answers4

1

Yesterday you have been warned about subclassing/categories on class clusters, now you see why. (Okay, actually it's more or a problem with unexpected behaviour of NSUserDefaults, but it's related ^^)

Your problem is that the object you get from NSUserDefaults is not a NSDecimalNumber, because NSDecimalNumbers are not plist values that can be saved in NSUserDefaults. But NSDecimalNumber is a subclass of NSNumber, which can be saved in NSUserDefaults, so NSUserDefaults will only save the "NSNumber part" of the NSDecimalNumber, basically it converts it to a NSNumber. Through this process it loses its precision and it's class.

When you get the object from NSUserDefaults you will actually get an instance of NSNumber. And your category does not exist on NSNumber.


I added some debug log to your code and ran it for you:

// Wrong!
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithString:@"1.23456789123456789"];
NSLog(@"%@ - %@", decimalNumber, NSStringFromClass([decimalNumber class]));
[[NSUserDefaults standardUserDefaults] setObject:decimalNumber forKey:@"___number"];
NSDecimalNumber *objectFromUserDefaults = [[NSUserDefaults standardUserDefaults] objectForKey:@"___number"];
NSLog(@"%@ - %@", objectFromUserDefaults, NSStringFromClass([objectFromUserDefaults class]));

Which yields this result:

xxx[98912:70b] 1.23456789123456789 - NSDecimalNumber
xxx[98912:70b] 1.234567891234568 - __NSCFNumber

As you can see the returned object is an instance of __NSCFNumber

Here is the correct way:

// Correct
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithString:@"1.23456789123456789"];
NSLog(@"%@ - %@", decimalNumber, NSStringFromClass([decimalNumber class]));
NSData *archivedDecimalNumber = [NSKeyedArchiver archivedDataWithRootObject:decimalNumber];
[[NSUserDefaults standardUserDefaults] setObject:archivedDecimalNumber forKey:@"___archivedNumber"];
NSData *dataFromUserDefaults = [[NSUserDefaults standardUserDefaults] objectForKey:@"___archivedNumber"];
NSDecimalNumber *decimalNumberFromUserDefaults = [NSKeyedUnarchiver unarchiveObjectWithData:dataFromUserDefaults];
NSLog(@"%@ - %@", decimalNumberFromUserDefaults, NSStringFromClass([decimalNumberFromUserDefaults class]));

which yields:

xxx[98912:70b] 1.23456789123456789 - NSDecimalNumber
xxx[98912:70b] 1.23456789123456789 - NSDecimalNumber

as expected.

BUT you should still get rid of that NSDecimalNumber category. Just put that code into it's own class. Call it ZHNumberFormatter and do your formatting in there.

NSNumberFormatters take their time during creation, you don't want to create them for every single conversion.

Community
  • 1
  • 1
Matthias Bauch
  • 89,811
  • 20
  • 225
  • 247
  • Yeah I learned a lot now :-) I still have the category but just save a double now for this one particular value in the user defaults. Now it works. And I've created a Singleton for my NumberFormatter so I'm just using one in the whole app. Thx alot for your very thorough analysis of my code! – MichiZH Jan 28 '14 at 14:00
0

Reset your simulator and try to run the app.

iOS Simulator --> Reset content and settings.

Preet
  • 41
  • 4
  • I did...it's always the same. First my app runs good until I access the tab this line of code in my edit above is. Then it crashes with the 2nd error message. When I try to reopen my app I don't even see the first screen and the first error message appears. I don't know what to do :-( – MichiZH Jan 28 '14 at 11:26
  • Allocate object and then save to NSUserDefalut. Try the Below code it worked. NSDecimalNumber *decNum = [[NSDecimalNumber alloc] initWithDouble:0.0f]; [decNum getLocalizedCurrencyString]; – Preet Jan 28 '14 at 11:44
0

User NSKeyedArchiver and NSKeyedUnarchiver to store the object in user defaults. Try to use below code its working fine.

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDecimalNumber *grossIncome = [defaults objectForKey:@"grossincome"];
if (!grossIncome) {
    NSDecimalNumber *decNum = [[NSDecimalNumber alloc] initWithDouble:0.0f];
    NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:decNum];
    [defaults setObject:myEncodedObject forKey:@"grossincome"];
    [defaults synchronize];

    NSData *myDecodedObject = [defaults objectForKey:@"grossincome"];
    grossIncome = (NSDecimalNumber*)[NSKeyedUnarchiver unarchiveObjectWithData:myDecodedObject];

}
[grossIncome getLocalizedCurrencyString];
Preet
  • 41
  • 4
  • Could you explain why I need to do that? And does this affect any items stored in NSUserDefaults if I access them "normally"? I was able to narrow it down. It definitely is a problem of these lines of code when I delete this file. And after the crash my app doesn't start anymore because my NSFetchedResultsController cache keeps crashing the app. Haven't found out why yet but when I remove the cache it works again. – MichiZH Jan 28 '14 at 12:09
  • It won't affect the other items stored in – Preet Jan 28 '14 at 12:19
-1

You probably still refer to the category somewhere. You should create an exception Breakpoint. (look for "create exception Breakpoint" in the help menu) This will stop you on the guilty line of code!

Florian Burel
  • 3,408
  • 1
  • 19
  • 20
  • Thx see my edit, I have the line of code causing this. Even though it makes no sense at all since my old class isn't used here.. – MichiZH Jan 28 '14 at 11:12
  • 1
    This is better off as a comment, rather than an answer. – Abizern Jan 28 '14 at 11:17
  • The Log suggest that you are calling the getLocalizedCurrencyString on a NSNumber. Reading your code, I have to assume there is a NSNumber already stored in your preference (probably from an old run before you change from NSNumber to NSDecimalNumber) Try deleting your app from your test device / simulator et reinstalling it again – Florian Burel Jan 28 '14 at 11:21
  • Yeah I thought that too..I've deleted it everywhere, Iphone, Simulator. Restored settings, cleaned. But it doesn't stop crashing. The strange thing is I've even restored all files from yesterday evening with Time Machine and everything worked yesterday perfectly. But now nothing launches anymore even with ALL the files from yesterday! What did I do wrong? :-( I could cry right now... – MichiZH Jan 28 '14 at 11:58