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

时间:2014-06-12 17:25:30

标签: ios

我有一个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"将已保存的文件加载到文档目录中。

5 个答案:

答案 0 :(得分:3)

不是个好主意

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时提供性能。

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

希望有所帮助:)

答案 1 :(得分:3)

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

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

  • 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]方法内部。

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

#import "ViewController.h"
#import "TableViewCell.h"

static const NSUInteger kTableSizeIncrement = 20;

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

@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];
            });
        });
    }
}

请注意,您需要使用原子属性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...
}

答案 2 :(得分:1)

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

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

一样简单

2)您也可以将所有图片保存到NSDocumentDirectory,然后存储在那里 - 只需使用NSDictionary个路径。

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

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

答案 3 :(得分:0)

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

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

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

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

此外,这些数字只是为了举例。

答案 4 :(得分:-1)

请勿从-cellForRowAtIndexPath:中的文件加载数据。

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

非常昂贵