2012-07-16 15 views
39

Wkrótce mam NSDictionary z adresami URL obrazów, które muszę pokazać w moim UITableView. Każda komórka ma tytuł i obraz. Udało mi się to zrobić, chociaż przewijanie było opóźnione, ponieważ wydawało się, że komórki pobierają swój obraz za każdym razem, gdy pojawią się na ekranie. Szukałem trochę i znalazłem SDWebImage na github. To spowodowało, że opóźnienie przewijania zniknęło. Nie jestem do końca pewien, co to było, ale wierzyłem, że to trochę buforowało. Ale! Za każdym razem, gdy otwieram aplikację po raz pierwszy, nie widzę żadnych obrazów i muszę przewinąć w dół i utworzyć kopię zapasową, aby mogły zostać dostarczone. A jeśli wyjdę z aplikacji z przyciskiem głównym i znowu się otworzę, wydaje się, że działa buforowanie, ponieważ obrazy na ekranie są widoczne, ale jeśli przewińę jedną komórkę w dół, następna komórka nie ma obrazu. Do momentu przewinięcia obok i kopii zapasowej lub kliknięcia. Czy tak powinno działać buforowanie? Lub jaki jest najlepszy sposób buforowania obrazów pobranych z Internetu? Obrazy są aktualizowane na bieżąco, więc byłem blisko, aby je zaimportować do projektu, ale lubię mieć możliwość aktualizowania zdjęć bez przesyłania aktualizacji ..Najlepszy sposób na buforowanie obrazów w aplikacji ios?

Czy nie jest możliwe załadowanie wszystkich zdjęć do całości tableview tworzy pamięć podręczną (biorąc pod uwagę, że coś jest w pamięci podręcznej) podczas uruchamiania? Czy dlatego czasami widzę komórki bez obrazów?

I tak, ciężko mi zrozumieć, czym jest pamięć podręczna.

--EDIT--

Próbowałem to tylko obrazy o tym samym rozmiarze (500x150), a aspekt błędów nie ma, jednak kiedy przewijać w górę lub w dół, tam są zdjęcia na wszystko komórki, ale na początku są w błędzie. Po wyświetleniu komórki przez kilka milisekund pojawi się odpowiedni obraz. Jest to niezwykle denerwujące, ale może tak musi być? .. Wygląda na to, że najpierw wybiera niewłaściwy indeks z pamięci podręcznej. Jeśli przewijam powoli, to widzę, że obrazy migają od niewłaściwego obrazu do prawidłowego. Jeśli przewijam szybko, to uważam, że błędne obrazy są widoczne przez cały czas, ale nie mogę stwierdzić z powodu szybkiego przewijania. Gdy szybkie przewijanie spowalnia i w końcu się zatrzymuje, niepoprawne obrazy nadal pojawiają się, ale natychmiast po zatrzymaniu przewijania aktualizuje się do właściwych obrazów. Mam również niestandardową klasę UITableViewCell, ale nie wprowadziłem żadnych dużych zmian ... Jeszcze nie przeszedłem przez mój kod, ale nie mogę wymyślić co może być nie tak ... Może mam coś w niewłaściwy porządek .. mam zaprogramowane dużo w Java, C#, PHP itp, ale mam trudności ze zrozumieniem Objective-C, ze wszystkimi .h i .m ... mam też `

@interface FirstViewController : UITableViewController{ 

/**/ 
NSCache *_imageCache; 
} 

(między innymi zmiennymi) w FirstViewController.h. Czy to nie jest poprawne? Jest to mój .

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

Czy możesz opublikować swój kod w 'cellRowRowAtIndexPath' i cokolwiek innego, co może być istotne tutaj? Buforowanie polega tylko na przechowywaniu czegoś, co powinno być potrzebne, aby uzyskać dostęp ponownie w łatwiej dostępnym miejscu, aby przyspieszyć późniejszy dostęp. W tym przypadku buforowanie oznacza, że ​​aplikacja powinna przechowywać dane obrazu dla każdej komórki po pobraniu go po raz pierwszy, a następnie po prostu ponownie użyć go w dowolnym momencie, gdy wyświetlana jest konkretna komórka (co SDWebImage powinno robić dla ty). – Dima

+0

Gotowe. Wyłączyłem SDWebImage, ale wciąż otrzymuję dziwne wyniki. Czasami wyświetlany jest zły obraz podczas przewijania. – Sti

+1

Po prostu zainicjuj 'cell.imageView.image' tuż przed tym pierwszym' dispatch_async'. – Rob

Odpowiedz

60

Buforowanie oznacza przechowywanie kopii potrzebnych danych, dzięki czemu nie trzeba jej ładować z wolniejszych źródeł. Na przykład mikroprocesory często mają pamięć podręczną, w której przechowują kopie danych, aby nie musiały korzystać z pamięci RAM, co jest znacznie wolniejsze. Dyski twarde często mają pamięć podręczną, z której system plików może uzyskać znacznie szybszy dostęp do bloków danych, do których ostatnio dostęp był.

Podobnie, jeśli aplikacja ładuje wiele obrazów z sieci, może być w interesie buforowanie ich na urządzeniu zamiast pobierania ich za każdym razem, gdy ich potrzebujesz. Jest wiele sposobów na zrobienie tego - wygląda na to, że już go znalazłeś. Możesz chcieć przechowywać zdjęcia, które pobrałeś w katalogu aplikacji/Library/Caches, szczególnie jeśli nie spodziewasz się ich zmiany.Ładowanie obrazów z dodatkowego magazynu będzie o wiele szybsze niż ładowanie ich przez sieć.

Być może zainteresuje Cię również mało znana klasa NSCache, która pozwala przechowywać potrzebne obrazy w pamięci. NSCache działa jak słownik, ale gdy pamięć się napięła, zacznie zwalniać część jego zawartości. Najpierw możesz sprawdzić pamięć podręczną dla danego obrazu, a jeśli go nie znajdziesz, możesz zajrzeć do katalogu pamięci podręcznych, a jeśli go tam nie znajdziesz, możesz go pobrać. Nic z tego nie przyspieszy ładowania obrazu w aplikacji przy pierwszym uruchomieniu, ale gdy aplikacja pobierze większość potrzebnych informacji, będzie o wiele bardziej responsywna.

+1

Wow, świetna odpowiedź. Sprawdzę NSCache. Ale nie mogę sobie wyobrazić, jak to działa. Jeśli utworzę obiekt NSCache w powiedzmy viewDidLoad, czy nie będzie on pusty przy każdym uruchomieniu aplikacji? Czy może działa to w inny sposób ... Jak mogę uzyskać dostęp do mojego poprzedniego obiektu pamięci podręcznej ..? Cóż, przeczytam o tym rano. Dzięki! – Sti

+1

Tak, to by było. Przeczytaj jednak więcej, a zobaczysz, że ma ona metodę delegata, która jest wywoływana, zanim usunie obiekt - możesz go użyć do zapisania obiektów w magazynie wtórnym. Lub możesz zaimplementować własną pamięć podręczną opartą na dysku i opuścić NSCache z równania. – Caleb

+0

Cześć Caleb, świetna odpowiedź, mam jednak notatkę. Jak dostęp do RAM może być WOLNY niż dostęp do dysku twardego. O ile wiem, jest odwrotnie, dostęp do adresu RAM jest szybszy niż jakikolwiek inny sposób przechowywania pamięci. Dzięki. – Malloc

18

Myślę, że Caleb dobrze odpowiedział na pytanie dotyczące buforowania. Chciałem tylko dotknąć procesu aktualizacji Twojego interfejsu podczas pobierania obrazów, np. Zakładając, że NSCache dla obrazów nazywa _imageCache:

pierwsze, określić właściwość kolejki operacji dla tableview:

@property (nonatomic, strong) NSOperationQueue *queue; 

potem w viewDidLoad zainicjować to:

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

A następnie w cellForRowAtIndexPath, można wtedy:

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

Wspominam tylko o tym, ponieważ widziałem ostatnio kilka próbek kodu pływających po SO, które używają GCD do aktualizacji odpowiedniej właściwości UIImageViewimage, ale w procesie wysyłania aktualizacji interfejsu użytkownika z powrotem do głównej kolejki używają ciekawych technik (np. ponowne ładowanie komórki lub tabeli, po prostu aktualizowanie właściwości obrazu istniejącego obiektu cell zwróconego na szczycie obiektu tableView:cellForRowAtIndexPath (co stanowi problem, jeśli wiersz przewinął się poza ekranem, a komórka została usunięta i jest ponownie używana dla nowy wiersz) itp.). Używając cellForRowAtIndexPath (nie mylić z tableView:cellForRowAtIndexPath), można określić, czy komórka jest nadal widoczna i/lub czy przewinęła się i została usunięta i ponownie użyta.

+0

Dziękuję bardzo za odpowiedź i kod! Próbowałem kodu, ale stało się coś dziwnego. Kiedy przewijam szybko, niektóre aspekty niektórych obrazów są błędne. Wygląda na to, że aspekt obrazu poniżej zostanie przeniesiony na obraz nad nim lub inny. odwrotnie..Czy można tego uniknąć, czy też muszę wykonać wszystkie obrazy o tym samym rozmiarze? (Lub przynajmniej ten sam aspekt) .. – Sti

+0

@StianFlatby To dziwne. Widzisz inne zachowanie, gdy przewijasz powoli, a kiedy przewijasz szybko? Czy robisz konfigurację swojego obrazu poza tą dispatch_async do głównej kolejki? Zrobiłem wiele testów, ale muszę przyznać, że wszystkie obrazy do moich wyskoków tabelarskich mają stały rozmiar (i małe miniaturki, które są zoptymalizowane pod kątem widoków stołów ... duże obrazy w tabelach zawsze będą miały fatalną wydajność). Może możesz zaktualizować swoje pytanie za pomocą pełnego kodu źródłowego 'cellForRowAtIndexPath'. – Rob

+0

@StianFlatby Właśnie przetestowałem go w bibliotece obrazów o mieszanych rozmiarach i wydaje mi się, że (a) różnica w odtwarzaniu komórek była funkcją tego, czy obraz został załadowany z pamięci podręcznej (synchronicznie w głównej kolejce), czy nie (wysyłanie asynchronicznie z kolejki tła), a nie prędkość przewijania; i (b) wygląda na to, że jeśli robisz to synchronicznie, domyślna komórka jest ponownie formatowana na podstawie rozmiaru/obecności obrazów, więc albo chcesz utworzyć własną niestandardową komórkę, dzięki czemu możesz skonfigurować swój imageView w dowolny sposób chcesz lub po prostu zmień rozmiar swoich obrazów do pewnego stałego rozmiaru. – Rob

1

Myślę, że będzie lepiej dla Ciebie użytkownika coś jak DLImageLoader. Więcej informacji ->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 
             } 
            }]; 
11

Najprostszym rozwiązaniem jest iść z czymś mocno używany, który został przetestowany stres.

SDWebImage to potężne narzędzie, które pomogło mi rozwiązać podobny problem i można go z łatwością zainstalować w kapsułkach kakao. W podfile: cache

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

Setup: obraz

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

Cache:

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

https://github.com/rs/SDWebImage

0

zdjęć buforowania można zrobić tak prosto jak to.

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

Dla części pytania o nieodpowiednich obrazów, to ze względu na ponowne wykorzystanie komórek. Ponowne użycie komórek oznacza, że ​​istniejące komórki, które są poza zasięgiem wzroku (na przykład komórki wychodzące z ekranu na górze po przewinięciu w kierunku do dołu, to te, które powracają ponownie od dołu). nieprawidłowe obrazy. Ale gdy pojawi się komórka, kod do pobierania właściwego obrazu zostanie wykonany i otrzymasz odpowiednie obrazy.

Możesz użyć symbolu zastępczego w metodzie "prepareForReuse" komórki. Ta funkcja jest używana głównie wtedy, gdy musisz zresetować wartości, gdy komórka zostanie podniesiona do ponownego użycia. Ustawienie tutaj symbolu zastępczego spowoduje, że nie otrzymasz żadnych nieprawidłowych obrazów.

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:@"[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"]]; 
Powiązane problemy