2010-09-21 13 views
6

Próbuję poprawić wydajność mojej intensywnej dla obrazu aplikacji na telefon iPhone, używając pamięci podręcznej obrazów na dysku zamiast przechodzenia przez sieć. Modelowałem pamięć podręczną obrazów po SDImageCache (http://github.com/rs/SDWebImage/blob/master/SDImageCache.m) i jest prawie taki sam, ale bez asynchronicznych operacji wejścia/wyjścia pamięci podręcznej.Przewiń widok i wydajność widoku tabeli podczas ładowania obrazów z dysku

Mam niektóre widoki przewijania i widoki tabel, które ładują te obrazy asynchronicznie. Jeśli obraz znajduje się na dysku, zostanie załadowany z pamięci podręcznej obrazów, w przeciwnym razie zostanie wysłane żądanie sieciowe, a wynik zostanie zapisany w pamięci podręcznej.

Problem, który napotykam, polega na tym, że podczas przewijania widoków przewijania lub widoków tabel zauważalne jest opóźnienie, gdy obraz jest ładowany z dysku. W szczególności animacja przechodzenia z jednej strony na drugą w widoku przewijania ma małe zamrożenie w połowie przejścia.

Próbowałem to naprawić przez:

  • Używanie NSOperationQueue i NSInvocationOperation obiektów, aby wnioski o dostęp do dysku (w taki sam sposób jak SDImageCache), ale to nie pomaga z opóźnieniem w wszystko.
  • Poprawianie kodu kontrolera widoku przewijania tak, aby ładował on tylko obrazy, gdy widok przewijania nie jest już przewijany. Oznacza to, że dostęp do dysku jest uruchamiany tylko wtedy, gdy przewijany widok przestaje się przewijać, ale jeśli natychmiast spróbuję przewinąć do następnej strony, mogę zauważyć opóźnienie wczytywania obrazu z dysku.

Czy istnieje sposób, aby mój dostęp do dysku był lepszy lub miał mniejszy wpływ na interfejs użytkownika?

Zauważ, że już buforuję obrazy również w pamięci. Więc gdy wszystko zostanie załadowane do pamięci, interfejs użytkownika jest miły i elastyczny. Ale gdy uruchamiana jest aplikacja lub są wysyłane ostrzeżenia o małej ilości pamięci, napotkam wiele opóźnień interfejsu, ponieważ obrazy są ładowane z dysku.

Odpowiednie fragmenty kodu znajdują się poniżej. Nie sądzę, że robię coś wyszukanego lub zwariowanego. Opóźnienie nie wydaje się być zauważalne na telefonie iPhone 3G, ale jest wyraźnie widoczne na iPodzie Touch drugiej generacji.

Zdjęcie Kod buforowanie:

Oto odnośny fragment mojego kodu buforowania obrazu. Całkiem proste.

- (BOOL)hasImageDataForURL:(NSString *)url { 
    return [[NSFileManager defaultManager] fileExistsAtPath:[self cacheFilePathForURL:url]]; 
} 

- (NSData *)imageDataForURL:(NSString *)url { 
    NSString *filePath = [self cacheFilePathForURL:url]; 

    // Set file last modification date to help enforce LRU caching policy 
    NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; 
    [attributes setObject:[NSDate date] forKey:NSFileModificationDate]; 
    [[NSFileManager defaultManager] setAttributes:attributes ofItemAtPath:filePath error:NULL]; 

    return [NSData dataWithContentsOfFile:filePath]; 
} 

- (void)storeImageData:(NSData *)data forURL:(NSString *)url { 
    [[NSFileManager defaultManager] createFileAtPath:[self cacheFilePathForURL:url] contents:data attributes:nil]; 
} 

Przewiń kod widok kontroler

Oto odnośny fragment kodu, który używam do wyświetlania obrazów w moim przekonaniu przewijania kontrolerów.

- (void)scrollViewDidScroll:(UIScrollView *)theScrollView { 
    CGFloat pageWidth = theScrollView.frame.size.width; 
    NSUInteger index = floor((theScrollView.contentOffset.x - pageWidth/2)/pageWidth) + 1; 

    [self loadImageFor:[NSNumber numberWithInt:index]]; 
    [self loadImageFor:[NSNumber numberWithInt:index + 1]]; 
    [self loadImageFor:[NSNumber numberWithInt:index - 1]]; 
} 

- (void)loadImageFor:(NSNumber *)index { 
    if ([index intValue] < 0 || [index intValue] >= [self.photoData count]) { 
     return; 
    } 

    // ... initialize an image loader object that accesses the disk image cache or makes a network request 

    UIView *iew = [self.views objectForKey:index];  
    UIImageView *imageView = (UIImageView *) [view viewWithTag:kImageViewTag]; 
    if (imageView.image == nil) { 
     NSDictionary *photo = [self.photoData objectAtIndex:[index intValue]]; 
     [loader loadImage:[photo objectForKey:@"url"]]; 
    } 
} 

Ładowarka obiekt obraz jest po prostu lekki przedmiot, który sprawdza pamięć podręczną dysku i decyduje, czy należy pobrać obraz z dysku lub sieci.Raz to zrobić, to wywołuje metodę na kontrolerze widoku przewijania do wyświetlania obrazu:

- (void)imageLoadedFor:(NSNumber *)index image:(UIImage *)image { 
    // Cache image in memory 
    // ... 

    UIView *view = [self.views objectForKey:index]; 
    UIImageView *imageView = (UIImageView *) [view viewWithTag:kImageViewTag]; 
    imageView.contentMode = UIViewContentModeScaleAspectFill; 
    imageView.image = image; 
} 

UPDATE

byłem eksperymentuje z aplikacją, a ja wyłączyć pamięć podręczną obrazu i powrócił do zawsze podejmowania żądania sieciowe. Wygląda na to, że zwykłe użycie żądań sieciowych do pobierania obrazów to , powodując również opóźnienie podczas przewijania widoków przewijania i widoków tabel! Oznacza to, że po zakończeniu żądania sieciowego i wyświetleniu obrazu na stronie przewijania lub komórce tabeli interfejs jest nieco opóźniony i ma kilka sekund opóźnienia, gdy próbuję go przeciągnąć.

Opóźnienie wydaje się być bardziej zauważalne podczas korzystania z pamięci podręcznej dysku, ponieważ opóźnienie występuje zawsze przy przejściu strony. Być może robię coś nie tak podczas przypisywania załadowanego obrazu do odpowiedniego UIImageView?

Również - Próbowałem używać małych obrazów (miniatury 50x50), a opóźnienie wydaje się poprawiać. Wygląda więc na to, że wydajność osiąga się przez załadowanie dużego obrazu z dysku lub załadowanie dużego obrazu do obiektu UIImage. Sądzę, że jednym z ulepszeń byłoby zmniejszenie rozmiaru obrazów ładowanych do widoku przewijania i widoków tabel, co jednak planowałem zrobić. Jednak nie rozumiem, w jaki sposób inne aplikacje intensywnie korzystające z fotografii mogą wyświetlać zdjęcia wyglądające na dość wysokiej rozdzielczości w przewijanych widokach bez problemów z wydajnością, przechodząc na dysk lub przez sieć.

+0

Mamy ten sam problem na iPadzie z dużymi obrazami. Powiadomię cię, jeśli znajdziemy jakieś rozwiązanie. –

+0

Jednym z głównych kluczy do przewijania jest nigdy nie blokowanie głównego wątku. Głównym wątkiem jest wątek interfejsu użytkownika, który musi zarządzać aktualizacjami o wysokiej szybkości klatek na ekranie i powiązanymi z nimi zdarzeniami zwrotnymi do kontrolera. Nie widząc twojego kodu NSUURLConnection nie mogę powiedzieć, czy robisz to w oddzielnym wątku, czy nie, ale to byłby mój pierwszy przeczucie. Czy możesz opublikować swój kod obsługi sieci? – Yetanotherjosh

+0

sprawdź projekt TithmageView github. Mają całkiem fajną implementację dla leniwych obrazów ładujących i buforowania dysku https://github.com/totocaster/TCImageView/blob/master/TCImageView.m –

Odpowiedz

0

Obraz z dysku jest rzeczywiście odczytywany podczas rysowania obrazu w widoku obrazu. Nawet jeśli buforujemy odczyt obrazu z dysku, nie ma to wpływu, ponieważ po prostu utrzymuje odniesienie do pliku. W tym celu może być konieczne użycie większych obrazów.

Pozdrawiam, Deepa

1

Należy sprawdzić aplikację LazyTableImages próbki.

1

Jeśli zawęziłyśmy go do aktywności sieciowej, chciałbym zamknąć żądanie, aby upewnić się, że jest ono w 100% wyłączone z głównego wątku. Chociaż można używać asynchronicznie NSURLConnection i odpowiadać na jego metody delegowania, łatwiej jest opakować synchroniczne żądanie w operacji w tle. Możesz użyć NSOperation lub Grand Central Dispatch, jeśli twoje potrzeby są bardziej złożone. An (stosunkowo) Prosty przykład w implementacji imageLoader mogą być:

// imageLoader.m 

// assumes that that imageCache uses kvp to look for images 
- (UIImage *)imageForKey:(NSString *)key 
{ 
    // check if we already have the image in memory 
    UImage *image = [_images objectForKey:key]; 

    // if we don't have an image: 
    // 1) start a background task to load an image from a file or URL 
    // 2) return a default image to display while loading 
    if (!image) { 
     [self performSelectorInBackground:@selector(loadImageForKey) withObject:key]; 
     image = [self defaultImage]; 
    } 

    return image; 
} 

- (void)loadImageForKey:(NSString *)key 
{ 
    NSAutoReleasePool *pool = [[NSAutoReleasePool alloc] init]; 

    // attempt to load the image from the file cache 
    UIImage *image = [self imageFromFileForKey:key]; 

    // if no image, load the image from the URL 
    if (!image) { 
     image = [self imageFromURLForKey:key]; 
    } 

    // if no image, return default or imageNotFound image 
    if (!image) { 
     image = [self notFoundImage]; 
    } 

    if ([_delegate respondsTo:@selector(imageLoader:didLoadImage:ForKey:)]) { 
     [_delegate imageLoader:self didLoadImage:image forKey:key]; 
    } 

    [pool release]; 
} 

- (UIImage *)imageFromURLForKey:(NSString *)key 
{ 
    NSError *error = nil; 
    NSData *imageData = [NSData dataWithContentsOfURL:[self imageURLForKey:key] 
               options:0 
               error:&error]; 

    UIImage *image; 

    // handle error if necessary 
    if (error) { 
     image = [self errorImage]; 
    } 

    // create image from data 
    else { 
     image = [UIImage imageWithData:imageData]; 
    } 

    return image; 
} 
0

miałem ten problem - jesteś uderzanie limit jak szybko UI można załadować obraz podczas przewijania - tak bym po prostu pracować wokół problemu i popraw komfort użytkowania.

  1. Tylko załadować obrazy podczas przewijania jest w nowej „stronie” (prostokąt statyczny) i
  2. umieścić wskaźnik aktywności za przezroczystą przewijania widoku obsłużyć przypadek, gdy użytkownik przesuwa się szybciej niż aplikacji można ładowanie zawartości
0

Zazwyczaj dekodowanie obrazu zajmuje dużo czasu, a to powoduje, że interfejs użytkownika ulega zawieszeniu (ponieważ wszystko dzieje się w głównym wątku). Za każdym razem, gdy dzwonisz pod numer [UIImage imageWithData:] na dużym obrazie, zauważysz problem. Dekodowanie mniejszego obrazu jest znacznie szybsze.

dwie opcje:

  1. Można załadować wersję miniatury każdego obrazu, a dopiero potem „wyostrzyć” na didFinishScrolling. Miniatury powinny wystarczająco szybko dekodować, aby nie pomijać żadnych ramek.
  2. Możesz wczytać wersję miniatur każdego obrazu lub najpierw pokazać wskaźnik ładowania, a następnie odszyfrować obraz w pełnej rozdzielczości w innym wątku i dodać go, gdy będzie gotowy. (Jest to technika używana w rodzimej aplikacji Zdjęcia).

Wolę drugie podejście; W dzisiejszych czasach GDC jest dość łatwe.

Powiązane problemy