wake-up-neo.net

Der beste Weg, Bilder auf der iOS-App zwischenzuspeichern?

In Kürze habe ich eine NSDictionary mit URLs für Bilder, die ich in meiner UITableView anzeigen muss. Jede Zelle hat einen Titel und ein Bild. Ich hatte dies erfolgreich gemacht, obwohl das Scrollen verzögerte, da es schien, als würden die Zellen jedes Mal ihr Bild herunterladen, wenn sie auf den Bildschirm kamen ... Ich suchte etwas und fand SDWebImage auf github. Dies führte dazu, dass der Scroll-Lagg wegging. Ich bin mir nicht ganz sicher, was es getan hat, aber ich glaubte, dass es einige Zwischenspeicherungen gegeben hat. Aber! Jedes Mal, wenn ich die App zum ersten Mal öffne, sehe ich KEINE Bilder, und ich muss nach unten scrollen und zurückkehren, damit sie ankommen. Und wenn ich die App mit der Home-Taste beende und wieder öffne, scheint das Zwischenspeichern zu funktionieren, da die Bilder auf dem Bildschirm sichtbar sind. Wenn ich jedoch eine Zelle nach unten scrolle, hat die nächste Zelle kein Bild. Bis ich an ihm vorbei und zurück blättere oder wenn ich darauf klicke. Soll Caching so funktionieren? Oder wie kann man am besten aus dem Internet heruntergeladene Bilder zwischenspeichern? Die Bilder werden regelmäßig aktualisiert, daher war ich kurz davor, sie einfach in das Projekt zu importieren, aber ich habe die Möglichkeit, Bilder zu aktualisieren, ohne ein Update hochzuladen.

Ist es nicht möglich, beim Start alle Bilder für den gesamten Tableview aus dem Cache zu laden (da sich etwas im Cache befindet)? Habe ich deshalb manchmal Zellen ohne Bilder?

Und ja, es fällt mir schwer, den Cache zu verstehen.

--EDIT--

Ich habe dies nur mit Bildern der gleichen Größe (500x150) versucht, und der Aspektfehler ist weg. Wenn ich jedoch nach oben oder unten scrolle, befinden sich Bilder in allen Zellen, aber zuerst sind sie falsch. Nachdem sich die Zelle einige Millisekunden in der Ansicht befunden hat, wird das rechte Bild angezeigt. Das ist erstaunlich ärgerlich, aber vielleicht muss es so sein? Wie es scheint, wählt es zuerst den falschen Index aus dem Cache. Wenn ich langsam scrolle, kann ich sehen, dass die Bilder vom falschen Bild zum richtigen blinken. Wenn ich schnell scrolle, glaube ich, dass die falschen Bilder immer sichtbar sind, was ich jedoch aufgrund des schnellen Scrollens nicht feststellen kann. Wenn sich der schnelle Bildlauf verlangsamt und schließlich stoppt, werden immer noch die falschen Bilder angezeigt. Unmittelbar nach dem Bildlauf werden die richtigen Bilder angezeigt. Ich habe auch eine eigene UITableViewCell-Klasse, aber ich habe keine großen Änderungen vorgenommen. Ich habe meinen Code noch nicht sehr viel durchgearbeitet, aber ich kann mir nicht vorstellen, was falsch sein könnte. Vielleicht habe ich etwas in der falsche Reihenfolge .. Ich habe viel in Java, c #, php usw. programmiert, aber ich habe Schwierigkeiten, Objective-c mit all dem .h und .m ....__ zu verstehen. Ich habe auch `

@interface FirstViewController : UITableViewController{

/**/
NSCache *_imageCache;
}

(unter anderen Variablen) in FirstViewController.h. Ist das nicht richtig?

Hier ist meine 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

Caching bedeutet lediglich, eine Kopie der benötigten Daten aufzubewahren, damit Sie sie nicht aus einer langsameren Quelle laden müssen. Beispielsweise verfügen Mikroprozessoren häufig über einen Cache-Speicher, in dem sie Datenkopien aufbewahren, damit sie nicht auf den RAM zugreifen müssen, was viel langsamer ist. Festplatten haben häufig Speichercaches, aus denen das Dateisystem viel schneller auf Datenblöcke zugreifen kann, auf die kürzlich zugegriffen wurde.

Wenn Ihre App viele Bilder aus dem Netzwerk lädt, kann es in Ihrem Interesse sein, sie auf Ihrem Gerät zu cachen, anstatt sie jedes Mal herunterzuladen, wenn Sie sie benötigen. Es gibt viele Möglichkeiten, dies zu tun - es klingt, als hätten Sie schon eine gefunden. Speichern Sie die heruntergeladenen Bilder möglicherweise im Verzeichnis/Library/Caches Ihrer App, insbesondere wenn Sie nicht erwarten, dass sich die Bilder ändern. Das Laden der Bilder vom Sekundärspeicher ist viel schneller als das Laden über das Netzwerk.

Sie könnten auch an der wenig bekannten NSCache - Klasse interessiert sein, um die benötigten Bilder im Speicher zu halten. NSCache funktioniert wie ein Wörterbuch, aber wenn der Speicher knapp wird, wird ein Teil des Inhalts freigegeben. Sie können den Cache zunächst für ein bestimmtes Bild überprüfen. Wenn Sie es dort nicht finden, können Sie in Ihrem Cache-Verzeichnis nachsehen. Wenn Sie es dort nicht finden, können Sie es herunterladen. Dies alles beschleunigt das Laden von Bildern in Ihre App, wenn Sie sie zum ersten Mal ausführen. Sobald Ihre App jedoch das meiste heruntergeladen hat, was sie benötigt, ist sie viel reaktionsschneller.

69
Caleb

Ich denke, Caleb hat die Caching-Frage gut beantwortet. Ich wollte gerade den Vorgang zum Aktualisieren Ihrer Benutzeroberfläche beim Abrufen von Bildern ansprechen, z. vorausgesetzt, Sie haben eine NSCache für Ihre Bilder namens _imageCache:

Definieren Sie zunächst eine Eigenschaftswarteschlange für die Tabellenansicht:

@property (nonatomic, strong) NSOperationQueue *queue;

Dann initialisieren Sie in viewDidLoad das:

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

In cellForRowAtIndexPath können Sie dann:

- (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;
}

Ich erwähne dies nur, da ich kürzlich auf SO ein paar Codebeispiele gesehen habe, die GCD zum Aktualisieren der entsprechenden UIImageViewimage -Eigenschaft verwenden. Beim Aktualisieren der Benutzeroberflächen-Aktualisierung in die Hauptwarteschlange verwenden sie jedoch das Update kuriose Techniken (z. B. Neuladen der Zelle oder Tabelle, Aktualisieren der Bildeigenschaft des vorhandenen cell-Objekts, das oben im tableView:cellForRowAtIndexPath zurückgegeben wird (was ein Problem ist, wenn die Zeile vom Bildschirm gescrollt wurde und die Zelle in die Warteschlange gestellt wurde und wird wiederverwendet für eine neue Zeile) usw.). Mit cellForRowAtIndexPath (nicht zu verwechseln mit tableView:cellForRowAtIndexPath) können Sie feststellen, ob die Zelle noch sichtbar ist und/oder ob sie möglicherweise gescrollt wurde und aus der Warteschlange genommen und erneut verwendet wurde.

23
Rob

Die einfachste Lösung besteht in der Verwendung eines stark beanspruchten Stresstests.

SDWebImage ist ein leistungsstarkes Tool, das mir dabei geholfen hat, ein ähnliches Problem zu lösen, und es kann problemlos mit Kakaobohnen installiert werden. In podfile:

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

Setup-Cache:

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

Cache-Image:

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

https://github.com/rs/SDWebImage

13
Matt Perejda

Der Teil der Frage nach falschen Bildern ist auf die Wiederverwendung von Zellen zurückzuführen. Die Wiederverwendung von Zellen bedeutet, dass die vorhandenen Zellen, die nicht mehr sichtbar sind (z. B. die Zellen, die beim Scrollen nach unten aus dem Bildschirm nach oben gehen, diejenigen sind, die von unten wieder zurückkommen.) Und so erhalten Sie falsche Bilder. Sobald die Zelle angezeigt wird, wird der Code zum Abrufen des richtigen Bildes ausgeführt und Sie erhalten die richtigen Bilder. 

Sie können einen Platzhalter in der 'prepareForReuse'-Methode der Zelle verwenden. Diese Funktion wird meistens verwendet, wenn Sie die Werte zurücksetzen müssen, wenn die Zelle zur Wiederverwendung aufgerufen wird. Wenn Sie hier einen Platzhalter einstellen, wird sichergestellt, dass Sie keine falschen Bilder erhalten.

1
Swasidhant

Ich denke, es wird für Sie so etwas wie DLImageLoader ..__ sein. Weitere Informationen -> 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

Das Zwischenspeichern von Bildern ist so einfach.

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:@"[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"]];
0
Kausik Jati