So I am getting a bizarre error when using UIPickerView. Before jumping into the code, a basic explanation of the app is in order:
This app uses a UISegmentedControl to select from a few different possibilities. Based on the selection from the segmented control, the UIPickerView is populated with different arrays. The picker view is guaranteed to have two (no more, no less) components. The arrays are of different sizes, this is where I run into the snag. I have some tests set up in the code to keep the values within the range of the arrays, but something is not working right.
Now onto the code:
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
NSMutableArray *array;
int x = self.segmentController.selectedSegmentIndex;
if (x == 0) {
array = [NSMutableArray arrayWithArray:self.length];
}
else if (x == 1) {
array = [NSMutableArray arrayWithArray:self.time];
}
else if (x == 2) {
array = [NSMutableArray arrayWithArray:self.speed];
}
//component == 0 has no problems whatsoever
if (component == 0) {
if (row < [array count]){
self.label1.text = [array objectAtIndex:row];
self.component0 = row;
}
else {
self.label1.text = [array objectAtIndex:[array count] - 1];
self.component0 = (NSInteger)([array count] - 1);
}
}
//component == 1 has problems, it always thinks that the row number is less than the array count
else if (component == 1) {
if (row < [array count]){
self.label2.text = [array objectAtIndex:row];
self.secondComponent = row;
}
else {
self.label2.text = [array objectAtIndex:[array count] - 1];
self.secondComponent = (NSInteger)([array count] - 1);
}
}
else {
NSLog(#"ERROR: Components out of range");
}
//If I comment out this part, the program works as expected
if (x == 0) {
[self convertLength];
}
else if (x == 1) {
[self convertTime];
}
else if (x == 2) {
[self convertSpeed];
}
}
The error comes when trying to read a value from an array, this is the error:
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 8 beyond bounds [0 .. 7]'
I get this error when I go the the second segment (time) and scroll all the way to the bottom on the second component, and then go to the first index.
If I try to do the same thing with the first component, it works exactly the way it is supposed to, hence I am very confused, as I use basically the exact same code to control the two components.
This is how I load/reload the picker view:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView {
return 2;
}
- (NSInteger)pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component {
int x = 0;
if (self.segmentController.selectedSegmentIndex == 0) {
x = [self.length count];
}
else if (self.segmentController.selectedSegmentIndex == 1)
{
x = [self.time count];
}
else if (self.segmentController.selectedSegmentIndex == 2)
{
x = [self.speed count];
}
return x;
}
- (NSString *)pickerView:(UIPickerView *)thePickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString *string;
NSArray *array = [[NSArray alloc] init];
if (self.segmentController.selectedSegmentIndex == 0) {
array = [NSArray arrayWithArray:self.length];
}
else if (self.segmentController.selectedSegmentIndex == 1)
{
array = [NSArray arrayWithArray:self.time];
}
else if (self.segmentController.selectedSegmentIndex == 2)
{
array = [NSArray arrayWithArray:self.speed];
}
if (row >= [array count]) {
string = [array objectAtIndex:[array count] - 1];
NSLog(#"Out of Range");
}
else {
string = [array objectAtIndex:row];
}
return string;
}
- (IBAction)segmentDidChange:(id)sender {
[self.pickerView reloadAllComponents];
[self pickerView:self.pickerView didSelectRow:self.component0 inComponent:0];
[self pickerView:self.pickerView didSelectRow:self.secondComponent inComponent:1];
}
Other useful information:
length array count: 8
time array count: 9
speed array count: 8
When I log the variables, they behave the way they are expected to, they change their value to be within the range of the "controller array" before I try to access any data within said array, but for whatever reason the second component wants to access an index not contained within the array.
Related
I made a bug when I implement the search function. I opened an asynchronous thread. But when deleting a character (a digit of a phone number), the app would crash.
Error:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x117d7320> was mutated while being enumerated.'
Code:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
//
self.isSearch = YES;
//remove the last search all the contacts
[self.resultArr removeAllObjects];
//
[self.rcs_SearchTableView reloadData];
//
dispatch_queue_t uploadQueue = dispatch_get_global_queue(0, 0);
dispatch_queue_t getMainQueue = dispatch_get_main_queue();
dispatch_async(uploadQueue, ^{
NSMutableArray *phoneArr = (NSMutableArray *)[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText];
//
//NSLog(#"清空上次搜索的数据:%#", self.resultArr);
//NSLog(#"输入的关键字是---%#---%lu",searchText,(unsigned long)searchText.length);
if (0 == searchText.length || [searchText isEqualToString:#" "]) {
self.isSearch = NO;
//[self.rcs_SearchTableView reloadData];
[self.resultArr removeAllObjects];
}
//[self.rcs_SearchTableView reloadData];
if (0 != phoneArr.count) {
//
for (NSUInteger i = 0; i < phoneArr.count; i ++) {
RCSPhoneModel *flagPhoneModel = phoneArr[i];
for (NSUInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *flagModel = self.rcsRecentSearchDataSource[i];
if ([flagPhoneModel.serverId isEqualToString:flagModel.serverId] || [flagPhoneModel.phone isEqualToString:flagModel.name]) {
//the same contact has multiple Numbers To prevent repeated add the same contacts
if (![self.resultArr containsObject:flagModel]) {
[self.resultArr addObject:flagModel];
continue;
}
}
}
}
}else{
//search contacts by name
for (NSInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *model = self.rcsRecentSearchDataSource[i];
NSString *nameStr = model.name;
if (nameStr.length >= searchText.length) {
//search all the name
if ([nameStr containsString:searchText]) {
[self.resultArr addObject:model];
}
}
}
}
//
if (self.resultArr.count > 0) {
self.isSearch = YES;
//[self.rcs_SearchTableView reloadData];
}
//The phone contacts or local contact synchronized to the server
dispatch_async(getMainQueue, ^{
[self.rcs_SearchTableView reloadData];
});
});
}
A for loop should not enumerate anything that could change on any other thread or that could change within that loop. You should only enumerate an object that you are certain is not going to change while being enumerated (either in another thread, or within the loop itself). One way to do this is to only use a local copy of the array to enumerate over.
I can't see where anything being enumerated in your for loops is changed within the loop, so I would guess that in some other code in some other thread, you are changing either self.rcsRecentSearchDataSource or phoneArr. This crashes the for loop that enumerates self.rcsRecentSearchDataSource or phoneArr because it is required to not change while being enumerated.
Does this really need to be run on a separate thread?
If so, use a thread-local copy of the array to enumerate over, instead of the original array. That way you can be sure that nothing else can modify it, because it does not exist in any other scope.
Eg, there are two places where you could change your code to:
NSArray *localSearchDataSource = [self.rcsRecentSearchDataSource copy];
for (NSUInteger i = 0; i < localSearchDataSource.count; i ++) {
and one place where you could change to:
NSArray *localPhoneArr = [[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText] copy];
for (NSUInteger i = 0; i < localPhoneArr .count; i ++) {
I got it answer and like unders codes:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
//
self.isSearch = YES;
//
if (0 == searchText.length || [searchText isEqualToString:#" "]) {
self.isSearch = NO;
//[self.resultArr removeAllObjects];
}
//Remove the last search all the contacts
[self.resultArr removeAllObjects];
//
[self.rcs_SearchTableView reloadData];
//
NSMutableArray *localSearchDataSource = [self.resultArr mutableCopy];
//Create an array of objects as well as the original array
//According to the input access to the phone number of the data
NSArray *localPhoneArr = [[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText] copy];
//
//[self.rcs_SearchTableView reloadData];
if (0 != localPhoneArr.count) {
//Because when data matching number takes longer, using asynchronous thread
dispatch_queue_t uploadQueue = dispatch_get_global_queue(0, 0);
dispatch_queue_t getMainQueue = dispatch_get_main_queue();
dispatch_async(uploadQueue, ^{
//
for (NSUInteger i = 0; i < localPhoneArr.count; i ++) {
RCSPhoneModel *flagPhoneModel = localPhoneArr[i];
for (NSUInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *flagModel = self.rcsRecentSearchDataSource[i];
if ([flagPhoneModel.serverId isEqualToString:flagModel.serverId] || [flagPhoneModel.phone isEqualToString:flagModel.name]) {
//The same contact has multiple Numbers To prevent repeated add the same contacts
if (![localSearchDataSource containsObject:flagModel]) {
[localSearchDataSource addObject:flagModel];
}
}
}
}
//Add the search results to the search data source
dispatch_async(getMainQueue, ^{
[self.resultArr addObjectsFromArray:localSearchDataSource];
[self.rcs_SearchTableView reloadData];
});
});
}else{
//Search contacts by name
for (NSInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *model = self.rcsRecentSearchDataSource[i];
NSString *nameStr = model.name;
if (nameStr.length >= searchText.length) {
//Search all name
if ([nameStr containsString:searchText]) {
[self.resultArr addObject:model];
}
}
}
}
//
if (self.resultArr.count > 0) {
self.isSearch = YES;
[self.rcs_SearchTableView reloadData];
}
}
I made two change that 'NSMutableArray *localSearchDataSource = [self.resultArr mutableCopy];' and 'NSArray *localPhoneArr = [[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText] copy];'. And finish it . Collection <__NSArrayM: 0x117d7320> was mutated while being enumerated.'
I am having the [__NSArrayM insertObject:atIndex:]: object cannot be nil crash when I test the app on my device, but not on the iOS simulator.
Here's what going on:
I am managing 5 UITextField on a View Controller, then I am passing the text of each UITextField to a another View Controller via an NSString using an IBAction (when I press the button, it crashes).
TextViewController
- (IBAction)choicebutton:(id)sender {
AnswerViewController *AVC = [self.storyboard instantiateViewControllerWithIdentifier:#"AnswerViewController"];
AVC.stringFromChoice1 = self.choice1.text;
AVC.stringFromChoice2 = self.choice2.text;
AVC.stringFromChoice3 = self.choice3.text;
AVC.stringFromChoice4 = self.choice4.text;
AVC.stringFromChoice5 = self.choice5.text;
[self presentViewController:AVC animated:YES completion:nil];
}
Then on the AnswerViewController, I am creating an NSMutableArray and randomizing the NSStrings to be displayed on a UILabel.
AnswerViewController
- (void)viewDidLoad
{
self.choiceAnswers1 = [[NSMutableArray alloc] initWithCapacity:5];
if(![self.stringFromChoice1 isEqualToString:#""])
{
[self.choiceAnswers1 addObject:self.stringFromChoice1];
}
if(![self.stringFromChoice2 isEqualToString:#""])
{
[self.choiceAnswers1 addObject:self.stringFromChoice2];
}
if(![self.stringFromChoice3 isEqualToString:#""])
{
[self.choiceAnswers1 addObject:self.stringFromChoice3];
}
if(![self.stringFromChoice4 isEqualToString:#""])
{
[self.choiceAnswers1 addObject:self.stringFromChoice4];
}
if(![self.stringFromChoice5 isEqualToString:#""])
{
[self.choiceAnswers1 addObject:self.stringFromChoice5];
}
int index = arc4random() % [self.choiceAnswers1 count];
self.choiceanswer.text = self.choiceAnswers1[index];
self.choiceanswer1.text = self.choiceAnswers1[index];
}
I've set it up this way in case the user doesn't fill all of the UITextFields, does this have to do anything with the crash? I can't figure this one out, please help!
Thanks!
Don’t use compare: against the empty string—it doesn’t catch the case where your string is nil and not #“”. Those are two distinct cases.
Instead of this:
if(![self.stringFromChoice1 isEqualToString:#""])
{
[self.choiceAnswers1 addObject:self.stringFromChoice1];
}
use this:
if (self.stringFromChoice1.length)
[self.choiceAnswers1 addObject:self.stringFromChoice1];
Since in C any non-0 value is true, and since sending a message to a nil object always returns 0, this catches all the cases. And is less wordy.
Less code is better code!
Change you viewDidLoad something like this.
- (void)viewDidLoad
{
self.choiceAnswers1 = [[NSMutableArray alloc] init];
if(self.stringFromChoice1.length > 0)
{
[self.choiceAnswers1 addObject:self.stringFromChoice1];
}
if(self.stringFromChoice2.length > 0)
{
[self.choiceAnswers1 addObject:self.stringFromChoice2];
}
if(self.stringFromChoice3.length > 0)
{
[self.choiceAnswers1 addObject:self.stringFromChoice3];
}
if(self.stringFromChoice4.length > 0)
{
[self.choiceAnswers1 addObject:self.stringFromChoice4];
}
if( self.stringFromChoice5.length > 0)
{
[self.choiceAnswers1 addObject:self.stringFromChoice5];
}
int index = arc4random() % [self.choiceAnswers1 count];
self.choiceanswer.text = self.choiceAnswers1[index];
self.choiceanswer1.text = self.choiceAnswers1[index];
}
Let me know it that helps.. :)
I have an NSMutable array which i'm trying to remove objects from and by that to decrease
the count of number of objects on the array, and the count always stays the same,
Meaning, the remove is not working.
Here is the code (it's from the online stanford IOS development course):
- (NSMutableArray *)cards
{
if (!_cards) _cards = [[NSMutableArray alloc] init];
return _cards;
}
- (void)addCard:(Card *)card atTop:(BOOL)atTop
{
if (atTop) {
[self.cards insertObject:card atIndex:0];
} else {
[self.cards addObject:card];
}
}
- (void)addCard:(Card *)card
{
[self addCard:card atTop:NO];
}
- (Card *)drawRandomCard
{
Card* randomCard = Nil;
NSLog(#"This is the count %d",[self.cards count]);
if ([self.cards count]) {
unsigned index = arc4random() % [self.cards count];
randomCard = self.cards[index];
[self.cards removeObjectAtIndex:index];
}
return randomCard;
}
The count is always 52, even after removing the objects.
Any ideas on how to fix this?
I try to find item in array by index
http://content.screencast.com/users/xdozorx/folders/Jing/media/cb5e24cf-9349-4aa2-a886-bfafb96299f5/00000051.png
here my code.
- (NSDictionary *) getItemAtIntex:(int) index inArray:(NSArray *) array
{
for (NSDictionary *item in array)
{
if (enumerateIndex == index)
{
NSLog(#"%#",item[#"comment"]);
return item;
}
else if ([item[#"childs"] count])
{
enumerateIndex ++;
[self getItemAtIntex:index inArray:item[#"childs"]];
}
}
return nil;
}
I call my method
enumerateIndex = 0;
NSDictionary *comment = [self getItemAtIntex:indexPath.row inArray:comments];
for example, with index 1, in debug I have two answers
subGL 1 -> 1
global 2
I need in response only subGL 1 -> 1
here my file https://www.dropbox.com/s/mvc21a8pl5n6xz3/commenst.json
You aren't doing anything with the return value of the recursive call. Try this:
- (NSDictionary *) getItemAtIntex:(int) index inArray:(NSArray *) array
{
for (NSDictionary *item in array)
{
if (enumerateIndex == index)
{
NSLog(#"%#",item[#"comment"]);
return item;
}
else if ([item[#"childs"] count])
{
enumerateIndex ++;
NSDictionary *result = [self getItemAtIntex:index inArray:item[#"childs"]];
if (result) {
return result;
}
}
}
return nil;
}
Building a custom UIPickerView for so I can let my users select time as 24 hours without having to go into the Settings app and turn on 24 hour time for the entire phone. Got some stubborn leaks of a couple strings and an array, and I could really use some help.
There are only three places where the arrays I'm creating with the strings are used. hours and minutes are both NSArray synthesized properties as well as ivars.
a) In viewWillAppear:animated, where the strings and arrays are actually created:
if (TwentyFourHourMode) {
//set up arrays for 24 hour picker
NSMutableArray *hoursMutable = [[NSMutableArray alloc] init];
NSString *hourString;
for (int i = 0; i < 24; i++) {
if (i < 10) {
hourString = [NSString stringWithFormat:#"0%i", i];
} else {
hourString = [NSString stringWithFormat:#"%i", i];
}
[hoursMutable addObject:hourString];
}
self.hours = [[NSArray alloc] initWithArray:hoursMutable];
[hoursMutable release];
NSMutableArray *minutesMutable = [[NSMutableArray alloc] init];
NSString *minuteString;
for (int i = 0; i < 60; i++) {
if (i < 10) {
minuteString = [NSString stringWithFormat:#"0%i", i];
} else {
minuteString= [NSString stringWithFormat:#"%i", i];
}
[minutesMutable addObject:minuteString];
}
self.minutes = [[NSArray alloc] initWithArray:minutesMutable];
[minutesMutable release];
//more stuff which does not leak or reference the arrays/strings in question
} else {
//unrelated crap
}
b) in my UIPickerView delegate methods - everything that uses these two arrays:
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 2;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (component == 0) {
return self.hours.count;
} else if (component == 1) {
return self.minutes.count;
} else {
return 0;
}
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
if (component == 0) {
return [self.hours objectAtIndex:row];
} else if (component == 1) {
return [self.minutes objectAtIndex:row];
} else {
return nil;
}
}
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component {
switch(component) {
case 0: return 44;
case 1: return 50;
default: return 44;
}
}
- (void) pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if (component == 0) {
[hour24 release];
hour24 = [self.hours objectAtIndex:row];
[hour24 retain];
} else if (component == 1) {
[minute24 release];
minute24 = [self.minutes objectAtIndex:row];
[minute24 retain];
}
c) and last but not least, in dealloc:
//set arrays to nil
self.hours = nil;
self.minutes = nil;
//release arrays
[hours release];
[minutes release];
Analyze is coming up clean, but Instruments is telling me hourString, minuteString, and self.hours are all being leaked. What really drives me nuts is that self.minutes isn't being leaked and it appears to be the same format of code as self.hours - I even copy-pasted and still get the same leak/no leak combo.
I'll be damned if I can figure out where this is coming from. Any ideas? Any further code y'all might need to help? Thanks, guys!
Edit: EmptyStack's suggestion stopped self.hours and minuteString from being leaked, but hourString is still leaking and there's now a new leak in this code just below the stuff above in viewWillAppear:animated (self.incomingTime is a synthesized NSString property, all arrays here are initialized locally):
NSArray *splitStrings = [self.incomingTime componentsSeparatedByString:#":"];
NSString *hourToDisplay = [splitStrings objectAtIndex:0];
//set this here so it doesn't give a null value
hour24 = [[NSString alloc] initWithString:hourToDisplay];
NSString *minuteSplitter = [splitStrings objectAtIndex:1];
NSArray *splitMinutes = [minuteSplitter componentsSeparatedByString:#" "];
NSString *minuteToDisplay = [splitMinutes objectAtIndex:0];
minute24 = [[NSString alloc] initWithString:minuteToDisplay];
Edit 2: Oh, for crying out loud, now minuteString is leaking again. I'm going to bed before my head explodes. Any suggestions overnight would be most welcome.
The problems are in the following lines,
self.hours = [[NSArray alloc] initWithArray:hoursMutable];
self.minutes = [[NSArray alloc] initWithArray:minutesMutable];
It seems hours and minutes are "retained properties" and you are allocating objects while assigning it to the properties. For example on the first line, [NSArray alloc] increases the retainCount by 1 and the setter self.hours in turn increases the retainCount by 1. Finally the retainCount becomes 2, which causes the leak even after you release those objects. You can use convenience constructors in these cases.
self.hours = [NSArray arrayWithArray:hoursMutable];
self.minutes = [NSArray arrayWithArray:minutesMutable];
And even a simpler way is to directly assign those arrays like this,
self.hours = hoursMutable;
self.minutes = minutesMutable;
Okay, finally found it, for anyone stumbling across this in the future: hour24 and minute24 were not being properly released in dealloc, so it was leaking strings and arrays all over the place. I found it by commenting out the code posted in the 2nd edit and putting it back in line by line until the leak sprang up. Thanks for the suggestions!