ABONAMENTE VIDEO REDACȚIA
RO
EN
×
▼ LISTĂ EDIȚII ▼
Numărul 22
Abonament PDF

iOS image caching. Libraries benchmark

Bogdan Poplauschi
Senior iOS Developer
@Yardi România
PROGRAMARE


În ultimii ani, tendința aplicațiilor iOS se îndreaptă spre un design cât mai interactiv și plăcut ochiului. Deoarece prezentarea imaginilor este un element cheie în tot acest proces, majoritatea aplicațiilor folosesc imagini care trebuie downloadate și afișate. Foarte mulți developeri au fost puși la un moment dat în situația de a-și popula controalele UI cu diferite imagini. Descărcarea de astfel de imagini consumă destul de multe resurse, cum ar fi date din serviciul de internet mobil, baterie, CPU. Prin urmare, din nevoia de a minimiza consumul acestor resurse, s-a dezvoltat așa numitul pattern cache.

Pentru a dobândi un user experience cât mai bun, este foarte important să înțelegem ce se întâmplă în interiorul sistemului iOS, atunci când stocăm imagini în cache sau le încărcăm.

În plus, consider că un set de benchmarks pentru bibliotecile open-source de image caching poate fi foarte util în alegerea soluției potrivite.

2. Abordarea Clasică

  • se downloadează imaginile în mod asincron.
  • se procesează imaginile pentru a fi afișate (modificarea dimensiunii, îndepărtarea efectului de ochi roșii, îndepărtarea marginilor etc.).
  • se stochează imaginile pe flash drive (unitatea internă de stocare).
  • se citesc de pe flash drive și se afișează la cerere.
// assuming we have an NSURL *imageUrl and UIImageView *imageView, we need to load the image from the URL and display it in the imageView 
if ([self hasImageDataForURL:imageUrl] {
   
	NSData *data = [self imageDataForUrl:imageUrl];
  
	UIImage *image = [UIImage imageWithData:imageData];
   
	dispatch_async(dispatch_get_main_queue(), ^{
     imageView.image = image;
   });
	
} else {
	
   [self downloadImageFromURL:imageUrl withCompletion:^(NSData *imageData, …) 
	{
     [self storeImageData:imageData …];
     
	UIImage *image = [UIImage imageWithData:imageData];
     
	dispatch_async(dispatch_get_main_queue(), ^{
       
	imageView.image = image;
     
	});
   
	}];
}

Matematica FPS

- 60 FPS este idealul pentru orice actualizare de UI, pentru a asigura o experiență fără cusur.

- 60 FPS => 16.7 ms/cadru. Acest lucru înseamnă că, dacă oricare dintre operațiunile de pe main queue durează mai mult de 16.7 ms, FPS-ul la scroll scade vizibil (efect de sacadare) deoarece CPU se va ocupa de alte operații, și nu de actualizarea UI.

3. Dezavantajele abordării clasice

Încărcarea imaginilor sau a fișierelor în general, de pe flash drive, e o operație costisitoare (accesarea flash drive-urilor este mult mai lentă decât cea a memoriei RAM).

Crearea unei instanțe Ullmage va avea ca rezultat o versiune comprimată a imaginii, mapată pe o secțiune de memorie (mapped memory). Imaginea comprimată este mică și nu poate fi afișată direct. În cazul încărcării de pe flash drive, imaginea este doar mapată, urmând ca încărcarea în memorie să se facă la cerere. Decomprimarea unei imagini este de asemenea costisitoare.

Atribuirea proprietății "image" din imageView în acest caz va crea un CATransaction care va fi înregistrat în run loop. La următoarea iterație, executarea CATransaction-ului implică (în funcție de imagine), crearea unor copii ale tuturor imaginilor care au fost setate ca layer contents. Copierea de imagini include:

  • alocarea de buffere pentru scriere/citire de fișiere și decomprimare,
  • citirea datelor de pe flash drive în memorie,
  • decomprimarea de imagini (rezultă un raw bitmap) - implică un consum ridicat de CPU,
  • imaginile nealiniate corect pe biți sunt copiate de către CoreAnimation pentru a fi corectate și redate corespunzător. Acest lucru nu este menționat în Apple docs, dar folosirea Instruments arată apeluri CA::Render::copy_image chiar și atunci când instrumentul CoreAnimation nu indică nicio imagine copiată.
  • începând cu iOS 7, decodorul hardware JPEG mai este accesibil doar aplicațiilor sistem. Aceasta înseamnă că aplicațiile noastre se bazează pe un decodor software care este mult mai încet. Acest aspect a fost documentat de către echipa FastImageCache pe pagina lor Github, dar și de Nick Lockwood într-un post pe Twitter.

4. O soluție robustă de caching a imaginilor în iOS ar trebui să:

  • descarce imagini în mod asincron, astfel încât main queue să fie folosit cât mai puțin posibil,
  • decomprime imaginile pe un background queue. Acest lucru nu e deloc simplu. Pentru mai multe detalii, accesați http://www.cocoanetics.com/2011/10/avoiding-image-decompression-sickness/ .
  • stocheze imaginea atât în cache-ul din memorie, cât și în cel de pe flash drive. Procesul de caching pe flash drive este important deoarece aplicația ar putea fi închisă sau ar putea fi nevoită să elibereze memorie din cauza resurselor limitate. În acest caz, re-încărcarea imaginilor de pe flash drive este mult mai rapidă decât descărcarea lor.

Notă: dacă folosiți NSCache pentru memoria cache, această clasă va elibera toate resursele referențiate, în caz de memory warning. Pentru mai multe detalii despre NSCache, accesați http://nshipster.com/nscache/ .

  • stocheze imaginea decomprimată pe flash drive și în memorie pentru a evita repetarea procesului de decomprimare.
  • folosească GCD și block-uri. Acestea fac codul mai performant, mai ușor de citit și de scris. În prezent, GCD și block-urile sunt indispensabile pentru operațiile asincrone.
  • bonus 1: categoria peste UllmageView pentru a face integrarea ușoară.
  • bonus 2: capacitatea de a procesa imaginile după descărcare și înainte de a le stoca în cache.

Folosirea avansată a imaginilor pe iOS

Pentru a afla mai multe despre folosirea de imagini pe iOS, despre cum funcționează framework-urile din cadrul iOS SDK (CoreGraphics, Image IO, CoreAnimation, CoreImage), CPU vs. GPU și altele, citiți acest articol bun, scris de @rsebbe.

Este Core Data un candidat valid?

Aici aveți un benchmark pentru caching de imagini folosind Core Data versus File System. Rezultatele recomandă File System (după cum ne așteptam).

5. Benchmarks

Doar uitându-ne la conceptele de mai sus înțelegem că e destul de greu să scrii o asemenea componentă de unul singur; mai mult, va lua mult timp și va fi extrem de dificil. Tocmai de aceea apelăm la soluții open source. Majoritatea dintre voi ați auzit de SDWebImage sau FastImageCache. Pentru a decide care vi se potrivește mai bine, le-am comparat și am analizat felul în care răspund cerințelor noastre.

Bibliotecile testate

Notă: AFNetworking a fost adăugat comparației pentru că, începând cu iOS7, oferă și un cache de tip flash drive (NSURLCache).

Scenariul

Pentru fiecare bibliotecă am realizat o instalare de la zero a aplicației de benchmark, apoi am pornit aplicația, scroll încet, până ce imaginile s-au încărcat, apoi scroll repede / încet alternativ. Am închis apoi aplicația pentru a forța încărcarea de pe flash drive (când aceasta era disponibilă), apoi am rulat din nou același scenariu de scroll.

Aplicația benchmark - proiect

Sursa proiectului demo poate fi găsită pe Github sub denumirea de ImageCachingBenchmark, împreună cu diagramele, tabelele cu date și altele.

Sursele proiectului de pe Github au trebuit modificate, la fel ca cele ale bibliotecilor, adăugându-se tipul cache-ului din care s-a făcut încărcarea. Nedorind să includ în repository și sursele Cocoapods (acest lucru nu este recomandat) și pentru că proiectul trebuia să fie compilabil după instalarea clean a Cocoapods, varianta de proiect din Github este ușor diferită de cea cu care s-au efectuat măsurătorile.

Dacă unii dintre voi doresc să ruleze din nou benchmark-urile, va trebui să creați un completionBlock pentru încărcarea imaginilor pentru toate bibliotecile, similar celui default din SDWebImage, care returnează tipul SDImage CacheType.

Rezultatele

Rezultatele complete pot fi găsite pe pagina proiectului Github. Deoarece acele tabele sunt foarte mari, am decis să creez diagrame folosind date de la cel mai rapid (iPhone 5s) și cel mai lent device (iPhone 4).

Rezultate pentru iPhone 5s

Notă: Prin disk înțelegem flash drive (dispozitivul de stocare internă).

Rezultate pentru iPhone 4

Legendă

  • async download = biblioteca oferă suport pentru descărcare asincronă.
  • backgr decompr = decomprimarea imaginilor se face pe un background queue/thread.
  • store decompr = imaginile sunt stocate in varianta decomprimată.
  • memory/flash drive cache = suport pentru cache pe flash drive sau în memorie.
  • UIImageView categ = biblioteca include o categorie peste UIImageView.
  • from memory/flash drive = au obținut cele mai bune rezultate la timpii de încărcare din cache (memorie sau flash drive).

6. Concluzii

Elaborarea unei componente de caching de imagini pentru iOS este dificilă.

SDWebImage și AFNetworking sunt proiecte serioase, cu mulți contribuitori și care sunt întreținute în mod corect. FastImageCache se îndreaptă spre același statut destul de repede. Dacă analizăm informațiile oferite mai sus, cred că putem fi de accord că SDWebImage este cea mai bună soluție la momentul actual, chiar dacă pentru unele proiecte AFNetworking sau FastImageCache s-ar potrivi mai bine. Totul depinde de cerințele proiectului în cauză.

Link-uri utile

https://github.com/rs/SDWebImage

https://github.com/path/FastImageCache

https://github.com/AFNetworking/AFNetworking

https://github.com/tumblr/TMCache

https://github.com/hpique/Haneke

http://bpoplauschi.wordpress.com/2014/03/21/ios-image-caching-sdwebimage-vs-fastimage/

https://github.com/bpoplauschi/ImageCachingBenchmark

LANSAREA NUMĂRULUI 87

Prezentări articole și
Panel: Project management

Joi, 19 Septembrie, ora 18:00
Hugo (The Office), Cluj-Napoca

Înregistrează-te

Facebook Meetup

Conferință

Sponsori

  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • UIPatj
  • MetroSystems
  • Globant
  • Colors in projects