Descompression de image en iOS 7

El problema de la descompression de imágenes ha sido muy discutido en Stack Overflow, pero hasta esta pregunta había 0 menciones de kCGImageSourceShouldCacheImmediately , una opción introducida en iOS 7 que, en teoría, se encarga de este problema. Desde los encabezados:

Especifica si la deencoding de imágenes y el almacenamiento en caching deberían ocurrir en el momento de creación de la image.

En Objc.io # 7, Peter Steinberger sugirió este enfoque:

 + (UIImage *)decompressedImageWithData:(NSData *)data { CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)@{(id)kCGImageSourceShouldCacheImmediately: @YES}); UIImage *image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); CFRelease(source); return image; } 

Bibliotecas como AFNetworking y SDWebImage siguen realizando la descompression de imágenes con el método CGContextDrawImage . Desde SDWebImage :

 + (UIImage *)decodedImageWithImage:(UIImage *)image { if (image.images) { // Do not decode animated images return image; } CGImageRef imageRef = image.CGImage; CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize}; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); int infoMask = (bitmapInfo & kCGBitmapAlphaInfoMask); BOOL anyNonAlpha = (infoMask == kCGImageAlphaNone || infoMask == kCGImageAlphaNoneSkipFirst || infoMask == kCGImageAlphaNoneSkipLast); // CGBitmapContextCreate doesn't support kCGImageAlphaNone with RGB. // https://developer.apple.com/library/mac/#qa/qa1037/_index.html if (infoMask == kCGImageAlphaNone && CGColorSpaceGetNumberOfComponents(colorSpace) > 1) { // Unset the old alpha info. bitmapInfo &= ~kCGBitmapAlphaInfoMask; // Set noneSkipFirst. bitmapInfo |= kCGImageAlphaNoneSkipFirst; } // Some PNGs tell us they have alpha but only 3 components. Odd. else if (!anyNonAlpha && CGColorSpaceGetNumberOfComponents(colorSpace) == 3) { // Unset the old alpha info. bitmapInfo &= ~kCGBitmapAlphaInfoMask; bitmapInfo |= kCGImageAlphaPremultipliedFirst; } // It calculates the bytes-per-row based on the bitsPerComponent and width arguments. CGContextRef context = CGBitmapContextCreate(NULL, imageSize.width, imageSize.height, CGImageGetBitsPerComponent(imageRef), 0, colorSpace, bitmapInfo); CGColorSpaceRelease(colorSpace); // If failed, return undecompressed image if (!context) return image; CGContextDrawImage(context, imageRect, imageRef); CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); CGContextRelease(context); UIImage *decompressedImage = [UIImage imageWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation]; CGImageRelease(decompressedImageRef); return decompressedImage; } 

Mi pregunta es ¿debemos pasar al enfoque de kCGImageSourceShouldCacheImmediately en iOS 7?

Hay algunos problemas con la implementación, por lo que puedo decir.

  1. Este "nuevo método" requiere algún tipo de representación en el hilo principal. Puede cargar la image y configurar el caching debería marcar inmediatamente, pero esto establecerá una cierta operación en el hilo principal para procesar. Esto causará tartamudez al cargar vistas de desplazamiento y vistas de colección. Tartamudea más por mí que por el modo antiguo con las queues de envío en segundo plano.

  2. Si está utilizando sus propios búferes de memory en lugar de files, necesitará crear proveedores de datos que copien los datos, ya que parece que los proveedores de datos esperan que los búferes de memory se cuelguen. Eso suena obvio, pero las banderas en esta function te llevan a creer que puedes hacer esto:

    • llena tu propio búfer con datos JPEG comprimidos de alguna fuente
    • Cree un proveedor de datos y adjunte los datos JPEG.
    • cree la fuente de la image con el proveedor de datos con CACHE INMEDIATAMENTE establecido
    • use la fuente de la image para crear una image CG
    • elimine todos los objects intermedios y entregue su object CGImage bien descomprimido a un object UIImage listo para desplazarse

Sin embargo, no lo hace porque espera el hilo principal donde tendrá lugar la descompression. Piensa que todo está bien porque contiene references a todos estos objects intermedios que lanzó. Liberaste todos estos objects pensando que se descomprimió INMEDIATAMENTE como lo decían las banderas. Si también arrojó ese búfer de memory, y ese búfer de memory se pasó en un sentido sin copy, entonces terminará con basura. O bien, si el búfer de memory se reutilizó como en mi caso, para cargar otra image, también obtendrá basura.

De hecho, no tiene forma de saber cuándo se descomprimirá y estará list para usar esta image.

TL; DR = "Considere kCGImageSourceShouldCacheInmediatamente para indicar cuándo es conveniente para el sistema operativo"

Cuando lo haces de la "forma antigua", sabes 100% qué estará disponible y cuándo. Debido a que no se aplaza, puede evitar algunas copys. No creo que esta API esté haciendo nada mágico de todos modos, creo que es solo mantener el búfer de memory y luego hacer las cosas de la "manera antigua" bajo el capó.

Entonces básicamente no hay comida gratis aquí. Mirando el rastreo de la stack de dónde se estrelló esta cosa cuando traté de reutilizar el búfer de memory después de que pensé que todo estaba cerrado, lo veo llamando a CA :: Transacción, CA :: Capa, CA :: Render y desde allí en ImageProviderCopy … todo el path hasta JPEGParseJPEGInfo (donde se estrelló al acceder a mi búfer).

Esto significa que kCGImageSourceShouldCacheInmediatamente no hace nada excepto establecer una bandera para decirle a la image que se descomprima en el hilo principal tan pronto como sea posible después de haberla creado y no de manera INMEDIATA como piensa INMEDIATAMENTE significa (en lectura). Hubiera hecho exactamente lo mismo si entregó la image a una vista de desplazamiento para mostrar y la image fue a dibujar. Si tienes suerte, hubo algunos ciclos adicionales entre el desplazamiento y esto mejoraría las cosas, pero básicamente creo que parece mucho más esperanzador que vaya a hacer más de lo que realmente hace.

Puede ser de esa manera:

 + (UIImage *)imageFromURL:(NSURL *)url { UIImage *result = nil; if ([url isFileURL]) { CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL); if (source) { NSDictionary * attributes = @{ (id)kCGImageSourceShouldCache : @YES }; CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)attributes); if (cgImage) { result = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); } CFRelease(source); } } return result; } 

Puede probar este código a continuación:

 +(NSData *)imageData:(UIImage *)image { //1.0 == 100% return UIImageJPEGRepresentation(image, 0.7); } 

¡Aclamaciones!