web-dev-qa-db-ja.com

iOSアプリで画像をキャッシュする最良の方法は?

まもなく、NSDictionaryに表示する必要がある画像のURLを含むUITableViewがあります。各セルにはタイトルと画像があります。セルは画面に入るたびに画像をダウンロードするように見えたので、スクロールは遅れていましたが、私はこれを成功させました。少し検索して、githubでSDWebImageを見つけました。これにより、スクロールラグがなくなりました。私はそれが何をしたのか完全にはわかりませんが、いくらかのキャッシングをしたと信じていました。しかし!初めてアプリを開くたびに、画像が表示されず、スクロールダウンしてバックアップする必要があります。そして、ホームボタンでアプリを終了して再び開くと、画面上の画像が表示されるため、キャッシュが機能しているように見えますが、1つのセルを下にスクロールすると、次のセルには画像がありません。スクロールして戻るまで、またはクリックするまで。これはキャッシングがどのように機能するはずですか?または、ウェブからダウンロードした画像をキャッシュする最良の方法は何ですか?画像は随時更新されているため、プロジェクトにインポートするだけでしたが、更新をアップロードせずに画像を更新できる可能性があります。

起動時にキャッシュからTableView全体のすべての画像をロードすることは不可能ですか?それが画像のない細胞を時々見るのはなぜですか?

そして、はい、キャッシュとは何かを理解するのに苦労しています。

-EDIT-

同じサイズ(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;
}
49
Sti

キャッシュとは、必要なデータのコピーを保持することを意味するため、低速なソースからデータをロードする必要はありません。たとえば、マイクロプロセッサは多くの場合、データのコピーを保持するキャッシュメモリを持っているため、RAMにアクセスする必要がありません。これは非常に低速です。多くの場合、ハードディスクにはメモリキャッシュがあり、そこからファイルシステムは最近アクセスされたデータのブロックにすばやくアクセスできます。

同様に、アプリがネットワークから大量の画像を読み込む場合、必要なたびにダウンロードするのではなく、デバイスに画像をキャッシュすることをお勧めします。それを行うには多くの方法があります-あなたはすでにそれを見つけたようです。ダウンロードした画像をアプリの/ Library/Cachesディレクトリに保存することをお勧めします。特に変更が予想されない場合はそうです。セカンダリストレージからのイメージのロードは、ネットワーク経由でのロードよりもはるかに高速です。

メモリに必要なイメージを保持するための、あまり知られていない NSCache クラスにも興味があるかもしれません。 NSCacheは辞書のように機能しますが、メモリが不足すると、コンテンツの一部を解放し始めます。最初に特定のイメージのキャッシュを確認できます。キャッシュが見つからない場合は、キャッシュディレクトリを確認し、見つからない場合はダウンロードできます。これらのいずれも、最初に実行するときのアプリでの画像の読み込みを高速化するものではありませんが、アプリが必要なもののほとんどをダウンロードすると、応答性が大幅に向上します。

72
Caleb

キャレブはキャッシングの質問によく答えたと思います。画像を取得する際にUIを更新するプロセスについて説明しました。 _imageCacheという画像のNSCacheがあると仮定します。

最初に、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を使用して適切なUIImageViewimageプロパティを更新するいくつかのコードサンプルを見ているので、これについてのみ言及していますが、 UI更新をメインキューにディスパッチする際に、好奇心techniques盛な手法を使用します(たとえば、セルまたはテーブルをリロードし、tableView:cellForRowAtIndexPathの先頭に返される既存のcellオブジェクトの画像プロパティを更新します。行が画面外にスクロールされ、セルがデキューされて新しい行に再利用されている場合など))。)を使用することでcellForRowAtIndexPathtableView:cellForRowAtIndexPathと混同しないでください) )、セルがまだ表示されているかどうか、および/またはスクロールオフされてキューから取り出されて再利用されたかどうかを判断できます。

23
Rob

最も簡単な解決策は、ストレステスト済みの頻繁に使用されるものを使用することです。

SDWebImageは、同様の問題を解決するのに役立つ強力なツールであり、ココアポッドを使用して簡単にインストールできます。ポッドファイル内:

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

13
Matt Perejda

間違った画像に関する質問の部分については、それはセルの再利用のためです。セルを再利用すると、既存のセルが表示されなくなります(たとえば、下に向かってスクロールすると画面の上部に表示されるセルは、下部から再び表示されるセルです)。誤った画像。ただし、セルが表示されると、適切な画像を取得するためのコードが実行され、適切な画像が取得されます。

セルの「prepareForReuse」メソッドでプレースホルダーを使用できます。この関数は主に、セルを再利用のために起動するときに値をリセットする必要がある場合に使用されます。ここにプレースホルダーを設定すると、間違った画像が表示されないようになります。

1
Swasidhant

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
                                        }
                                   }];
1
MaeSTRo

画像のキャッシュは、これと同じくらい簡単に行うことができます。

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;
    }];
0
tsuz
////.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"]];
0
Kausik Jati