Using NSCache as data source for pickerview may crash

438 Views Asked by At

I have some data that was originally stored in sqlite and will be loaded to NSCache when first used. I copy the data from NSCache to a pickerview object and use the data to generate pickerview("retain" in the object).

However, a small amount of users encounter crashes because the data in pickerview object is not as expected.

Datasource.m:

- (NSDictionary *)getAllCurrenciesForCache {
    NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"CurrencyList" ofType:@"json"]];
    NSDictionary *dict = [data objectFromJSONData];

    return dict;
}


- (NSArray *)getCurrencyList {
    NSDictionary *currencies = [cache objectForKey:@"key"];
    if(!currencies){
        NSDictionary *currencies = [self getAllCurrenciesForCache];
        [cache setObject:currencies forKey:@"key"];
    }

    NSArray *keys = [currencies allKeys];
    if(keys){ // do some sorting
        return [keys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
    }
    // shouldn't be here
    return nil;
}

ViewController.m:

    NSArray *data = [datasource getCurrencyList];
    NSMutableArray *currencies = [[NSMutableArray alloc] init];
    for (NSString *abbr in data) {
        [currencies addObject:[[MyClass alloc] initWithAbbr:abbr]];
    }

    MyPicker *picker = [[MyPicker alloc] initWithData:[NSArray arrayWithArray:currencies]];

MyPicker.h:

@property (nonatomic, retain) NSArray *currencies;

MyPicker.m:

- (id)initWithData:(NSArray *)data {
    self = [super init];
    if (self) {
        self.currencies = data;
        self.datasource = self;
        self.delegate = self;
    }

    return self;
}

#pragma mark - UIPickerViewDataSource
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return 1;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
    return self.currencies.count;
}

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
    // sometimes crashes
    MyClass *currency = self.currencies[row];


    return [currency showString];
}

#pragma mark - UIPickerViewDelegate
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
    [self notifySelection];
}

- (void)notifySelection {
    NSInteger index = [self selectedRowInComponent:0];

    // here may crash because of index out of bound
    MyClass *currency = self.currencies[index];

    // send the selected currency to view controller
}
2

There are 2 best solutions below

1
On

The data in the NSCache aren't stable, and are super inconsistent. I believe you can get a crash if you put your application in the background then reopened it, this will delete the NSCache.

You shouldn't use it in the case, I believe, I would suggest using either NSArray, if data are not that huge, or go with CoreData if they were.

Hope that helped.

1
On

I wouldn't use NSCache for this purpose. NSCache is designed to be used to store times that can be destroyed when memory is low.

You would use NSCache to prevent the app hitting the network or the disk, not as a source of truth for a picker view, or table view.

From the docs:

NSCache objects differ from other mutable collections in a few ways:

The NSCache class incorporates various auto-removal policies, which ensure that it does not use too much of the system’s memory. The system automatically carries out these policies if memory is needed by other applications. When invoked, these policies remove some items from the cache, minimizing its memory footprint.

Just use an instance of NSArray to back your picker.