4

我正在构建一个 RSS-Reader 并在导航栏的右上角放置一个刷新按钮。它工作正常,我没有崩溃。但是如果我在滚动应用程序时按下刷新按钮会崩溃。而且我不知道问题出在哪里。我分析了这个项目,但它找不到任何东西......

所以这是我得到的错误:

2012-01-22 16:36:48.205 GYSA[712:707] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 5 beyond bounds for empty array'
*** First throw call stack:
(0x37adb8bf 0x315c11e5 0x37a24b6b 0x7913 0x34ef39cb 0x34ef2aa9 0x34ef2233 0x34e96d4b 0x37a3a22b 0x33231381 0x33230f99 0x3323511b 0x33234e57 0x3325c6f1 0x3327f4c5 0x3327f379 0x37249f93 0x3747b891 0x37aa4f43 0x37aaf553 0x37aaf4f5 0x37aae343 0x37a314dd 0x37a313a5 0x375affcd 0x34ec1743 0x2ac9 0x2a54)
terminate called throwing an exception(gdb)

这是我的代码:

#import "RssFunViewController.h"
#import "BlogRssParser.h"
#import "BlogRss.h"

@implementation RssFunViewController

@synthesize rssParser = _rssParser;
@synthesize tableView = _tableView;
@synthesize appDelegate = _appDelegate;
@synthesize toolbar = _toolbar;

-(void)toolbarInit{
    UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc]
                                   initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh
                                   target:self action:@selector(reloadRss)];
    refreshButton.enabled = YES;
    self.navigationItem.rightBarButtonItem = refreshButton;
    [refreshButton release];
    UIImage *image = [UIImage imageNamed: @"navigationbar.png"];
    UIImageView *imageview = [[UIImageView alloc] initWithImage: image];

    UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithCustomView: imageview];
    self.navigationItem.leftBarButtonItem = button;
    [imageview release];
    [button release];
}


// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {

    [super viewDidLoad];
    self.view.autoresizesSubviews = YES;
    self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self toolbarInit];
    _rssParser = [[BlogRssParser alloc]init];
    self.rssParser.delegate = self;
    [[self rssParser]startProcess];
}

-(void)reloadRss{
    [self toggleToolBarButtons:NO];
    [[self rssParser]startProcess];
}

-(void)toggleToolBarButtons:(BOOL)newState{
    NSArray *toolbarItems = self.toolbar.items;
    for (UIBarButtonItem *item in toolbarItems){
        item.enabled = newState;
    }   
}

//Delegate method for blog parser will get fired when the process is completed
- (void)processCompleted{
    //reload the table view
    [self toggleToolBarButtons:YES];
    [[self tableView]reloadData];
}

-(void)processHasErrors{
    //Might be due to Internet
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Achtung!" message:@"Leider ist es im Moment nicht möglich eine Verbindung zum Internet herzustellen. Ohne Internetverbindung ist die App nur in beschränktem Umfang nutzbar!"
                                                   delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
    [alert show];   
    [alert release];
    [self toggleToolBarButtons:YES];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return [[[self rssParser]rssItems]count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"rssItemCell"];
    if(nil == cell){
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"rssItemCell"]autorelease];
    }
    cell.textLabel.text = [[[[self rssParser]rssItems]objectAtIndex:indexPath.row]title];
    cell.detailTextLabel.text = [[[[self rssParser]rssItems]objectAtIndex:indexPath.row]description];
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [[self appDelegate] setCurrentlySelectedBlogItem:[[[self rssParser]rssItems]objectAtIndex:indexPath.row]];
    [self.appDelegate loadNewsDetails];
    [_tableView deselectRowAtIndexPath:indexPath animated: YES];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

- (void)dealloc {
    [_appDelegate release];
    [_toolbar release];
    [_tableView release];
    [_rssParser release];
    [super dealloc];
}

@end

我找到了导致问题的代码行:

cell.textLabel.text = [[[[self rssParser]rssItems]objectAtIndex:indexPath.row]title];
cell.detailTextLabel.text = [[[[self rssParser]rssItems]objectAtIndex:indexPath.row]description];

如果我删除这些代码行,我将无法重现该错误。但正如您想象的那样,它们对于 RSS 提要是必需的 :)。

有什么解决办法吗?

这是获取代码:

#import "BlogRssParser.h"
#import "BlogRss.h"

@implementation BlogRssParser

@synthesize currentItem = _currentItem;
@synthesize currentItemValue = _currentItemValue;
@synthesize rssItems = _rssItems;
@synthesize delegate = _delegate;
@synthesize retrieverQueue = _retrieverQueue;


- (id)init{
    self = [super init];
    if(self){
        _rssItems = [[NSMutableArray alloc]init];
    }
    return self;
}

- (NSOperationQueue *)retrieverQueue {
    if(nil == _retrieverQueue) {
        _retrieverQueue = [[NSOperationQueue alloc] init];
        _retrieverQueue.maxConcurrentOperationCount = 1;
    }
    return _retrieverQueue;
}

- (void)startProcess{
    SEL method = @selector(fetchAndParseRss);
    [[self rssItems] removeAllObjects];
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self 
                                                                     selector:method 
                                                                       object:nil];
    [self.retrieverQueue addOperation:op];
    [op release];
}

-(BOOL)fetchAndParseRss{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    //To suppress the leak in NSXMLParser
    [[NSURLCache sharedURLCache] setMemoryCapacity:0];
    [[NSURLCache sharedURLCache] setDiskCapacity:0];

    BOOL success = NO;
    NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
    [parser setDelegate:self];
    [parser setShouldProcessNamespaces:YES];
    [parser setShouldReportNamespacePrefixes:YES];
    [parser setShouldResolveExternalEntities:NO];
    success = [parser parse];
    [parser release];
    [pool drain];
    return success;
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI 
 qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict{
    if(nil != qualifiedName){
        elementName = qualifiedName;
    }
    if ([elementName isEqualToString:@"item"]) {
        self.currentItem = [[[BlogRss alloc]init]autorelease];
    }else if ([elementName isEqualToString:@"media:thumbnail"]) {
        self.currentItem.mediaUrl = [attributeDict valueForKey:@"url"];
    } else if([elementName isEqualToString:@"title"] || 
              [elementName isEqualToString:@"description"] ||
              [elementName isEqualToString:@"link"] ||
              [elementName isEqualToString:@"guid"] ||
              [elementName isEqualToString:@"pubDate"]) {
        self.currentItemValue = [NSMutableString string];
    } else {
        self.currentItemValue = nil;
    }   
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if(nil != qName){
        elementName = qName;
    }
    if([elementName isEqualToString:@"title"]){
        self.currentItem.title = self.currentItemValue;
    }else if([elementName isEqualToString:@"description"]){
        self.currentItem.description = self.currentItemValue;
    }else if([elementName isEqualToString:@"link"]){
        self.currentItem.linkUrl = self.currentItemValue;
    }else if([elementName isEqualToString:@"guid"]){
        self.currentItem.guidUrl = self.currentItemValue;
    }else if([elementName isEqualToString:@"pubDate"]){
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];
        self.currentItem.pubDate = [formatter dateFromString:self.currentItemValue];
        [formatter release];
    }else if([elementName isEqualToString:@"item"]){
        [[self rssItems] addObject:self.currentItem];
    }
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if(nil != self.currentItemValue){
        [self.currentItemValue appendString:string];
    }
}

- (void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock{
    //Not needed for now
}

- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
    if(parseError.code != NSXMLParserDelegateAbortedParseError) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        [(id)[self delegate] performSelectorOnMainThread:@selector(processHasErrors)
         withObject:nil
         waitUntilDone:NO];
    }
}



- (void)parserDidEndDocument:(NSXMLParser *)parser {
    [(id)[self delegate] performSelectorOnMainThread:@selector(processCompleted)
     withObject:nil
     waitUntilDone:NO];
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}


-(void)dealloc{
    self.currentItem = nil;
    self.currentItemValue = nil;
    self.delegate = nil;

    [_rssItems release];
    [super dealloc];
}

@end
4

4 回答 4

5

您应该做的是将获取的数据数组复制到 ivar。然后从该 ivar 填充您的 tableview,然后processCompleted将新数据复制到 ivar 并调用reloadData. 这将使 tableview 不会处于您遇到的不一致状态。

@property (retain, nonatomic) NSArray *sourceArray;

- (void)processCompleted{
    self.sourceArray = [[[[self rssParser]rssItems] copy] autorelease];
    [self toggleToolBarButtons:YES];
    [[self tableView]reloadData];
}

然后在填充 tableview 时参考复制的数组。例如:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"rssItemCell"];
    if(nil == cell){
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"rssItemCell"]autorelease];
    }
    cell.textLabel.text = [[self.sourceArray objectAtIndex:indexPath.row]title];
    cell.detailTextLabel.text = [[self.sourceArray objectAtIndex:indexPath.row]description];
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    return cell;
}

同样在您引用的所有其他 tableview 委托方法中[[self rssParser]rssItems]

于 2012-01-22T16:02:45.830 回答
1

可能是当您同时滚动和刷新时,您的数据源在被填充之前被清空。因此,虽然您的 tableview 认为它有 5 行,但您的数据源没有 5 个项目,因为您是从源所在的任何位置下载它们。当它查询第五个项目时,那里什么都没有,你的应用程序就崩溃了。

编辑

我是正确的。您的刷新代码调用startProcess清空了用于填充数组的数组,然后一次向其中添加一个项目,并且您在后台队列中执行此操作,因此它可能是异步的。

解决方案是将您的新项目写入后台队列中的中间数组,当该过程完成后,用该数组替换您当前的 rssItems 并重新加载您的表格视图。

于 2012-01-22T15:54:59.117 回答
0

重新加载 tableview 时检查。当您滚动重新加载时,tableview 会自动被调用。因此,在将其内容分配给单元格之前,请检查数组中是否包含值。检查数组计数是否大于 0,然后写入这些行。

于 2012-01-22T16:04:12.860 回答
0

在我的例子中,在调用刷新的方法的开头添加这行代码是有效的:

tableView.scrollEnabled = NO;

当然你需要在最后再次设置你的tableView:

tableView.scrollEnabled = YES;
于 2012-01-22T16:08:07.980 回答