【问题标题】:Why is UITableView not reloading (even on the main thread)?为什么 UITableView 不重新加载(即使在主线程上)?
【发布时间】:2010-01-20 18:47:46
【问题描述】:

我有两个基本上做同样事情的程序。他们读取 XML 提要并解析元素。这两个程序的设计都是使用异步 NSURLConnection 来获取数据,然后生成一个新线程来处理解析。在解析 5 个项目的批次时,它会回调主线程以重新加载 UITableView。

我的问题是它在一个程序中运行良好,但在另一个程序中却不行。我知道解析实际上发生在后台线程上,并且我知道 [tableView reloadData] 正在主线程上执行;但是,在所有解析完成之前,它不会重新加载表。我难住了。据我所知……这两个程序的结构完全相同。这是应用程序中的一些无法正常工作的代码。

- (void)startConnectionWithURL:(NSString *)feedURL feedList:(NSMutableArray *)list {
self.feedList = list;

// Use NSURLConnection to asynchronously download the data. This means the main thread will not be blocked - the
// application will remain responsive to the user. 
//
// IMPORTANT! The main thread of the application should never be blocked! Also, avoid synchronous network access on any thread.
//
NSURLRequest *feedURLRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURL]];
self.bloggerFeedConnection = [[[NSURLConnection alloc] initWithRequest:feedURLRequest delegate:self] autorelease];

// Test the validity of the connection object. The most likely reason for the connection object to be nil is a malformed
// URL, which is a programmatic error easily detected during development. If the URL is more dynamic, then you should
// implement a more flexible validation technique, and be able to both recover from errors and communicate problems
// to the user in an unobtrusive manner.
NSAssert(self.bloggerFeedConnection != nil, @"Failure to create URL connection.");

// Start the status bar network activity indicator. We'll turn it off when the connection finishes or experiences an error.
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
self.bloggerData = [NSMutableData data];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [bloggerData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
self.bloggerFeedConnection = nil;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;   
// Spawn a thread to fetch the link data so that the UI is not blocked while the application parses the XML data.
//
// IMPORTANT! - Don't access UIKit objects on secondary threads.
//
[NSThread detachNewThreadSelector:@selector(parseFeedData:) toTarget:self withObject:bloggerData];
// farkData will be retained by the thread until parseFarkData: has finished executing, so we no longer need
// a reference to it in the main thread.
self.bloggerData = nil;
}

如果您从上到下阅读此内容,您可以看到 NSURLConnection 何时完成我分离一个新线程并调用 parseFeedData。

- (void)parseFeedData:(NSData *)data {
// You must create a autorelease pool for all secondary threads.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

self.currentParseBatch = [NSMutableArray array];
self.currentParsedCharacterData = [NSMutableString string];
self.feedList = [NSMutableArray array];
//
// It's also possible to have NSXMLParser download the data, by passing it a URL, but this is not desirable
// because it gives less control over the network, particularly in responding to connection errors.
//
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
[parser parse];

// depending on the total number of links parsed, the last batch might not have been a "full" batch, and thus
// not been part of the regular batch transfer. So, we check the count of the array and, if necessary, send it to the main thread.
if ([self.currentParseBatch count] > 0) {
    [self performSelectorOnMainThread:@selector(addLinksToList:) withObject:self.currentParseBatch waitUntilDone:NO];
}
self.currentParseBatch = nil;
self.currentParsedCharacterData = nil;
[parser release];        
[pool release];
}

在 did 结束元素委托中,我检查了在调用主线程执行更新之前是否已经解析了 5 个项目。

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:kItemElementName]) {
    [self.currentParseBatch addObject:self.currentItem];
    parsedItemsCounter++;
    if (parsedItemsCounter % kSizeOfItemBatch == 0) {
        [self performSelectorOnMainThread:@selector(addLinksToList:) withObject:self.currentParseBatch waitUntilDone:NO];
        self.currentParseBatch = [NSMutableArray array]; 
    }
}  

// Stop accumulating parsed character data. We won't start again until specific elements begin.
accumulatingParsedCharacterData = NO;
}

- (void)addLinksToList:(NSMutableArray *)links {
[self.feedList addObjectsFromArray:links];
// The table needs to be reloaded to reflect the new content of the list.

if (self.viewDelegate != nil && [self.viewDelegate respondsToSelector:@selector(parser:didParseBatch:)]) {
    [self.viewDelegate parser:self didParseBatch:links];
}

}

最后是 UIViewController 委托:

- (void)parser:(XMLFeedParser *)parser didParseBatch:(NSMutableArray *)parsedBatch {
NSLog(@"parser:didParseBatch:");
[self.selectedBlogger.feedList addObjectsFromArray:parsedBatch];
[self.tableView reloadData];

}

如果我在视图控制器委托触发以重新加载表时写入日志,并且当 cellForRowAtIndexPath 在重建表时触发时,日志看起来像这样:

解析器:didParseBatch:
解析器:didParseBatch:
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
解析器:didParseBatch:
解析器:didParseBatch:
解析器:didParseBatch:
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
解析器:didParseBatch:
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
解析器:didParseBatch:
解析器:didParseBatch:
解析器:didParseBatch:
解析器:didParseBatch:
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
tableView:cellForRowAtIndexPath

显然,当我每次都告诉它时,tableView 并没有重新加载。

正常运行的应用程序日志如下所示:

解析器:didParseBatch:
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
解析器:didParseBatch:
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
解析器:didParseBatch:
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
解析器:didParseBatch:
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
解析器:didParseBatch:
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
表视图:cellForRowAtIndexPath
tableView:cellForRowAtIndexPath

【问题讨论】:

  • 你解决过这个问题吗?

标签: iphone uitableview multithreading nsurlconnection


【解决方案1】:

我猜你的数据模型中的某些东西是罪魁祸首。最有可能的是,我认为这会导致节号错误或报告节的行数为零。

一个表在调用- tableView:cellForRowAtIndexPath 之前同时调用– numberOfSectionsInTableView:– tableView:numberOfRowsInSection:。如果他们中的任何一个报告错误的数字,tableview 将假定它没有数据并且什么都不做。

【讨论】:

  • 我不认为这个理论是正确的。节数硬编码为 1。有关详细信息,请参见下文。 parser:didParseBatch: tableView:numberOfRowsInSection:5 parser:didParseBatch: tableView:numberOfRowsInSection:10 tableView:cellForRowAtIndexPath tableView:cellForRowAtIndexPath tableView:cellForRowAtIndexPath tableView:cellForRowAtIndexPath tableView:cellForRowAtIndexPath parser:didParseBatch: tableView:numberOfRowsInSection:15 parser:didParseBatch 20 解析器:didParseBatch:tableView:numberOfRowsInSection:25
  • 那么我没有主意了。据我所知,重新加载不会导致tableView:cellForRowAtIndexPath 被调用的唯一原因是如果表认为不需要它并且只有数据源可以确定这一点。
猜你喜欢
  • 1970-01-01
  • 2016-09-24
  • 2011-09-18
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-03
相关资源
最近更新 更多