0

I have a singleton class, in its init method, I have setup an NSMutableArray, and later when I save it, app crashes randomly with this crash log, I am kind of sure that recordUploadCnt can't be nil, but can't find out where goes wrong.

  NSString* const KEY_SENSOR_UPLOAD = @"recordUploadCnt";
  @property(nonatomic, strong) NSMutableArray* recordUploadCnt;

- (id)init {
    if (self = [super init]) {
         NSMutableArray* cnt = [[NSUserDefaults standardUserDefaults] objectForKey:KEY_SENSOR_UPLOAD];
         self.recordUploadCnt = cnt ? [NSMutableArray arrayWithArray:cnt] :  [NSMutableArray new];
    }
    return self;
}

double currentTime = [[NSDate date] timeIntervalSince1970];
if ([self.recordUploadCnt count] < 6) {
    [self.recordUploadCnt addObject:[NSString stringWithFormat:@"%f",currentTime]];
} else {
    [self.recordUploadCnt removeObjectAtIndex:0];
    [self.recordUploadCnt addObject:[NSString stringWithFormat:@"%f",currentTime]];
}
[[NSUserDefaults standardUserDefaults] setObject:self.recordUploadCnt forKey:KEY_SENSOR_UPLOAD];
[[NSUserDefaults standardUserDefaults] synchronize];

Exception Type:  SIGSEGV
Exception Codes: SEGV_MAPERR at 0xebfecbeb8
Crashed Thread:  37
Thread 37 Crashed:
0   libobjc.A.dylib                 0x0000000184980430 _objc_msgSend :16 (in libobjc.A.dylib)
1   CoreFoundation                  0x00000001856bf4c8 __CFPropertyListIsValidAux :52 (in CoreFoundation)
2   CoreFoundation                  0x00000001856c02a4 __CFPropertyListIsArrayPlistAux :40 (in CoreFoundation)
3   CoreFoundation                  0x00000001855e3900 _CFArrayApplyFunction :80 (in CoreFoundation)
4   CoreFoundation                  0x00000001856bf5fc __CFPropertyListIsValidAux :360 (in CoreFoundation)
5   CoreFoundation                  0x00000001855fc414 _CFPropertyListWrite :96 (in CoreFoundation)
6   CoreFoundation                  0x00000001855fbc14 _CFPropertyListCreateData :316 (in CoreFoundation)
7   CoreFoundation                  0x0000000185778f28 _CFPrefsEncodeKeyValuePairIntoMessage :504 (in CoreFoundation)
8   CoreFoundation                  0x0000000185689f94 -[CFPrefsPlistSource sendMessageSettingValue:forKey:] :136 (in CoreFoundation)
9   CoreFoundation                  0x00000001856886ec -[CFPrefsPlistSource alreadylocked_setValues:forKeys:count:from:] :440 (in CoreFoundation)
10  CoreFoundation                  0x0000000185756034 -[CFPrefsSource setValues:forKeys:count:removeValuesForKeys:count:from:] :220 (in CoreFoundation)
11  CoreFoundation                  0x00000001856b9150 -[CFPrefsSearchListSource alreadylocked_setValues:forKeys:count:from:] :584 (in CoreFoundation)
12  CoreFoundation                  0x0000000185756034 -[CFPrefsSource setValues:forKeys:count:removeValuesForKeys:count:from:] :220 (in CoreFoundation)
13  CoreFoundation                  0x00000001857563f4 -[CFPrefsSource setValue:forKey:from:] :64 (in CoreFoundation)
14  CoreFoundation                  0x00000001856bbfa4 __108-[_CFXPreferences(SearchListAdditions) withSearchListForIdentifier:container:cloudConfigurationURL:perform:]_block_invoke :260 (in CoreFoundation)
15  CoreFoundation                  0x00000001856bb7c8 _normalizeQuintuplet :356 (in CoreFoundation)
16  CoreFoundation                  0x00000001856bbe94 -[_CFXPreferences(SearchListAdditions) withSearchListForIdentifier:container:cloudConfigurationURL:perform:] :108 (in CoreFoundation)
17  CoreFoundation                  0x000000018576008c -[_CFXPreferences setValue:forKey:appIdentifier:container:configurationURL:] :92 (in CoreFoundation)
18  CoreFoundation                  0x000000018576370c _CFPreferencesSetAppValueWithContainer :128 (in CoreFoundation)
19  Foundation                      0x000000018601bf80 -[NSUserDefaults(NSUserDefaults) setObject:forKey:] :68 (in Foundation)
gabbler
  • 13,626
  • 4
  • 32
  • 44
  • It looks like a problem with concurrency. You should use `@synchronized` or a serial dispatch queue to ensure that you don't have concurrent updates to your array or to the user default key. Also, this isn't a proper singleton pattern; you should use `dispatch_once` in your `init`. – Paulw11 Dec 01 '17 at 04:21
  • @Paulw11, that's make sense, I am using a `sharedInstance` method which uses `dispatch_once` to call the `init` method in the post. Is it possible to reproduce this crash? I haven't tried that. – gabbler Dec 01 '17 at 05:42
  • @gabbber, A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData. Try StackOverflow link https://stackoverflow.com/questions/6696558/storing-data-to-nsuserdefaults – Ashish Dec 01 '17 at 06:01
  • @Ashish, I am storing a NSMutableArray, so that shouldn't be the problem. – gabbler Dec 01 '17 at 06:24
  • @gabbler: Storing a `NSMutableArray` isn't what could cause the issue, what meant @Ashish, is that EACH objects (in all the "sub-levels possible" too) need to be NSCoding compliant. Could you check that all objects inside `self.recordUploadCnt` are NSString objects? – Larme Dec 01 '17 at 07:49
  • @Larme,yes, I have checked that the only place `self.recordUploadCnt` is modified is shown in the post, so it contains only NSString objects. – gabbler Dec 01 '17 at 07:57

2 Answers2

1

You convert your object to data & try to save, You cant save mutable object directly to NSUserDefaults

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.recordUploadCnt];

[[NSUserDefaults standardUserDefaults] setObject:data forKey: KEY_SENSOR_UPLOAD];

[[NSUserDefaults standardUserDefaults] synchronize];
0

With reference to Apple document: UserDefaults - Storing Default Objects

A default object must be a property list—that is, an instance of (or for collections, a combination of instances of) NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.

You mayneed to convert it into NSData (try this and see)

NSData *arrayData = [NSKeyedArchiver archivedDataWithRootObject: self.recordUploadCnt];
[[NSUserDefaults standardUserDefaults] setObject:arrayData forKey: KEY_SENSOR_UPLOAD];

Check you array values, exact before your add it into NSUserDefaults.

I tried following and it works for me:

#import "ViewController.h"

@interface ViewController () {

}

@property(nonatomic, strong) NSMutableArray* recordUploadCnt;
@end

NSString* const KEY_SENSOR_UPLOAD = @"recordUploadCnt";

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self testArrayWithUserDefaults];
}

-(void)testArrayWithUserDefaults {

    double currentTime = [[NSDate date] timeIntervalSince1970];
    [self.recordUploadCnt addObject:[NSString stringWithFormat:@"%f",currentTime]];
    [[NSUserDefaults standardUserDefaults] setObject:self.recordUploadCnt forKey:KEY_SENSOR_UPLOAD];
    [[NSUserDefaults standardUserDefaults] synchronize];

    NSMutableArray* userDefaultsArray = (NSMutableArray *)[[NSUserDefaults standardUserDefaults] objectForKey:KEY_SENSOR_UPLOAD];
    NSLog(@"Array = %@", [NSString stringWithFormat:@"%f",currentTime]);
}

Result: Array = 1512136898.293169

Here is snapshot of working copy:

enter image description here

Krunal
  • 77,632
  • 48
  • 245
  • 261
  • `self.recordUploadCnt` contains `NSString` objects, so that shouldn't be the problem. See my comment of the post. – gabbler Dec 02 '17 at 04:04