Menghilangkan gambar UIImageNamed: FUD

117

Edit Feb 2014: Perhatikan bahwa pertanyaan ini berasal dari iOS 2.0! Persyaratan dan penanganan gambar telah banyak berubah sejak saat itu. Retina membuat gambar lebih besar dan memuatnya sedikit lebih rumit. Dengan dukungan bawaan untuk gambar iPad dan retina, Anda pasti harus menggunakan ImageNamed dalam kode Anda .

Saya melihat banyak orang mengatakan imageNameditu buruk tetapi jumlah orang yang sama mengatakan kinerjanya bagus - terutama saat rendering UITableView. Lihat pertanyaan SO ini misalnya atau artikel ini di iPhoneDeveloperTips.com

UIImage's imageNamedmetode yang digunakan untuk bocor sehingga itu sebaiknya dihindari tetapi telah diperbaiki di rilis terbaru. Saya ingin memahami algoritme caching lebih baik untuk membuat keputusan yang masuk akal tentang di mana saya dapat mempercayai sistem untuk menyimpan gambar saya dan di mana saya harus bekerja ekstra dan melakukannya sendiri. Pemahaman dasar saya saat ini adalah bahwa hal itu sederhana NSMutableDictionarydari UIImagesdireferensikan oleh nama file. Itu menjadi lebih besar dan ketika memori habis itu menjadi jauh lebih kecil.

Misalnya, apakah ada yang tahu pasti bahwa cache gambar di belakang imageNamedtidak merespons didReceiveMemoryWarning? Sepertinya Apple tidak akan melakukan ini.

Jika Anda memiliki wawasan tentang algoritma caching, silakan posting di sini.

Rog
sumber
2
saya juga. Sepertinya SO tidak penuh dengan jenius seperti yang saya kira.
Rog
Sepertinya Anda telah menghabiskan waktu untuk menyelidiki ini. Apakah Anda pernah melakukan eksperimen yang akan menunjukkan dampak negatif dari gambar UIImageNamed dengan versi cocoa-touch terbaru? Buat UITableView dan tambahkan banyak (ribuan) baris dan lihat apakah performanya menurun saat Anda menampilkan gambar yang berbeda di setiap baris. Mungkin kemudian orang dapat mengomentari temuan Anda.
StefanB
Sebenarnya, saya belum melakukan eksperimen sebanyak itu. Saya menggunakan imageNamed dan meskipun saya belum mengoptimalkan memori aplikasi ini, saya tidak memiliki apa pun yang dapat saya tunjuk langsung ke imageNamed sebagai babi memori. Saya telah menunjukkan beberapa posting blog yang mengeluh tentang imagedNamed di sini dan, saya telah mengajukan pertanyaan serupa di papan apel yang mendapat lebih banyak tindakan. Saya akan mem-posting ulang ringkasan dan tautan di sini ketika saya punya waktu dan merasa sudah ada balasan yang akan didapatnya.
Rog

Jawaban:

85

tldr: ImagedNamed baik-baik saja. Ini menangani memori dengan baik. Gunakan dan berhentilah khawatir.

Sunting November 2012 : Perhatikan bahwa pertanyaan ini berasal dari iOS 2.0! Persyaratan dan penanganan gambar telah banyak berubah sejak saat itu. Retina membuat gambar lebih besar dan memuatnya sedikit lebih rumit. Dengan dukungan bawaan untuk gambar iPad dan retina, Anda pasti harus menggunakan ImageNamed dalam kode Anda. Sekarang, demi anak cucu:

Rangkaian sister di Apple Dev Forums menerima lalu lintas yang lebih baik. Secara khusus Rincewind menambahkan beberapa otoritas.

Ada masalah di iPhone OS 2.x di mana imageNamed: cache tidak akan dihapus, bahkan setelah peringatan memori. Pada saat yang sama + imageNamed: telah mendapatkan banyak kegunaan bukan untuk cache, tetapi untuk kenyamanan, yang mungkin telah memperbesar masalah lebih dari yang seharusnya.

sambil memperingatkan itu

Di depan kecepatan, ada kesalahpahaman umum tentang apa yang sedang terjadi. Hal terbesar yang dilakukan + imageNamed: adalah mendekode data gambar dari file sumber, yang hampir selalu memperbesar ukuran data secara signifikan (misalnya, file PNG berukuran layar mungkin menghabiskan beberapa lusin KB saat dikompresi, tetapi menghabiskan lebih dari setengah MB didekompresi - lebar * tinggi * 4). Sebaliknya + imageWithContentsOfFile: akan mendekompresi gambar itu setiap kali data gambar dibutuhkan. Seperti yang dapat Anda bayangkan, jika Anda hanya membutuhkan data gambar sekali, Anda tidak memenangkan apa pun di sini, kecuali memiliki versi cache dari gambar yang berkeliaran, dan kemungkinan lebih lama dari yang Anda butuhkan. Namun, jika Anda memiliki gambar besar yang perlu sering digambar ulang, maka ada alternatif lain, meskipun yang saya sarankan terutama adalah menghindari menggambar ulang gambar besar itu :).

Sehubungan dengan perilaku umum cache, ia melakukan cache berdasarkan nama file (jadi dua contoh + imageNamed: dengan nama yang sama akan menghasilkan referensi ke data cache yang sama) dan cache akan tumbuh secara dinamis saat Anda meminta lebih banyak gambar melalui + imageNamed :. Pada iPhone OS 2.xa bug mencegah cache menyusut saat peringatan memori diterima.

dan

Pemahaman saya adalah bahwa + imageNamed: cache harus menghormati peringatan memori pada iPhone OS 3.0. Ujilah saat Anda mendapat kesempatan dan laporkan bug jika ternyata tidak demikian.

Jadi, begitulah. imageNamed: tidak akan menghancurkan jendela Anda atau membunuh anak-anak Anda. Ini cukup sederhana tetapi ini adalah alat pengoptimalan. Sayangnya nama itu salah dan tidak ada persamaan yang mudah digunakan - oleh karena itu orang-orang menggunakannya secara berlebihan dan menjadi kesal ketika itu hanya melakukan tugasnya

Saya menambahkan kategori ke UIImage untuk memperbaikinya:

// 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 juga menyertakan beberapa kode contoh untuk membuat versi Anda sendiri yang dioptimalkan. Saya tidak bisa melihat itu sepadan dengan pemeliharaannya tetapi ini dia untuk kelengkapan.

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);

Kekurangan dari kode ini adalah bahwa gambar yang didekodekan menggunakan lebih banyak memori tetapi rendering lebih cepat.

Rog
sumber
Tampaknya di iOS11, [UIImage imageNamed:] tidak mengeluarkan gambar dari cache jika gambar berasal dari katalog xcasset. Hal ini menyebabkan crash karena kehabisan memori.
Juraj Antas
5

Menurut pengalaman saya, cache gambar yang dibuat oleh imageNamed tidak merespons peringatan memori. Saya memiliki dua aplikasi yang seramping yang saya bisa dapatkan sejauh manajemen mem, tetapi masih macet secara misterius karena kurangnya mem. Saat saya berhenti menggunakan imageNamed untuk memuat gambar, kedua aplikasi menjadi lebih stabil secara dramatis.

Saya akan mengakui bahwa kedua aplikasi memuat gambar yang agak besar, tetapi tidak ada yang benar-benar luar biasa. Pada aplikasi pertama, saya melewatkan caching sama sekali karena kecil kemungkinannya pengguna akan kembali ke gambar yang sama dua kali. Yang kedua, saya membangun kelas caching yang sangat sederhana dengan melakukan apa yang Anda sebutkan - menjaga UIImages dalam NSMutableDictionary dan kemudian membersihkan isinya jika saya menerima peringatan memori. Jika imageNamed: di-cache seperti itu, maka saya seharusnya tidak melihat peningkatan kinerja apa pun. Semua ini berjalan di 2.2 - Saya tidak tahu apakah ada implikasi 3.0 tentang ini.

Anda dapat menemukan pertanyaan saya yang lain seputar masalah ini dari aplikasi pertama saya di sini: Pertanyaan StackOverflow tentang cache gambar UII

Satu catatan lain - InterfaceBuilder menggunakan imageNamed di bawah sampulnya. Sesuatu yang perlu diingat jika Anda mengalami masalah ini.

Bdebeez
sumber
Saya melihat perilaku yang persis sama, dan membaca dari file secara langsung menyelesaikannya. Juga, itu terjadi ketika saya mencoba memuat gambar yang sangat besar - 11.456 x 3.226 px, 15MB. Teori saya adalah bahwa sistem kehabisan memori sebagian melalui operasi cache ImageNamed. Dan tidak ada penanganan bawaan untuk kasus ini.
Dogweather