加载要在UITableView cellForRowAtIndexPath中使用的数据

我有一个UITableView,我用图像数据填充它。 此数据加载正常,但当记录数量增加(例如超过50)时,应用程序开始出现冻结等问题。 我知道这是导致问题的我的cellForRowAtIndexPath中的行:

NSData* data = [DKStoreManager loadFileWithName:[finalArray objectAtIndex:indexPath.row] forFolderNumber:[_FolderCode intValue] forUser:[_PassedUserID intValue] andType:_FolderType]; NSDictionary *myDictionary = (NSDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:data]; 

我理解它是因为当我在viewDidLoad中只加载一次数据时使用的myDictionary作为全局变量,然后所有单元格在逻辑上相同,但表格滚动得很好并且应用程序不会崩溃。 finalArray是一个数组,其中的文件名按字母顺序排序,行数对应于其计数。 任何人都可以建议一种方法在cellForRowAtIndexPath方法之外加载这些数据吗? 如果所有NSData都不同,我如何将所有内容传递给cellForRowAtIndexPath?

我试图做的事情:

1)我试图子类化UITableViewCells并从方法加载数据:

 cell.FileName = [finalArray objectAtIndex:indexPath.row]; cell.PassedUserID = _PassedUserID; cell.FolderCode = _FolderCode; cell.FolderType = _FolderType; [cell loadContents]; 

我确保使用BOOL,loadContents只在子类中运行一次。 当我向下或向上滚动时,单元格会改变位置。 一团糟…

2)我注意到,如果我删除if(cell == nil){并停止重复使用单元格,则单元格更改位置没有问题,但是存在大量加载时间问题

2)移动if(cell == nil){方法中的所有内容,单元格仍然在滚动上更改位置但滚动更快…

4)加载viewDidLoad中显示“loading …”的所有数据,但加载速度非常慢,但实际上并没有成功。

5)使用dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {对数据加载,但不起作用,表滚动真的很慢……

PS,myDictionary包含图像和单元格的名称。

编辑:

所有数据都保存在本地,方法“loadFileWithName”将已保存的文件加载到文档目录中。

请参阅下面的延迟加载表的示例。

此示例提供了包含无限数量单元格的表视图; 仅当满足以下所有条件时,才会在后台线程中加载每个单元格的数据:

  • UITableView请求该单元格;
  • 请求数据的单元格是可见的;
  • UITableView不滚动或滚动没有减速(即在用户的手指触摸屏幕时执行滚动)。

也就是说,消除了在快速滚动期间系统的过度负载,并且仅为用户真正需要看到的单元加载数据。

在该示例中,为每个单元模拟耗时且线程阻塞的数据加载操作,其中睡眠后台线程持续0.2秒。 要在您的实际应用程序中使用此示例,请执行以下操作:

  • 替换tableElementPlaceholder getter的实现;
  • performActualFetchTableCellDataForIndexPaths:方法中,将以下行替换为实际的加载单元格数据:

[NSThread sleepForTimeInterval:0.2]; // emulation of time-consuming and thread-blocking operation

  • 为您的单元格实现调整tableView:cellForRowAtIndexPath:方法的实现。
  • 请注意,加载代码中所需的任何对象数据都应该是线程安全的,因为加载是在非主线程中执行的(即atomic属性和可能的NSLock应该在performActualFetchTableCellDataForIndexPaths:方法中使用,在您耗时的线程阻塞代码中替换[NSThread sleepForTimeInterval:0.2]调用)。

    您可以从这里下载完整的Xcode项目。

     #import "ViewController.h" #import "TableViewCell.h" static const NSUInteger kTableSizeIncrement = 20; @interface ViewController ()  @property (nonatomic, strong) NSMutableArray* tableData; @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (nonatomic, readonly) id tableElementPlaceholder; @property (nonatomic, strong) NSTimer* tableDataLoadDelayTimer; - (void)fetchTableCellDataForIndexPath:(NSIndexPath*)indexPath; - (void)performActualFetchTableCellDataForIndexPaths:(NSArray*)indexPaths; - (void)tableDataLoadDelayTimerFired:(NSTimer*)timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.tableData = [NSMutableArray arrayWithCapacity:kTableSizeIncrement]; for (NSUInteger i = 0; i < kTableSizeIncrement; i++) { [self.tableData addObject:self.tableElementPlaceholder]; } // Do any additional setup after loading the view, typically from a nib. } - (id)tableElementPlaceholder { return @""; } - (void)fetchTableCellDataForIndexPath:(NSIndexPath*)indexPath { if (self.tableView.decelerating && !self.tableView.tracking) { if (self.tableDataLoadDelayTimer != nil) { [self.tableDataLoadDelayTimer invalidate]; } self.tableDataLoadDelayTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(tableDataLoadDelayTimerFired:) userInfo:nil repeats:NO]; } else { [self performActualFetchTableCellDataForIndexPaths:@[indexPath]]; } } - (void)tableDataLoadDelayTimerFired:(NSTimer*)timer { [self.tableDataLoadDelayTimer invalidate]; self.tableDataLoadDelayTimer = nil; NSArray* indexPathsForVisibleRows = [self.tableView indexPathsForVisibleRows]; [self performActualFetchTableCellDataForIndexPaths:indexPathsForVisibleRows]; } - (void)performActualFetchTableCellDataForIndexPaths:(NSArray*)indexPaths { for (NSIndexPath* indexPath in indexPaths) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ [NSThread sleepForTimeInterval:0.2]; // emulation of time-consuming and thread-blocking operation NSString* value = [NSString stringWithFormat:@"Text at cell #%ld", (long)indexPath.row]; dispatch_async(dispatch_get_main_queue(), ^{ self.tableData[indexPath.row] = value; [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; }); }); } } #pragma mark UITableViewDataSource protocol - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.tableData.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row == (self.tableData.count - 1)) { for (NSUInteger i = 0; i < 20; i++) { [self.tableData addObject:self.tableElementPlaceholder]; } dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView reloadData]; }); } TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TableViewCell" forIndexPath:indexPath]; NSString* text = [self.tableData objectAtIndex:indexPath.row]; if (text.length == 0) { cell.activityIndicator.hidden = NO; [cell.activityIndicator startAnimating]; cell.label.hidden = YES; [self fetchTableCellDataForIndexPath:indexPath]; } else { [cell.activityIndicator stopAnimating]; cell.activityIndicator.hidden = YES; cell.label.hidden = NO; cell.label.text = text; } return cell; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } @end 

    对于您的项目, performActualFetchTableCellDataForIndexPaths:方法如下所示:

     - (void)performActualFetchTableCellDataForIndexPaths:(NSArray*)indexPaths { for (NSIndexPath* indexPath in indexPaths) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSData* data = [DKStoreManager loadFileWithName:[finalArray objectAtIndex:indexPath.row] forFolderNumber:[self.FolderCode intValue] forUser:[self.PassedUserID intValue] andType:self.FolderType]; NSDictionary *myDictionary = (NSDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:data]; dispatch_async(dispatch_get_main_queue(), ^{ self.tableData[indexPath.row] = myDictionary; [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; }); }); } } 

    请注意,您需要使用primefaces属性self.FolderCodeself.PassedUserID而不是实例变量_FolderCode_PassedUserID ,因为加载文件是在单独的线程中执行的,您需要使此数据对线程安全。

    至于tableElementPlaceholder方法,它可能如下所示:

     - (id)tableElementPlaceholder { return [NSNull null]; } 

    相应地,在tableView: cellForRowAtIndexPath:方法中检查数据加载是否完成如下:

     NSObject* data = [self.tableData objectAtIndex:indexPath.row]; if (data == [NSNull null]) { cell.activityIndicator.hidden = NO; [cell.activityIndicator startAnimating]; cell.label.hidden = YES; [self fetchTableCellDataForIndexPath:indexPath]; } else if ([data isKindOfClass:[NSData class]]) { [cell.activityIndicator stopAnimating]; cell.activityIndicator.hidden = YES; NSData* actualData = (NSData*)data; // Initialize your cell with actualData... } 

    不是个好主意

    tableView上加载大量单元格查看它不是显示数据的好方法。 假设一个用户想要查看你的数据,他会看看你的第一个元素(假设最多20个单元格),为什么他应该拖动才能看到元素一直向下? 如果他想要更多,他只会下降并点击加载更多。 用户不希望等待所有数据并将其加载到内存(RAM)。

    建议

    对我来说,最好的方法是在你加载元素的底部时添加一个加载更多函数 (也称为无限滚动 )(它与网站上的分页相同)。

    履行

    有一个伟大的库来实现这个名为SVPullToRefresh的东西。 这个库允许您通过向下拖动tableView来触发一个方法( completionHandler ),您应该在tableView的末尾加载更多数据。

     //This is where we add infinite scrolling to the tableView __weak YourViewController *weakSelf = self; [self.tableView addInfiniteScrollingWithActionHandler:^{ [weakSelf loadMoreData]; [weakSelf.tableView reloadData]; }]; 

    现在我们添加了更多的负载,我们需要设置我们的数据。 首先我们声明一个NSInteger *lastIndex; 作为一个实例变量,在viewWillAppear我们用0实例化它,并调用loadMoreData,它将赋予lastIndex值20

     - (void)viewWillAppear:(BOOL)animated { lastIndex = 0; [self loadMoreData]; } 

    这是处理从Array到tableView的数据的方法。 这里我们描述一个示例,它可以获得更多按下的20个元素。

     -(void)loadMoreData { //Lets suppose we are loading 20 elemets each time //Here we control if we have elements on array if(yourDataArray.count - lastIndex>0) { //We control if we have 20 or more so we could load all 20 elements if(yourDataArray.count - lastIndex>=20) { lastIndex += 20; } else { //Our last index is the last element on array lastIndex = yourDataArray.count - lastIndex; } } else { //We have loaded all the elements on the array so alert the user } } 

    numberOfRowsInSection我们返回lastIndex

     - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return lastIndex; } 

    结论

    要处理图像,您应该使用SDWebImage作为建议的其他答案。 通过这种方式处理滚动问题,您可以在第一次看到时在窗口上添加大量数据。 也许这不是处理你的想法的正确答案(如果你真的需要将所有数据加载到tableView),但它告诉你如何避免加载数据,以便在滚动到tableView时提供性能。

    ps我在我的社交网络应用程序中使用此示例,它工作得很好。 (此外,Instagram,Facebook和其他应用程序正在处理以这种方式加载数据)。

    希望能帮助到你 :)

    在过去的几年里,我看到了与你类似的方法,但它们都是错的。 如果我是你,我会使用两种不同的方法:

    1)如果从远程服务器加载图像,请使用名为SDWebImage的大型库或编写自己的库。 它将变得像[self.imgViewBack setImageWithURL:[NSURL URLWithString:data.eventImageURL];一样简单[self.imgViewBack setImageWithURL:[NSURL URLWithString:data.eventImageURL];

    2)您还可以将所有图像保存到NSDocumentDirectory ,并将它们存储在那里 – 只需使用路径的NSDictionary

    我的一般建议如下:永远不要创建大小超过几千字节的对象的原始数据的数组或字典。 这将导致整个地方出现可怕的冻结和记忆浪费。 始终使用对象的路径并将其存储在其他位置 – 您将受益。

    通常, cellForRowAtIndexPath方法是UITableViewDataSource协议中最常用的方法,因此它应该以闪电般的速度执行。

    如果您必须在本地存储所有内容而不是像SergiusGee所说的那样存储在服务器上,我建议例如在内存中保留50个图像。

    tableView:willDisplayCell:forRowAtIndexPath: delegate方法,您可以控制哪些元素被加载。

    例如,在显示第50个单元格时,您有索引30-70之间的图像。 在每第10个单元格之后,您可以卸载不需要的单元格并重新加载新的单元格以保持[x-20, x+30]范围,其中x是当前单元格。

    显然,这需要付出相当大的努力来实现,但加载所有内容并将其保存在内存中肯定不是答案。

    此外,这些数字仅用于举例。

    不要在-cellForRowAtIndexPath:加载文件中的数据-cellForRowAtIndexPath:

    尝试将它们加载到-viewDidLoad:UIImage数组中-viewDidLoad:同步或异步(由您决定),因为它不需要花费太多时间来呈现UIImage ,但加载原始数据以构建UIImage 非常昂贵