objective-c - UITableView 数据源问题。当 UITableView :cellForRowAtIndexPath: is called 时,我的数据数组突然为空

标签 objective-c ios xcode cocoa-touch uitableview

我正在研究 iOS 编程的 BNR 指南,在尝试解决将 UITableView 数据分成多个部分的挑战时遇到了一些麻烦。我正在做的事情的总结是这样的。我需要一种灵活的方式来管理 tableView 的部分,因此我构建了一个 NSMutableArray 来保存这些部分。数组中的每个对象通过 NSDictionary 代表一个表部分。字典有 2 个键,一个用于部分标题的字符串和一个用于保存该部分所有内容的数组。我有一个小例程,它从存储类中获取 allPossesions 并将它们排序并存储到适当的数组和字典中。我一直在努力重写我的代码以合并它,但我遇到了一个令人困惑的障碍。当我的应用程序在调试器中运行时,我抛出了很多 NSLogs 来跟踪发生了什么。在 TableView 的大多数支持方法中,访问和记录我的 sections 数组或它的 nsdictionaries 的内容似乎没有任何问题;但是当调用 cellForRowAtIndexpath 时,代码之神不再对我微笑。不知何故,当我尝试访问或注销我的属性(property)数组时,它突然变空了。

我这辈子都弄不明白。一天多来,我一直在为此苦苦思索,很乐意接受任何意见或帮助。下面是我的 itemsViewController.h 和实现。请忽略日志的困惑和注释掉的部分。我一直在努力解决这个问题并将它们留在其中,这样人们就可以告诉我我可能需要改变我的方法。另外可能值得注意的是,该表最初是空的,在我尝试向其添加内容之前,应用程序没有任何问题。

//
//  ItemsViewController.h
//  HomePwnr

#import <Foundation/Foundation.h>
#import "ItemDetailViewController.h"

@interface ItemsViewController : UITableViewController
{
   NSMutableArray *sections; // variable to hold the number of sections
}
-(void)addNewPossession:(id)sender;
-(void)divideSections;
@end

这里是实现

//
//  ItemsViewController.m
//  HomePwnr

#import "ItemsViewController.h"
#import "PossessionStore.h"
#import "Possession.h"

@implementation ItemsViewController

- (id) init
{
      NSLog(@"ItemsViewController init called");
      // Call the superclass's designated initializer
   self = [super initWithStyle:UITableViewStyleGrouped];

   if (self) {
         // create a new barItem that will send addNePossession: to itemsViewController
      UIBarButtonItem *bbi = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addNewPossession:)];

         // Set this barButtonItem as the right item in the Navigation item
      [[self navigationItem] setRightBarButtonItem:bbi];

         //The Navigation item retains its buttons so release bbi
      [bbi release];

         // set the title of the navigation item
      [[self navigationItem] setTitle:@"Homepwner"];

      //[[self navigationItem] setLeftBarButtonItem:[self editButtonItem]];
   }
   sections = [[[NSMutableArray alloc] init] retain]; // added the extra retain here to make sure my sections weren't getting released pre maturely

      // set up sections here by dividing allPossessions
   [self divideSections];
   return self;
}

- (id) initWithStyle:(UITableViewStyle)style
{
   return [self init];
}

-(void)divideSections
{
   NSLog(@"divideSections called");
      // For simplicity we'll just empty out the sections array and rebuild it each time we add or modify a possesion
   [sections removeAllObjects];

   NSArray *cheapStuff = [[NSArray alloc] initWithArray:[[PossessionStore defaultStore] possesionsFromPredicate:@"valueInDollars < 50"]];
   NSArray *expensiveStuff = [[NSArray alloc] initWithArray:[[PossessionStore defaultStore] possesionsFromPredicate:@"valueInDollars >= 50"]];


   // we'll be making an NSDictionary for each section. it will hold an array of possesions for each section and it's key will serve as the sections header
   if ([cheapStuff count] > 0) { 
      NSMutableDictionary *section1 = [NSMutableDictionary dictionaryWithObject:cheapStuff forKey:@"Possessions"];
      [section1 setValue:@"Cheap Stuff" forKey:@"Header"];
      [sections addObject:section1];
         // sections now retains NSDictionary so we release it
      [section1 release];
   }
   if ([expensiveStuff count] > 0) {
      NSMutableDictionary *section2 = [NSMutableDictionary dictionaryWithObject:expensiveStuff forKey:@"Possessions"];
      [section2 setValue:@"Cheap Stuff" forKey:@"Header"];
      [sections addObject:section2];
         // sections now retains NSDictionary so we release it
      [section2 release];
   }
      //now our arrays are retained by the dictionarys so we release them
   [cheapStuff release];
   [expensiveStuff release];
   NSLog(@" End of divideSections sections holding %@", sections);

}

/*
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
   ItemDetailViewController *detailViewController = [[[ItemDetailViewController alloc] init] autorelease];
      // NSArray *possessions = [[PossessionStore defaultStore] allPossessions];
      // give the detail view controller a pointer to the possesion object in row
      // get the NSDictionary located at the section index, get the dictionary's array, get the possession at row index   
   Possession *p = [[[sections objectAtIndex:[indexPath section]] objectAtIndex:0] objectAtIndex:[indexPath row]];
   [detailViewController setPossession:p];


      // push it onto the navigationControllers stack
   [[self navigationController] pushViewController:detailViewController animated:YES];


}


- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
      ////If the table view is asking to commit the delete command
   if (editingStyle == UITableViewCellEditingStyleDelete) {

//PossessionStore *ps = [PossessionStore defaultStore];
//   NSArray *possessions = [ps allPossessions];
//   Possession *p = [possessions objectAtIndex:[indexPath row]];

      int section = [indexPath section];
      int row = [indexPath row];
      Possession *p = [[[sections objectAtIndex:section] objectAtIndex:0] objectAtIndex:row];
      [[[PossessionStore defaultStore] allPossessions] removePossession:p];

         // remove the row from the table view with an animation
      [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
   }
}

-(void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
   [[PossessionStore defaultStore] movePossessionAtIndex:[sourceIndexPath row] toIndex:[destinationIndexPath row]];
}
*/


-(void)addNewPossession:(id)sender
{
   //
   NSLog(@"addNewPossession called - sections = %@", sections);
   [[PossessionStore defaultStore] createPossession];
   //NSLog(@"Possesion store now holds %@", [[PossessionStore defaultStore] allPossessions]);

      //we've added a new possession so we'll divide out the sections again
   [self divideSections];
   //NSLog(@"addNewPossession exiting - sections = %@", sections);

      //tableview returns the tablesview
   [[self tableView] reloadData];
   //NSLog(@"table view reloading data - sections = %@", sections);

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
   int numSections = [[(NSArray *)[sections objectAtIndex:section] objectForKey:@"Possessions"] count];
   //NSLog(@"numberOfRowsInSection: called for section %i, returning %i.", section, numSections);
   return numSections;
}

- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
   NSLog(@"returning number of sections: %i", [sections count]);
      // return the count of the sections array
   return [sections count];
}

- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
   NSLog(@"tableView:titleForHeaderInSectionCalled - sections = %@", sections);
      //Configure the header titles based on the number of sections
   if ([sections count] <= 1) {
         // return simple title for only one section in table
      NSLog(@"Returning My Stuff");
      return @"My Stuff";
   } else {
      NSLog(@"The header returned is %@", [[sections objectAtIndex:section] objectForKey:@"Header"]);
         // or return the key for the dictionary entry for the current section
      return [[[sections objectAtIndex:section] objectAtIndex:section] objectForKey:@"Header"];
   }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   NSLog(@"tableView:cellForRowAtIndexPath called for section %d, Row %d", [indexPath section], [indexPath row]);
   NSLog(@"Sections = %@", sections);
   NSLog(@"The Dictionary is %@", [sections objectAtIndex:[indexPath section]]);
   //NSLog(@"thisSection array should be %@", (NSArray *)[[sections objectAtIndex:thisSection] objectForKey:@"Possessions"]);
   //NSArray *thisSectionArray = [[sections objectAtIndex:thisSection] objectForKey:@"Possessions"];
      // Check for reusable cell
   UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
      // If there is no cell of this type create a new one
   if (!cell) {
      cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"] autorelease];
   }

      // get the NSDictionary located at the section index, get the dictionary's array, get the possession at row index   
      //Possession *p = [thisSection objectAtIndex:[indexPath row]];
      //[[cell textLabel] setText:[p description]];
   [[cell textLabel] setText:@"cell text"];

   return cell;
}

-(void)viewWillAppear:(BOOL)animated
{
   [self divideSections];
   [super viewWillAppear:YES];
   NSLog(@"viewWillAppear called - sections = %@", sections);
}

-(void)viewDidUnload
{
   [super viewDidUnload];
   NSLog(@"viewDidUnload called - sections = %@", sections);   
}

@end

最后这是我尝试运行该应用程序的日志。绿色小指示器正好位于我尝试在 cellForRow 期间记录各部分内容的行......在应用程序崩溃后。

2012-03-10 06:22:00.177 HomePwnr[44399:f803] ItemsViewController init called
2012-03-10 06:22:00.180 HomePwnr[44399:f803] divideSections called
2012-03-10 06:22:00.181 HomePwnr[44399:f803]  End of divideSections sections holding (
)
2012-03-10 06:22:00.188 HomePwnr[44399:f803] divideSections called
2012-03-10 06:22:00.189 HomePwnr[44399:f803]  End of divideSections sections holding (
)
2012-03-10 06:22:00.189 HomePwnr[44399:f803] returning number of sections: 0
2012-03-10 06:22:00.190 HomePwnr[44399:f803] returning number of sections: 0
2012-03-10 06:22:00.191 HomePwnr[44399:f803] viewWillAppear called - sections = (
)
2012-03-10 06:22:04.234 HomePwnr[44399:f803] addNewPossession called - sections = (
)
2012-03-10 06:22:04.235 HomePwnr[44399:f803] divideSections called
2012-03-10 06:22:04.237 HomePwnr[44399:f803]  End of divideSections sections holding (
        {
        Header = "Cheap Stuff";
        Possessions =         (
            "Shiny Gun (7R3K0): Worth $40, recorded on 2012-03-10 11:22:04 +0000"
        );
    }
)
2012-03-10 06:22:04.238 HomePwnr[44399:f803] returning number of sections: 1
2012-03-10 06:22:04.239 HomePwnr[44399:f803] tableView:titleForHeaderInSectionCalled - sections = (
        {
        Header = "Cheap Stuff";
        Possessions =         (
            "Shiny Gun (7R3K0): Worth $40, recorded on 2012-03-10 11:22:04 +0000"
        );
    }
)
2012-03-10 06:22:04.240 HomePwnr[44399:f803] Returning My Stuff
2012-03-10 06:22:04.241 HomePwnr[44399:f803] tableView:titleForHeaderInSectionCalled - sections = (
        {
        Header = "Cheap Stuff";
        Possessions =         (
            "Shiny Gun (7R3K0): Worth $40, recorded on 2012-03-10 11:22:04 +0000"
        );
    }
)
2012-03-10 06:22:04.241 HomePwnr[44399:f803] Returning My Stuff
2012-03-10 06:22:04.243 HomePwnr[44399:f803] tableView:cellForRowAtIndexPath called for section 0, Row 0
(lldb)

最佳答案

嗯,两个建议。第一个是移动到 ARC(自动引用计数),这样编译器将处理您所有的保留/释放等。

除此之外,您的直接问题是在“divideSections”中您释放第 1 节和第 2 节的地方。两者都是使用方便的例程 dictionaryWithObject 创建的,它返回一个自动发布的字典。因此,即使它由 alloc 例程保留并通过将其添加到数组部分,它的保留计数为零,因此可以随时消失。 (您可以知道是这种情况,因为约定只有带有“alloc、copy 或 retain”的例程才会返回保留的对象)

找到这些东西的最简单方法是让 Xcode 分析而不是构建。这将指向许多这样的内存问题,以及许多其他问题。

此代码引用了我在下面的评论。 Section 应该是一个 NSArray 属性。然后你这样写 setter/getter :

-(void) sections {
   if (!sections) {
      <<code from dividesections that ends with...>>
      self.sections = [NSArray arrayWithObjects: section1,section2,nil];
   }
   return sections;
 }

这样您就知道部分总是正确的,并且不必担心何时调用 divideSections。

关于objective-c - UITableView 数据源问题。当 UITableView :cellForRowAtIndexPath: is called 时,我的数据数组突然为空,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9646119/

相关文章:

ios - Google Drive GTL 框架,dyld : Library not loaded: @loader_path/. ./Frameworks/GTL.framework/GTL

objective-c - NSDictionary 问题

ios - Swift iOS MapKit 及时行驶距离

xcode - Swift 在为此模块构建 ast 上下文时出现 fatal error

objective-c - OpenCL 到 OpenGL 纹理问题

ios - 如何保存我的 iPhone 应用程序的用户首选项?

ios - 尝试访问 PFUser 子类的动态属性时出错

iphone - IBAction 或 [UIButton addTarget :action: forControlEvents:] for UIButton created in . xib 文件?哪种方法更好?

objective-c - CCRenderTexture的renderTextureWithWidth在cocos2d v3.2.1+中编译失败

objective-c - IOS & Objective C - 我如何解决这个错误 "implicit declaration of function "class_getname "is invalid in c99"?