0

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
}
benck
  • 2,034
  • 1
  • 22
  • 31
  • Where did you read that notifyTarget: was an acceptable method to use? I've **never** seen it used, and objc_msgSend is a clear indication that you're doing something wrong. – Jessedc Dec 15 '13 at 20:35
  • Actually it was modified from https://github.com/TimCinel/ActionSheetPicker . It's an actionsheet with pickerview, and the notifyTarget method is called when the "done" button is pressed so that the origin view controller knows which option is selected. – benck Dec 16 '13 at 02:39
  • That project has 54 issues 20 pull requests and hasn't been updated for over two years. I wouldn't be using it. If notify target is failing, just use UIPickerView and it's delegate in a more standard way. – Jessedc Dec 16 '13 at 02:50
  • Actually I have another part of code that is similar to this one but will also crash. The above code is updated and there are two places that may crash as shown in comments. – benck Dec 16 '13 at 09:06
  • I wonder that if there is race condition in method "didSelectRow"? However, even if the "row" value is not the same as "index" in notifySelection(), it shouldn't crash since all indexes should be valid. – benck Dec 16 '13 at 09:10

2 Answers2

1

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.

Albara
  • 1,306
  • 8
  • 14
  • In function getCurrencyList, it returns a NSArray instead of a NSCache. Will there be a problem? – benck Dec 15 '13 at 13:20
1

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.

Community
  • 1
  • 1
Jessedc
  • 12,320
  • 3
  • 50
  • 63
  • I copy the data from cache into NSArray, so the auto cleaning mechanism shouldn't be a problem. – benck Dec 15 '13 at 13:17