Usuwanie obrazu UIImage o nazwie: FUD

117

Edycja lutego 2014: Zwróć uwagę, że to pytanie pochodzi z iOS 2.0! Od tego czasu wymagania dotyczące obrazu i obsługa znacznie się zmieniły. Retina powiększa obrazy i ładuje je nieco bardziej skomplikowane. Dzięki wbudowanej obsłudze obrazów iPada i siatkówki, z pewnością powinieneś używać ImageNamed w swoim kodzie .

Widzę, że wiele osób twierdzi, że imageNamedjest źle, ale równa liczba osób twierdzi, że wydajność jest dobra - zwłaszcza podczas renderowania UITableViews. Zobacz na przykład to pytanie SO lub ten artykuł na iPhoneDeveloperTips.com

UIImage„s imageNamedmetoda stosowana przeciekać tak było najlepiej unikać, ale został naprawiony w najnowszych wersjach. Chciałbym lepiej zrozumieć algorytm buforowania, aby podjąć uzasadnioną decyzję o tym, gdzie mogę zaufać systemowi buforując moje obrazy i gdzie muszę zrobić więcej i zrobić to sam. Mój obecny podstawowe zrozumienie, że to prosta NSMutableDictionaryod UIImagesodwołuje pliku. Zwiększa się, a kiedy kończy się pamięć, robi się znacznie mniejsza.

Na przykład, czy ktoś wie na pewno, że pamięć podręczna obrazu za nią imageNamednie reaguje didReceiveMemoryWarning? Wydaje się mało prawdopodobne, aby Apple tego nie zrobił.

Jeśli masz wgląd w algorytm buforowania, opublikuj go tutaj.

Rog
źródło
2
Ja też. Wygląda na to, że SO nie jest tak pełen geniuszy, jak myślałem.
Rog
Wygląda na to, że spędziłeś trochę czasu na badaniu tego. Czy przeprowadziłeś jakieś eksperymenty, które pokazałyby jakikolwiek negatywny wpływ obrazu UIImage, nazwanego najnowszą wersją dotykową kakao? Utwórz UITableView i dodaj partie (wiele tysięcy) wierszy i zobacz, czy wydajność spada, gdy wyświetlasz inny obraz w każdym wierszu. Może wtedy ludzie będą mogli skomentować twoje odkrycia.
stefanB
Właściwie nie robiłem zbyt wiele eksperymentów. Używam imageNamed i chociaż nie zoptymalizowałem jeszcze pamięci w tej aplikacji, nie mam nic, co mógłbym wskazać bezpośrednio na imageNamed jako świnię pamięci. Wskazałem kilka postów na blogu narzekających na obrazowanie Nazwany tutaj i zadałem podobne pytanie na tablicach jabłek, które uzyskały więcej akcji. Ponownie opublikuję podsumowanie i link tutaj, gdy będę miał czas i poczuję, że otrzymałem odpowiedzi, które otrzyma.
Rog

Odpowiedzi:

85

tldr: ImagedNamed jest w porządku. Dobrze radzi sobie z pamięcią. Użyj go i przestań się martwić.

Edytuj listopad 2012 : Zwróć uwagę, że to pytanie pochodzi z iOS 2.0! Od tego czasu wymagania dotyczące obrazu i obsługa znacznie się zmieniły. Retina powiększa obrazy i ładuje je nieco bardziej skomplikowane. Dzięki wbudowanej obsłudze obrazów iPada i siatkówki, z pewnością powinieneś używać ImageNamed w swoim kodzie. Teraz, dla dobra potomnych:

Wątek siostra na forum jabłko Dev otrzymał jakieś lepsze ruchu. W szczególności Rincewind dodał trochę autorytetu.

W systemie iPhone OS 2.x występują problemy, w których plik imageNamed: cache nie był czyszczony nawet po ostrzeżeniu dotyczącym pamięci. W tym samym czasie + imageNamed: zyskał wiele zastosowań nie ze względu na pamięć podręczną, ale ze względu na wygodę, która prawdopodobnie powiększyła problem bardziej niż powinien.

ostrzegając, że

Jeśli chodzi o prędkość, istnieje ogólne niezrozumienie tego, co się dzieje. Największą rzeczą, jaką robi + imageNamed: jest dekodowanie danych obrazu z pliku źródłowego, co prawie zawsze znacznie zawyża rozmiar danych (na przykład plik PNG o rozmiarze ekranu może zużywać kilkadziesiąt KB podczas kompresji, ale zajmuje ponad pół MB zdekompresowany - szerokość * wysokość * 4). Natomiast + imageWithContentsOfFile: dekompresuje ten obraz za każdym razem, gdy potrzebne są dane obrazu. Jak możesz sobie wyobrazić, jeśli potrzebujesz danych obrazu tylko raz, nic tutaj nie wygrywasz, poza tym, że wersja obrazu przechowywana w pamięci podręcznej kręci się w pobliżu i prawdopodobnie dłużej niż potrzebujesz. Jeśli jednak masz duży obraz, który musisz często przerysowywać, istnieją alternatywy, chociaż przede wszystkim zalecałbym unikanie przerysowywania tego dużego obrazu :).

Jeśli chodzi o ogólne zachowanie pamięci podręcznej, robi ona pamięć podręczną na podstawie nazwy pliku (więc dwa wystąpienia + imageNamed: o tej samej nazwie powinny skutkować odwołaniami do tych samych danych w pamięci podręcznej), a pamięć podręczna będzie rosła dynamicznie, gdy zażądasz więcej obrazów za pośrednictwem + imageNamed :. W systemie iPhone OS 2.xa błąd uniemożliwia zmniejszenie pamięci podręcznej po otrzymaniu ostrzeżenia dotyczącego pamięci.

i

Rozumiem, że + imageNamed: cache powinien uwzględniać ostrzeżenia dotyczące pamięci w systemie iPhone OS 3.0. Przetestuj, kiedy będziesz miał okazję i zgłoś błędy, jeśli okaże się, że tak nie jest.

Więc masz to. imageNamed: nie rozbije okien ani nie zamorduje twoich dzieci. To całkiem proste, ale jest to narzędzie optymalizacyjne. Niestety jest źle nazwany i nie ma odpowiednika, który byłby tak łatwy w użyciu - stąd ludzie nadużywają go i denerwują się, gdy po prostu wykonuje swoją pracę

Dodałem kategorię do UIImage, aby to naprawić:

// header omitted
// Before you waste time editing this, please remember that a semi colon at the end of a method definition is valid and a matter of style.
+ (UIImage*)imageFromMainBundleFile:(NSString*)aFileName; {
    NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
    return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", bundlePath,aFileName]];
}

Rincewind dołączył również przykładowy kod do zbudowania własnej zoptymalizowanej wersji. Nie sądzę, że warto go utrzymać, ale tutaj jest to kompletność.

CGImageRef originalImage = uiImage.CGImage;
CFDataRef imageData = CGDataProviderCopyData(
     CGImageGetDataProvider(originalImage));
CGDataProviderRef imageDataProvider = CGDataProviderCreateWithCFData(imageData);
CFRelease(imageData);
CGImageRef image = CGImageCreate(
     CGImageGetWidth(originalImage),
     CGImageGetHeight(originalImage),
     CGImageGetBitsPerComponent(originalImage),
     CGImageGetBitsPerPixel(originalImage),
     CGImageGetBytesPerRow(originalImage),
     CGImageGetColorSpace(originalImage),
     CGImageGetBitmapInfo(originalImage),
     imageDataProvider,
     CGImageGetDecode(originalImage),
     CGImageGetShouldInterpolate(originalImage),
     CGImageGetRenderingIntent(originalImage));
CGDataProviderRelease(imageDataProvider);
UIImage *decompressedImage = [UIImage imageWithCGImage:image];
CGImageRelease(image);

Wadą tego kodu jest to, że zdekodowany obraz zużywa więcej pamięci, ale renderowanie jest szybsze.

Rog
źródło
Wygląda na to, że na iOS11 [UIImage imageNamed:] nie usuwa obrazów z pamięci podręcznej, jeśli obrazy pochodzą z katalogu xcasset. Prowadzi to do awarii z powodu braku pamięci.
Juraj Antas
5

Z mojego doświadczenia wynika, że ​​pamięć podręczna obrazu utworzona przez imageNamed nie reaguje na ostrzeżenia dotyczące pamięci. Miałem dwie aplikacje, które były tak oszczędne, jak tylko mogłem, jeśli chodzi o zarządzanie pamięcią, ale nadal w niewytłumaczalny sposób ulegały awarii z powodu braku pamięci. Kiedy przestałem używać imageNamed do ładowania obrazów, obie aplikacje stały się znacznie bardziej stabilne.

Przyznam, że obie aplikacje ładowały dość duże obrazy, ale nic, co byłoby całkowicie niezwykłe. W pierwszej aplikacji po prostu całkowicie pominąłem buforowanie, ponieważ było mało prawdopodobne, że użytkownik wróci do tego samego obrazu dwa razy. W drugim utworzyłem naprawdę prostą klasę buforującą, robiąc to, o czym wspomniałeś - utrzymując UIImages w NSMutableDictionary, a następnie opróżniając jego zawartość, jeśli otrzymałem ostrzeżenie dotyczące pamięci. Jeśli imageNamed: miałby tak buforować, nie powinienem był widzieć żadnej aktualizacji wydajności. Wszystko to działało w wersji 2.2 - nie wiem, czy ma to jakieś konsekwencje w wersji 3.0.

Moje inne pytanie dotyczące tego problemu z mojej pierwszej aplikacji można znaleźć tutaj: Pytanie StackOverflow dotyczące buforowania UIImage

Jeszcze jedna uwaga - InterfaceBuilder używa imageNamed pod okładkami. Coś, o czym należy pamiętać, jeśli napotkasz ten problem.

Bdebeez
źródło
Widziałem dokładnie to samo zachowanie i odczytanie z pliku bezpośrednio rozwiązało problem. Dzieje się tak również, gdy próbuję załadować bardzo duży obraz - 11 456 x 3226 pikseli, 15 MB. Moja teoria jest taka, że ​​systemowi zabrakło pamięci w trakcie operacji pamięci podręcznej ImageNamed. W tym przypadku nie ma wbudowanej obsługi.
Dogweather