Подтвердить что ты не робот

Лучший способ кэширования изображений в приложении ios?

Вскоре у меня есть NSDictionary с URL-адресами для изображений, которые мне нужно показать в моем UITableView. Каждая ячейка имеет заголовок и изображение. Я успешно это сделал, хотя прокрутка отставала, так как казалось, что клетки загружали свое изображение каждый раз, когда они выходили на экран. Я немного искал и нашел SDWebImage в github. Это заставило прокрутить лагг. Я не совсем уверен, что он сделал, но я полагал, что он сделал некоторое кэширование. Но! Каждый раз, когда я открываю приложение в первый раз, я вижу NO-изображения, и мне приходится прокручивать вниз и создавать резервные копии для их поступления. И если я выхожу из приложения с помощью домашней кнопки и снова открываю, то похоже, что кеширование работает, потому что изображения на экране видны, однако, если я прокручиваю одну ячейку вниз, то следующая ячейка не имеет изображения. Пока я не прокручу его и не вернусь, или если я нажму на него. Это как работает кеширование? Или каков наилучший способ кэширования изображений, загруженных из Интернета? Изображения обновляются, поэтому я был близок к тому, чтобы просто импортировать их в проект, но мне нравится иметь возможность обновлять изображения без загрузки обновления.

Невозможно загрузить все изображения для всего вида таблицы из кеша (учитывая, что что-то есть в кеше) при запуске? Поэтому я иногда вижу ячейки без изображений?

И да, я с трудом понимаю, что такое кеш.

- EDIT -

Я попробовал это только с изображениями того же размера (500x150), и аспект-ошибка ушла, однако при прокрутке вверх или вниз на всех ячейках есть изображения, но сначала они ошибаются. После того, как ячейка была на вид в течение нескольких миллисекунд, появится правильное изображение. Это удивительно раздражает, но может быть, как это должно быть?.. Кажется, он сначала выбирает неверный индекс из кеша. Если я прокручиваю медленно, то я вижу, что изображения мигают от неправильного изображения до правильного. Если я прокручу быстро, то я считаю, что неправильные изображения всегда видны, но я не могу сказать из-за быстрой прокрутки. Когда быстрая прокрутка замедляется и, в конце концов, останавливается, появляются неправильные изображения, но сразу же после прекращения прокрутки она обновляется до правильных изображений. У меня также есть пользовательский класс UITableViewCell, но я не внес никаких больших изменений. Я еще не прошел мой код, но я не могу придумать, что может быть неправильным. Может быть, у меня что-то есть в неправильном порядке. Я много программировал в java, С#, 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;
}
4b9b3361

Ответ 1

Кэширование означает только сохранение копии необходимых вам данных, так что вам не нужно загружать ее из более медленного источника. Например, микропроцессоры часто имеют кэш-память, где хранятся копии данных, поэтому им не нужно обращаться к ОЗУ, что намного медленнее. На жестких дисках часто хранятся кэши памяти, из которых файловая система может получить гораздо более быстрый доступ к блокам данных, к которым недавно обращались.

Аналогично, если ваше приложение загружает много изображений из сети, вам может потребоваться кэшировать их на вашем устройстве, а не загружать их каждый раз, когда вы в них нуждаетесь. Есть много способов сделать это - похоже, что вы уже нашли его. Возможно, вам захочется сохранить загружаемые изображения в каталоге вашего приложения/библиотеки/кэша, особенно если вы не ожидаете их изменения. Загрузка изображений из вторичного хранилища будет намного быстрее, чем загрузка их по сети.

Вам также может быть интересен малоизвестный NSCache класс для хранения изображений, которые вам нужны в памяти. NSCache работает как словарь, но когда память становится жесткой, он начинает выпускать часть своего содержимого. Сначала вы можете проверить кеш для данного изображения, и если вы его не найдете, вы можете посмотреть в своем каталоге кешей, и если вы его не найдете, вы можете его скачать. Ни одно из этого не ускорит загрузку изображений в вашем приложении при первом запуске, но как только ваше приложение загрузит большую часть того, что ему нужно, оно будет гораздо более отзывчивым.

Ответ 2

Я думаю, что Калеб хорошо ответил на вопрос кэширования. Я просто касался процесса обновления вашего пользовательского интерфейса при получении изображений, например. если у вас есть NSCache для ваших изображений с именем _imageCache:

Сначала определите свойство очереди операций для представления таблицы:

@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, но в процессе отправки обновления пользовательского интерфейса обратно в основную очередь, они используют любопытные методы (например, перезагружают ячейку или таблицу, просто обновляя свойство изображения существующего объекта cell, возвращаемого в верхней части tableView:cellForRowAtIndexPath (что является проблемой, если строка прокручивается с экрана и ячейка была удалена и повторно используется для новой строки) и т.д.). Используя cellForRowAtIndexPath (не путать с tableView:cellForRowAtIndexPath), вы можете определить, будет ли ячейка все еще видна и/или если она может быть прокручена и выгружена и повторно использована.

Ответ 3

Самое простое решение - пойти с чем-то сильно используемым, которое подвергалось стрессовым испытаниям.

SDWebImage - это мощный инструмент, который помог мне решить подобную проблему и легко установить w/ cocoa pods. В 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

Ответ 4

Думаю, для вас лучше будет что-то вроде 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
                                        }
                                   }];

Ответ 5

Кэширование изображений может быть выполнено так же просто, как это.

ImageService.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;
    }];

Ответ 6

Для части вопроса о неправильных изображениях это связано с повторным использованием ячеек. Повторное использование ячеек означает, что существующие ячейки, которые выходят из поля зрения (например, ячейки, выходящие из экрана в верхней части, когда вы прокручиваете к нижней части, возвращаются назад со дна.) И вы получаете неправильные изображения. Но как только камера появляется, код для получения правильного изображения выполняется, и вы получаете правильные изображения.

Вы можете использовать заполнитель в методе 'prepareForReuse' ячейки. Эта функция в основном используется, когда вам нужно reset значения, когда ячейка поднимается для повторного использования. Установка заполнитель здесь будет гарантировать, что вы не получите никаких неправильных изображений.

Ответ 7

////.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:@"[email protected]#$%^&*()_+|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"]];