2012-01-22 87 views
4

我正在構建一個RSS閱讀器並在導航欄的右上角放置一個refreshbutton。它工作正常,我沒有崩潰。但如果我在滾動應用程序崩潰期間按下刷新按鈕。我不知道問題出在哪裏。我分析了項目,但找不到任何...索引'5'超出了空陣列崩潰的界限

因此,這裏是我的錯誤:

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]; 

如果我刪除這些codelines我不能重現錯誤。但是它們對於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 

回答

6

你應該做的是複製你獲取的數據數組到伊娃。然後從該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; 
} 

而且同樣在您引用[[self rssParser]rssItems]所有其他的tableview委託方法。

+0

你會如何做到這一點?我真的不確定 – AmiiQo

+0

編輯回覆,對不起,只要輸入一次,然後我的瀏覽器崩潰了。 – NJones

+0

非常感謝,我愛你:P! – AmiiQo

1

這可能是因爲,當您滾動,並在同一時間刷新,您的數據源被排空,被填充之前。所以,當你的tableview認爲它有5行時,你的數據源沒有5個項目,因爲你從源代碼的任何地方下載它們。當它詢問第五項時,那裏沒有任何東西,你的應用程序就會崩潰。

編輯

我是對的。你的刷新代碼調用startProcess,它清空你用來填充數組的數組,然後你一次添加一個項目,並且你在後臺隊列中這樣做,所以它可能是異步的。

解決這個問題的方法是將新項目寫入後臺隊列中的中間數組,並且當該過程完成時,用該數組替換當前的rssItems並重新加載tableview。

+0

那說得通。我是新手,所以你有什麼想法我可以改變代碼,使其工作? – AmiiQo

+0

這取決於您的獲取代碼的實現。 – Abizern

0

重新加載tableview時檢查。當你滾動重載tableview時會自動被調用。因此,在將數組的內容分配給單元格之前,請檢查該數組是否具有值。檢查數組數是否爲0,然後寫入這些行。

0

在我的情況下加入這行代碼在呼籲刷新的方法開始工作:

tableView.scrollEnabled = NO; 

當然你需要在年底重新設置您的tableView作者:

tableView.scrollEnabled = YES; 
+0

如果我按下刷新後滾動,這纔有用。但是如果我滾動然後按刷新怎麼辦? – AmiiQo

+0

嗯......我的代碼實際上有點複雜。我在我的tableView上面顯示了一個mask(帶有一個activityIndi​​cator),在上面滾動我的表,將scrollEnabled設置爲NO,最後重新加載我的tableView。 – Beppe

相關問題