2013-05-22 8 views
5

Potrzebuję zmienić rozmiar dużego obrazu zapisanego lokalnie (zawartego w self.optionArray), a następnie wyświetlić go w kolekcji view. Jeśli po prostu to pokażę, iOS próbuje zmienić rozmiar obrazów podczas szybkiego przewijania, powodując awarie pamięci.UICollectionView Cell Zmiana obrazu w widoku z GCD

W poniższym kodzie kolekcjaView będzie płynnie przewijana, ale czasami, gdy przewijam bardzo szybko, pojawi się niepoprawny obraz, który pokazuje, a następnie zmienia się na prawidłowy, gdy przewijanie maleje. Dlaczego nie ustawiasz tego ustawienia na cell.cellImage.image do nil?

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{ 

    CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath]; 
    cell.cellImage.image = nil; 
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

      dispatch_async(queue, ^{ 
       cell.cellImage.image = nil; 
       UIImage *test = [self.optionArray objectAtIndex:indexPath.row]; 
       UIImage *localImage2 = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)]; 

       dispatch_sync(dispatch_get_main_queue(), ^{ 

        cell.cellImage.image = localImage2 
        cell.cellTextLabel.text = @""; 
        [cell setNeedsLayout]; 
       }); 

      }); 

     } 

    return cell; 
    } 

- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize { 
    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0); 
    [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; 
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 
    return newImage; 
} 

EDIT: dodałem kolejną asynchronicznie do pamięci podręcznej pierwszego i nil i zainicjowaniu cell.image. Mam ten sam problem przy początkowym przewijaniu w dół. Jednak na przewinięciu kopii zapasowej jest teraz bez skazy.

dodałem to:

-(void)createDictionary 
{ 
    for (UIImage *test in self.optionArray) { 
     UIImage *shownImage = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)]; 
     [localImageDict setObject:shownImage forKey:[NSNumber numberWithInt:[self.optionArray indexOfObject:test]]]; 
    } 
} 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 
    if (!localImageDict) { 
     localImageDict = [[NSMutableDictionary alloc]initWithCapacity:self.optionArray.count]; 
    } 
    else { 
     [localImageDict removeAllObjects]; 
    } 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

    dispatch_async(queue, ^{ 
     [self createDictionary]; 
    }); 

} 
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
    CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath]; 
    cell.cellImage.image = nil; 
    cell.cellImage.image = [[UIImage alloc]init]; 

     if ([localImageDict objectForKey:[NSNumber numberWithInt:indexPath.row]]) { 
      cell.cellImage.image = [localImageDict objectForKey:[NSNumber numberWithInt:indexPath.row]]; 
      cell.cellTextLabel.text = @""; 
     } 
    else { 

     cell.cellImage.image = nil; 
     cell.cellImage.image = [[UIImage alloc]init]; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

     dispatch_async(queue, ^{ 
      UIImage *test = [self.optionArray objectAtIndex:indexPath.row]; 
      UIImage *shownImage = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)]; 
      [localImageDict setObject:shownImage forKey:[NSNumber numberWithInt:indexPath.row]]; 

      dispatch_sync(dispatch_get_main_queue(), ^{ 

       cell.cellImage.image = shownImage; 

       cell.cellTextLabel.text = @""; 
       [cell setNeedsLayout]; 
      }); 

     }); 
    } 

} 
return cell; 

Odpowiedz

6

Biorąc bliżej przyjrzeć się swojej próbki kodu, widzę źródło problemu pamięci. Najważniejszym problemem, który się wyskakuje jest to, że wydajesz się trzymać wszystkie swoje obrazy w tablicy. Wymaga to niezwykłej ilości pamięci (i wywnioskowałem z potrzeby zmiany rozmiarów obrazów, które muszą być duże).

Aby zmniejszyć zasięg działania aplikacji, nie należy utrzymywać tablicy obiektów UIImage. Zamiast tego po prostu utrzymuj tablicę adresów URL lub ścieżek do swoich obrazów, a następnie twórz w locie obiekty, które są potrzebne w interfejsie użytkownika (proces zwany ładowaniem leniwym). A gdy obrazek opuści ekran, możesz go zwolnić (UICollectionView, podobnie jak UITableView wykonuje wiele prac porządkowych, o ile nie zachowujesz silnych odniesień do obrazów).

Aplikacja powinna ogólnie utrzymywać tylko obiekty UIImage dla obrazów aktualnie widocznych. Możesz buforować te zmienione obrazy (na przykład przy użyciu NSCache) ze względu na wydajność, ale pamięci podręczne zostaną automatycznie usunięte, gdy skończy się pamięć.

Dobrze, że jesteś już dobrze zorientowany w przetwarzaniu asynchronicznym. W każdym razie, realizacja może wyglądać tak:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
    CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath]; 

    NSString *filename = [self.filenameArray objectAtIndex:indexPath.row]; // I always use indexPath.item, but if row works, that's great 

    UIImage *image = [self.thumbnailCache objectForKey:filename];   // you can key this on whatever you want, but the filename works 

    cell.cellImage.image = image;           // this will load cached image if found, or `nil` it if not found 

    if (image == nil)              // we only need to retrieve image if not found in our cache 
    { 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

     dispatch_async(queue, ^{ 
      UIImage *test = [UIImage imageWithContentsOfFile:filename]; // load the image here, now that we know we need it 
      if (!test) 
      { 
       NSLog(@"%s: unable to load image", __FUNCTION__); 
       return; 
      } 

      UIImage *localImage2 = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)]; 
      if (!localImage2) 
      { 
       NSLog(@"%s: unable to convert image", __FUNCTION__); 
       return; 
      } 

      [self.thumbnailCache setObject:localImage2 forKey:filename]; // save the image to the cache 

      dispatch_async(dispatch_get_main_queue(), ^{     // async is fine; no need to keep this background operation alive, waiting for the main queue to respond 
       // see if the cell for this indexPath is still onscreen; probably is, but just in case 

       CustomTabBarCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath]; 
       if (updateCell) 
       { 
        updateCell.cellImage.image = localImage2 
        updateCell.cellTextLabel.text = @""; 
        [updateCell setNeedsLayout]; 
       } 
      }); 

     }); 
    } 

    return cell; 
} 

ta zakłada, że ​​można zdefiniować właściwość klasy thumbnailCache który jest silny odniesienie do NSCache że będziesz zainicjować w viewDidLoad lub gdziekolwiek. Buforowanie to sposób na uzyskanie najlepszych z obu światów, załadowanie obrazów w pamięci w celu uzyskania optymalnej wydajności, ale zostanie on zwolniony, gdy poczujesz presję pamięci.

Najwyraźniej beztrosko zakładam "oh, po prostu zamień tablicę obrazów na tablicę nazw plików obrazów" i wiem, że prawdopodobnie będziesz musiał wejść w kilka różnych części kodu, aby to zrobić działa, ale jest to niewątpliwie źródło zużycia pamięci. Oczywiście, zawsze możesz mieć inne problemy z pamięcią (zatrzymuj cykle itp.), Ale tutaj nie ma nic w opisanym fragmencie.

+0

Wszystkie obrazy są lokalne, żadne nie są pobierane z serwera. Czy to zmienia jakąś twoją rekomendację? – Eric

+0

@Eric Ah, przepraszam, że tego nie zauważyłem. W takim przypadku mój drugi punkt raczej się nie ujawni (choć teoretycznie mógłby). Czwarty punkt nie jest też problemem. Ale punkty 1 i 3 są zdecydowanie trafne i możesz zauważyć niewielką poprawę wydajności czwartego punktu, jeśli korzystasz z pamięci podręcznej. Zachęcam Cię do przetestowania kodu na najwolniejszym możliwym urządzeniu, ponieważ niektóre z tych problemów nie pojawią się na symulatorze lub nowszym urządzeniu. – Rob

+0

Czy możesz obejrzeć moją edycję? – Eric

1

Miałem podobny problem, ale robiłem to inaczej.

Miałem też problem "pop-in", ponieważ obrazy, które zostały załadowane asynchronicznie, były migane, dopóki nie pojawiły się poprawne.

Jednym z powodów tego jest fakt, że bieżąca ścieżka indeksu dla komórki, która została początkowo usunięta, nie pasuje do indeksu umieszczanego w niej obrazu.

Zasadniczo, jeśli szybko przewiniesz od 0 do 19, a komórka, którą chcesz zaktualizować, ma numer # 20 i chcesz, aby wyświetlała obraz # 20, ale nadal asynchronicznie ładuje obrazy 3, 7, 14.

Aby temu zapobiec, śledziłem dwa wskaźniki; # 1) najnowszą ścieżkę indeksowania, która odzwierciedla faktyczną pozycję komórki i # 2) indeks odpowiadający obrazowi, który jest faktycznie ładowany asynchronicznie (w tym przypadku powinien to być faktycznie ścieżka indeksu, którą przechodzisz do komórki na ścieżceodcinkuindex, zostaje ona zachowana jako proces asynchroniczny działa przez kolejkę, więc w rzeczywistości będą to "stare" dane dla niektórych ładowań obrazów).

Jednym ze sposobów uzyskania najnowszej ścieżki indeksowej może być utworzenie prostej metody, która zwraca NSInteger dla bieżącej lokalizacji komórki. Zapisz to jako currentIndex.

Następnie dodałem kilka instrukcji, które sprawdziły, czy oba były równe przed faktycznym wypełnieniem obrazu.

tak, jeśli (currentIndex == imageIndex) następnie załadować obraz.

jeśli umieścisz komunikat NSLog (@ "CURRENT ...% d ... IMAGE ...% d", currentIndex, imageIndex) przed tymi instrukcjami if, które możesz zobaczyć dość wyraźnie, gdy te dwa nie pasują do siebie i połączenia asynchroniczne powinny zostać zakończone.

Mam nadzieję, że to pomoże.

1

Znalazłem sformułowanie tego, co powiedział chuey101, mylące. Wymyśliłem sposób, a potem zrozumiałem, że chuey101 oznacza to samo.

Jeśli ma to pomóc komuś, obrazy są migane i zmieniane z powodu różnych uruchomionych wątków. Tak więc, kiedy spawnujesz wątek dla operacji na obrazie, jego zostanie utworzony dla konkretnej komórki nie, powiedzmy c1. Ale w końcu, kiedy faktycznie załadujesz swój obraz do komórki, będzie to komórka, na którą patrzysz, ta, do której przewiniesz - powiedzmy c2. Tak więc, kiedy przewinąłeś do c2, pojawiły się c2 wątki, po jednej przewracanej na drugą, podczas przewijania. Z tego co rozumiem, wszystkie te wątki będą próbowały ładować swoje obrazy do bieżącej komórki, c2. Masz więc błyski obrazów.

Aby tego uniknąć, musisz sprawdzić, czy ładujesz obraz do komórki, do którego chcesz załadować. Zatem pobierz collectionviewcell indexpath.row przed załadowaniem do niego obrazu (loading_image_into_cell). Pobierz także komórkę, z której utworzono wątek, zanim odrodzisz wątek, tj. W głównym wątku (image_num_to_load). Teraz przed załadowaniem sprawdź, czy te dwie liczby są równe.

Problem rozwiązany :)