50

很快,我就有了一个NSDictionary我需要在我的UITableView. 每个单元格都有一个标题和一个图像。我成功地做到了这一点,尽管滚动滞后,因为看起来细胞每次进入屏幕时都会下载它们的图像。我搜索了一下,发现SDWebImage在github上。这使得滚动滞后消失了。我不完全确定它做了什么,但我相信它做了一些缓存。但!每次我第一次打开应用程序时,我都看不到任何图像,我必须向下滚动并备份它们才能到达。如果我用主页按钮退出应用程序,然后再次打开,那么似乎缓存正在工作,因为屏幕上的图像是可见的,但是,如果我向下滚动一个单元格,则下一个单元格没有图像。直到我滚动过去并备份,或者如果我点击它。这是缓存应该如何工作的吗?或者缓存从网络下载的图像的最佳方法是什么?图像更新很少,所以我几乎只是将它们导入项目,但我希望有可能在不上传更新的情况下更新图像..

是否不可能在启动时从缓存中加载整个 tableview 的所有图像(假设缓存中有东西)?这就是为什么我有时会看到没有图像的细胞?

是的,我很难理解缓存是什么。

- 编辑 -

我只使用相同尺寸(500x150)的图像进行了尝试,并且纵横比错误消失了,但是当我向上或向下滚动时,所有单元格上都有图像,但起初它们是错误的。单元格在视图中停留几毫秒后,就会出现正确的图像。这非常烦人,但也许它必须是这样的?..它似乎一开始从缓存中选择了错误的索引。如果我滚动缓慢,那么我可以看到图像从错误的图像闪烁到正确的图像。如果我快速滚动,那么我相信始终可以看到错误的图像,但由于快速滚动,我无法判断。当快速滚动变慢并最终停止时,仍然会出现错误的图像,但在停止滚动后会立即更新为正确的图像。我也有一个习惯UITableViewCell上课,但我没有做任何大的改变..我还没有仔细检查我的代码,但我想不出可能有什么问题..也许我有一些错误的顺序..我有用 java、c#、php 等编写了很多程序,但我很难理解 Objective-c,所有的.h.m......我也有

@interface FirstViewController : UITableViewController{

/**/
NSCache *_imageCache;
}

(在其他变量中)在FirstViewController.h. 这不正确吗?

这是我的cellForRowAtIndexPath

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"hallo";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil)
{
    cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}

NSMutableArray *marr = [hallo objectAtIndex:indexPath.section];
NSDictionary *dict = [marr objectAtIndex:indexPath.row];

NSString* imageName = [dict objectForKey:@"Image"];
//NSLog(@"url: %@", imageURL);

UIImage *image = [_imageCache objectForKey:imageName];

if(image)
{
    cell.imageView.image = image;
}
else
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSString* imageURLString = [NSString stringWithFormat:@"example.com/%@", imageName];
        NSURL *imageURL = [NSURL URLWithString:imageURLString];
        UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:imageURL]];

        if(image)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                CustomCell *cell =(CustomCell*)[self.tableView cellForRowAtIndexPath:indexPath];
                if(cell)
                {
                    cell.imageView.image = image;
                }
            });
            [_imageCache setObject:image forKey:imageName];
        }
    });
}

cell.textLabel.text = [dict objectForKey:@"Name"];

return cell;
}
4

7 回答 7

78

缓存只是意味着保留您需要的数据的副本,这样您就不必从一些较慢的源加载它。例如,微处理器通常具有高速缓存存储器,它们在其中保存数据副本,这样它们就不必访问速度慢得多的 RAM。硬盘通常具有内存缓存,文件系统可以从中更快地访问最近访问过的数据块。

同样,如果您的应用程序从网络加载了大量图像,那么将它们缓存在您的设备上而不是每次需要它们时都下载它们可能符合您的兴趣。有很多方法可以做到这一点——听起来你已经找到了一种。您可能希望将下载的图像存储在应用程序的 /Library/Caches 目录中,尤其是在您不希望它们发生变化的情况下。从辅助存储加载图像将比通过网络加载要快得多。

您可能还对鲜为人知的用于将所需图像保存在内存中的NSCache类感兴趣。NSCache 像字典一样工作,但是当内存紧张时,它会开始释放其中的一些内容。您可以先检查给定图像的缓存,如果在那里找不到它,您可以查看您的缓存目录,如果在那里找不到它,您可以下载它。这些都不会在您第一次运行应用程序时加快图像加载速度,但是一旦您的应用程序下载了所需的大部分内容,它的响应速度就会更快。

于 2012-07-16T20:10:05.367 回答
25

我认为 Caleb 很好地回答了缓存问题。我只是想谈谈在检索图像时更新 UI 的过程,例如假设你有一个NSCache名为_imageCache

首先,为tableview定义一个操作队列属性:

@property (nonatomic, strong) NSOperationQueue *queue;

然后在 中viewDidLoad,初始化:

self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 4;

然后在 中cellForRowAtIndexPath,您可以:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ilvcCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // set the various cell properties

    // now update the cell image

    NSString *imagename = [self imageFilename:indexPath]; // the name of the image being retrieved

    UIImage *image = [_imageCache objectForKey:imagename];

    if (image)
    {
        // if we have an cachedImage sitting in memory already, then use it

        cell.imageView.image = image;
    }
    else
    {
        cell.imageView.image = [UIImage imageNamed:@"blank_cell_image.png"];

        // the get the image in the background

        [self.queue addOperationWithBlock:^{

            // get the UIImage

            UIImage *image = [self getImage:imagename];

            // if we found it, then update UI

            if (image)
            {
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    // if the cell is visible, then set the image

                    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                    if (cell)
                        cell.imageView.image = image;
                }];

                [_imageCache setObject:image forKey:imagename];
            }
        }];
    }

    return cell;
}

我只提到这一点是因为我最近在 SO 上看到了一些代码示例,它们使用 GCD 来更新适当的UIImageView image属性,但是在将 UI 更新分派回主队列的过程中,它们采用了奇怪的技术(例如,重新加载单元格或表格,仅更新cell顶部返回的现有对象的图像属性tableView:cellForRowAtIndexPath(如果该行已滚动离开屏幕并且该单元格已出列并被重新用于新行,则这是一个问题)等.)。通过使用cellForRowAtIndexPath(不要与 混淆tableView:cellForRowAtIndexPath),您可以确定单元格是否仍然可见和/或它是否可能已滚动并已出列并重用。

于 2012-07-16T20:25:09.380 回答
13

最简单的解决方案是使用经过压力测试的大量使用的东西。

SDWebImage 是一个强大的工具,它帮助我解决了类似的问题,并且可以很容易地与可可豆荚一起安装。在 podfile 中:

platform :ios, '6.1'
pod 'SDWebImage', '~>3.6'

设置缓存:

SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];
[imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image)
{
    // image is not nil if image was found
}];

缓存图片:

[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];

https://github.com/rs/SDWebImage

于 2014-10-24T16:53:08.447 回答
1

我认为像 DLImageLoader 这样的用户会更好。更多信息-> https://github.com/AndreyLunevich/DLImageLoader-iOS

[[DLImageLoader sharedInstance] loadImageFromUrl:@"image_url_here"
                                   completed:^(NSError *error, UIImage *image) {
                                        if (error == nil) {
                                            imageView.image = image;
                                        } else {
                                            // if we got an error when load an image
                                        }
                                   }];
于 2014-08-14T11:22:26.427 回答
1

对于错误图像的部分问题,这是因为单元格的重用。单元格的重用意味着现有的单元格,这些单元格不在视野范围内(例如,当您向下滚动时,从顶部离开屏幕的单元格是从底部再次返回的单元格。)所以你得到不正确的图像。但是一旦单元格出现,获取正确图像的代码就会执行,并且您会获得正确的图像。

您可以在单元格的“prepareForReuse”方法中使用占位符。此功能主要用于当您需要重新设置单元格以供重用时的值时。在此处设置占位符将确保您不会得到任何不正确的图像。

于 2016-06-01T13:46:39.347 回答
0

缓存图像可以像这样简单地完成。

图像服务.m

@implementation ImageService{
    NSCache * Cache;
}

const NSString * imageCacheKeyPrefix = @"Image-";

-(id) init {
    self = [super init];
    if(self) {
        Cache = [[NSCache alloc] init];
    }
    return self;
}

/** 
 * Get Image from cache first and if not then get from server
 * 
 **/

- (void) getImage: (NSString *) key
        imagePath: (NSString *) imagePath
       completion: (void (^)(UIImage * image)) handler
    {
    UIImage * image = [Cache objectForKey: key];

    if( ! image || imagePath == nil || ! [imagePath length])
    {
        image = NOIMAGE;  // Macro (UIImage*) for no image 

        [Cache setObject:image forKey: key];

        dispatch_async(dispatch_get_main_queue(), ^(void){
            handler(image);
        });
    }
    else
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0ul ),^(void){

            UIImage * image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[imagePath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]];

            if( !image)
            {
                image = NOIMAGE;
            }

            [Cache setObject:image forKey: key];

            dispatch_async(dispatch_get_main_queue(), ^(void){
                handler(image);
            });
        });
    }
}


- (void) getUserImage: (NSString *) userId
           completion: (void (^)(UIImage * image)) handler
{
    [self getImage: [NSString stringWithFormat: @"%@user-%@", imageCacheKeyPrefix, userId]
         imagePath: [NSString stringWithFormat: @"http://graph.facebook.com/%@/picture?type=square", userId]
        completion: handler];
}

SomeViewController.m

    [imageService getUserImage: userId
                    completion: ^(UIImage *image) {
            annotationImage.image = image;
    }];
于 2016-01-06T01:34:00.740 回答
0
////.h file

#import <UIKit/UIKit.h>

@interface UIImageView (KJ_Imageview_WebCache)


-(void)loadImageUsingUrlString :(NSString *)urlString placeholder :(UIImage *)placeholder_image;

@end

//.m file


#import "UIImageView+KJ_Imageview_WebCache.h"


@implementation UIImageView (KJ_Imageview_WebCache)




-(void)loadImageUsingUrlString :(NSString *)urlString placeholder :(UIImage *)placeholder_image
{




    NSString *imageUrlString = urlString;
    NSURL *url = [NSURL URLWithString:urlString];



    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,     NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *getImagePath = [documentsDirectory stringByAppendingPathComponent:[self tream_char:urlString]];

    NSLog(@"getImagePath--->%@",getImagePath);

    UIImage *customImage = [UIImage imageWithContentsOfFile:getImagePath];




    if (customImage)
    {
        self.image = customImage;


        return;
    }
    else
    {
         self.image=placeholder_image;
    }


    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *uploadTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {


        if (error)
        {
            NSLog(@"%@",[error localizedDescription]);

           self.image=placeholder_image;

            return ;
        }

        dispatch_async(dispatch_get_main_queue(), ^{


            UIImage *imageToCache = [UIImage imageWithData:data];



            if (imageUrlString == urlString)
            {

                self.image = imageToCache;
            }



            [self saveImage:data ImageString:[self tream_char:urlString]];

        });




    }];

    [uploadTask resume];



}


-(NSString *)tream_char :(NSString *)string
{
    NSString *unfilteredString =string;

    NSCharacterSet *notAllowedChars = [[NSCharacterSet characterSetWithCharactersInString:@"!@#$%^&*()_+|abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"] invertedSet];
    NSString *resultString = [[unfilteredString componentsSeparatedByCharactersInSet:notAllowedChars] componentsJoinedByString:@""];
    NSLog (@"Result: %@", resultString);



    return resultString;

}

-(void)saveImage : (NSData *)Imagedata ImageString : (NSString *)imageString
{

    NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);

    NSString* documentDirectory = [documentDirectories objectAtIndex:0];
    NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:imageString];




    if (![Imagedata writeToFile:documentDirectoryFilename atomically:NO])
    {
        NSLog((@"Failed to cache image data to disk"));
    }
    else
    {
        NSLog(@"the cachedImagedPath is %@",documentDirectoryFilename);
    }

}

@end


/// call 

 [cell.ProductImage loadImageUsingUrlString:[[ArrProductList objectAtIndex:indexPath.row] valueForKey:@"product_image"] placeholder:[UIImage imageNamed:@"app_placeholder"]];
于 2016-12-07T10:45:04.143 回答