Î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.
// 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;
});
}];
}
- 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.
Î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:
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/ .
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.
Aici aveți un benchmark pentru caching de imagini folosind Core Data versus File System. Rezultatele recomandă File System (după cum ne așteptam).
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.
Notă: AFNetworking a fost adăugat comparației pentru că, începând cu iOS7, oferă și un cache de tip flash drive (NSURLCache).
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.
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 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).
Notă: Prin disk înțelegem flash drive (dispozitivul de stocare internă).
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ă.
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/