在 Swift 中加速 TableView 加载 JSON

Posted

技术标签:

【中文标题】在 Swift 中加速 TableView 加载 JSON【英文标题】:Speed up TableView in Swift loading JSON 【发布时间】:2015-09-16 07:55:33 【问题描述】:

我目前正在使用我的数据库中的信息(一些带有图像及其名称的产品)开发一个 JSON TableView。一切都很好,但是当我向下(或向上)滚动时速度很慢。我已经对这个主题进行了很多研究,但是我尝试了他们的代码,但我仍然不知道如何将图像存储在缓存中以加速 TableView。

这是我的代码:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! BuscarCellTableViewCell

    if searchController.active 
        cell.nombre.text = searchResults[indexPath.row].nombre

        cell.marca.text = searchResults[indexPath.row].marca

        if let url = NSURL(string: searchResults[indexPath.row].imagen) 
            if let data = NSData(contentsOfURL: url) 
                cell.imagen.image = UIImage(data: data)
            
        
    
    else 
        cell.nombre.text = productos[indexPath.row].nombre

        cell.marca.text = productos[indexPath.row].marca

        if let url = NSURL(string: productos[indexPath.row].imagen) 
            if let data = NSData(contentsOfURL: url) 
                cell.imagen.image = UIImage(data: data)
            
        
    

    return cell


override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) 

    // Define the initial state (Before the animation)
    cell.alpha = 0.25

    // Define the final state (After the animation)
    UIView.animateWithDuration(1.0, animations:  cell.alpha = 1 )


func getLatestLoans() 
    let request = NSURLRequest(URL: NSURL(string: LoadURL)!)
    let urlSession = NSURLSession.sharedSession()
    let task = urlSession.dataTaskWithRequest(request, completionHandler:  (data, response, error) -> Void in

        let res = response as! NSHTTPURLResponse!
        var err: NSError?

        if error != nil 
            println(error.localizedDescription)
        

        // Parse JSON data
        self.productos = self.parseJsonData(data)

        // Reload table view
        dispatch_async(dispatch_get_main_queue(), 
            self.tableView.reloadData()
        )
    )

    task.resume()


func parseJsonData(data: NSData) -> [Producto] 
    var productos = [Producto]()
    var error:NSError?

    let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as? NSDictionary

    // Return nil if there is any error
    if error != nil 
        println(error?.localizedDescription)
    

    // Parse JSON data
    let jsonProductos = jsonResult?["lista_productos"] as! [AnyObject]
    for jsonProducto in jsonProductos 

        let producto = Producto()
        producto.nombre = jsonProducto["nombre"] as! String
        producto.imagen = jsonProducto["imagen"] as! String

        productos.append(producto)
    

    return productos

从 JSON 文件中向下滚动“产品”时如何加快 TableView 的速度?

提前致谢,

问候。

【问题讨论】:

【参考方案1】:

cellForRowAtIndexPath: 为每个新的/重用的单元格渲染调用。您不应在此方法中进行服务器调用。你应该这样做:

    填充数据对象“productos”后,调用新方法fetchImages,该方法可以在后台触发NSOperations 以获取图像。 在获取图像时,您可以在每个单元格图像视图上显示加载叠加层。 一旦图像响应返回,删除加载覆盖并刷新单元格。 在文件系统中缓存图像以避免在用户上下滚动表格视图时再次重新获取它们。

按照此操作,您应该会看到应用程序滚动行为所需的改进。

编辑:根据 OP 请求:

第 1 步:希望您在从服务器加载模型数据后立即调用下面的 fetchImages 方法。如果您在本地加载数据,请在loadView 中调用fetchImages

- (void)fetchImages 
    NSMutableArray *anImageURLsList = [NSMutableArray new]; // Assuming this contains your image URL list

    if ([anImageURLsList count] > 0) 
        __weak MyController *aBlockSelf = self;

        [[MyImageFetchController sharedImageFetchController] addURLs:anImageURLsList withDescription:[self description] andCompletionBlock:^(NSMutableArray *iFailedURLs) 
            dispatch_async(dispatch_get_main_queue(), ^[UIApplication sharedApplication].networkActivityIndicatorVisible = NO; );

            if (aBlockSelf) 
                [[MyImageFetchController sharedImageFetchController].objectTokens seValue:[NSString stringWithFormat:@"%d", NO] forKey:[aBlockSelf description]];
                [aBlockSelf performSelectorOnMainThread:@selector(refresh) withObject:nil waitUntilDone:YES];
            
        ];

        [[MyImageFetchController sharedImageFetchController].objectTokens setValue:[NSString stringWithFormat:@"%d", YES] forKey:[self description]];
        [MyImageFetchController sharedImageFetchController].delegate = self;
        [[MyImageFetchController sharedImageFetchController] startOperations];

    dispatch_async(dispatch_get_main_queue(), ^[UIApplication sharedApplication].networkActivityIndicatorVisible = YES; );
    



- (void)refresh 
    [self.tableView reloadData];

第 2 步: 现在让我们编写 NSOperation 子类以通过操作队列获取图像。下面的代码仅涵盖与此处讨论有关的重要方面。在这里,我为图像缓存实现提供了一个委托回调。

- (void)addURLs:(NSMutableArray *)iURLs withDescription:(NSString *)iObjectDescription andCompletionBlock:(ImageFetchCompletionBlock)iImageFetchCompletionBlock 
    self.urls = iURLs;
    [self.objectTokens removeAllObjects];
    self.imageFetchCompletionBlock = iImageFetchCompletionBlock;
    self.objectDescription = iObjectDescription;

    if (self.failedURLs) 
        [self.failedURLs removeAllObjects];
    



- (void)urlFailed:(NSString *)iFailedURL 
    @synchronized(self) 
        if (iFailedURL) 
            [self.failedURLs addObject:iFailedURL];
        
    



- (void)startOperations 
    MyImageFetchOperation *anImageFetchOperation = nil;
    NSMutableArray *anOperationList = [[NSMutableArray alloc] initWithCapacity:self.urls.count];
    self.queue = [NSOperationQueue new];

    for (NSString *aURL in [self.urls copy]) 
        anImageFetchOperation = [[MyImageFetchOperation alloc] initWithImageURL:aURL delegate:self];
        [anOperationList addObject:anImageFetchOperation];
    

    [self.queue setMaxConcurrentOperationCount:3];
    [self.queue addOperations:anOperationList waitUntilFinished:NO];



- (void)operationCompletedForURL:(NSString *)iImageURL 
    @synchronized(self) 
        [self.urls removeObject:iImageURL];

        if ([self.urls count] == 0) 
            if ([[self.objectTokens valueForKey:self.objectDescription] boolValue]) 
                self.imageFetchCompletionBlock([self.failedURLs mutableCopy]);
             else 
                dispatch_async(dispatch_get_main_queue(), ^[UIApplication sharedApplication].networkActivityIndicatorVisible = NO; );
            

            if (self.failedURLs && [self.failedURLs count] > 0) 
                [self.failedURLs removeAllObjects];
            

            self.queue = nil;
        
    



- (NSString *)cacheDirectoryForImages 
    NSString *anImageCacheDirectory = kImageCacheDirectoryKey; // Your image cache directory 

    if (self.delegate && [self.delegate respondsToSelector:@selector(cacheDirectoryForImages)]) 
        anImageCacheDirectory = [self.delegate cacheDirectoryForImages];
    

    return anImageCacheDirectory;

第 3 步:现在,让我们写 cellForRowAtIndexPath。在这里,我使用了一个自定义单元,它实现了一个 imageView 并且还期望一个自定义加载覆盖。您可以将加载叠加层放在这里。

- (UITableViewCell *)tableView:(UITableView *)iTableView cellForRowAtIndexPath:(NSIndexPath *)iIndexPath 
    NSString *cellType = @"defaultCell";
    MyCustomTableViewCell *cell = (MyCustomTableViewCell *)[iTableView dequeueReusableCellWithIdentifier:cellType];

    if (!cell) 
        cell = [[MyCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:aCellType];
    

    cell.textLabel.text = @“Dummy”; // Set your cell data

    NSString *anImageURL = self.productos[iIndexPath.row][@“image"];

    [cell.loadingOverlay removeView];
    cell.loadingOverlay = nil;

    // If image is present in Cache
    if (anImageURL && [anImageURL existsInCache]) 
        cell.imageView.image = [UIImage imageNamed:<Image from cache>];
     else if ([[[MyImageFetchController sharedImageFetchController].objectTokens valueForKey:[self description]] boolValue]) 
        cell.imageView.image = [UIImage imageNamed:defaultImage];
        cell.loadingOverlay = [MyLoadingOverlay loadingOverlayInView:aCell.imageView];
     else 
        cell.imageView.image = [UIImage imageNamed:defaultImage];
    

    return cell;

【讨论】:

嗨@Abhinav,我很欣赏你的指导方针,我会遵循它们。不幸的是,我不是 ios 开发方面的专家,我非常感谢一些代码,因为我不知道从哪里开始......!提前致谢。 当然,请给我几分钟时间为您提供一些帮助代码。它不会全部连接,但会为您提供有关如何执行此操作的指导。 @Abhinav:没必要,Apple 的网站上有很多例子。 我很快写了一些东西并粘贴在这里:)。请原谅一些肮脏的编码标准! 非常感谢@Abhinav,我将遵循您的代码,并尝试将其扩展到我的示例。问候:)【参考方案2】:

对于延迟加载,您应该使用 SDWebImage Library 。它为您提供了图像的捕获,还提供了在为每个单元格加载图像时添加加载器的功能。 点击此链接可将 SDWebImage 集成到您的应用中。 https://github.com/rs/SDWebImage

【讨论】:

谢谢@Akshay,它是苹果接受的第三方吗?问候 是的,非常欢迎第三方。一般来说,您不应该为已经解决的问题创建解决方案。关于图像加载 - 请参阅this answer【参考方案3】:

关于图像加载性能(将其移至后台线程,将图像存储到内存缓存等)。以下是重复的问题:

display downloaded images in tableview

Async image loading from url inside a UITableView cell - image changes to wrong image while scrolling

【讨论】:

以上是关于在 Swift 中加速 TableView 加载 JSON的主要内容,如果未能解决你的问题,请参考以下文章

Swift:在警报视图中按下按钮后,tableView 不会重新加载数据

在 iOS Swift 中没有正确加载 Tableview 数组?

重新加载 tableView 部分而不重新加载标题部分 - Swift

从列表中选择并重新加载 Tableview Swift

Swift Tableview 从 Firebase 数据库加载数据

当我尝试重新加载日期时,Swift 5 TableView 在 ViewController 中发现 nil