Dissipando a imagem UIImageNamed: FUD

117

Editar fevereiro de 2014: Observe que esta questão data do iOS 2.0! Os requisitos e o manuseio de imagens mudaram muito desde então. Retina torna as imagens maiores e carregá-las um pouco mais complexas. Com o suporte integrado para imagens de iPad e retina, você certamente deve usar ImageNamed em seu código .

Vejo muitas pessoas dizendo que imageNamedé ruim, mas um número igual de pessoas dizendo que o desempenho é bom - especialmente ao renderizar UITableViews. Veja esta pergunta do SO, por exemplo, ou este artigo em iPhoneDeveloperTips.com

UIImagede imageNamed método usado para vazar era melhor evitá-lo, mas foi corrigido em versões recentes. Gostaria de compreender melhor o algoritmo de cache para tomar uma decisão fundamentada sobre onde posso confiar no sistema para armazenar minhas imagens em cache e onde preciso ir mais longe e fazer isso sozinho. Meu atual entendimento básico é que é um simples NSMutableDictionaryde UIImagesreferenciado pelo nome do arquivo. Ele fica maior e quando a memória se esgota fica muito menor.

Por exemplo, alguém sabe com certeza que o cache de imagem por trás imageNamed não responde didReceiveMemoryWarning? Parece improvável que a Apple não fizesse isso.

Se você tiver alguma ideia sobre o algoritmo de cache, poste aqui.

Rog
fonte
2
eu também. Parece que SO não é tão cheio de gênios quanto eu pensava.
Rog
Parece que você passou algum tempo investigando isso. Você fez alguma experiência que mostraria qualquer impacto negativo do UIImage imageNamed com a versão mais recente do cocoa-touch? Crie um UITableView e adicione muitas (muitos milhares) linhas e veja se o desempenho diminui quando você mostra imagens diferentes em cada linha. Talvez então as pessoas possam comentar sobre suas descobertas.
stefanB
Na verdade, não fiz muitas experiências. Eu uso imageNamed e, embora ainda não tenha otimizado a memória deste aplicativo, não tenho nada que possa apontar diretamente para imageNamed como um devorador de memória. Eu indiquei algumas das postagens de blog reclamando sobre imagedNamed aqui e fiz uma pergunta semelhante nos fóruns da apple que teve mais ação. Vou postar novamente um resumo e um link aqui quando tiver tempo e sentir que recebeu as respostas que receberá.
Rog

Respostas:

85

tldr: ImagedNamed está bem. Ele lida bem com a memória. Use-o e pare de se preocupar.

Editar novembro de 2012 : Observe que esta questão data do iOS 2.0! Os requisitos e o manuseio de imagens mudaram muito desde então. Retina torna as imagens maiores e carregá-las um pouco mais complexas. Com o suporte integrado para imagens de iPad e retina, você certamente deve usar ImageNamed em seu código. Agora, para o bem da posteridade:

O tópico irmão no Apple Dev Forums recebeu um tráfego melhor. Especificamente, Rincewind adicionou alguma autoridade.

Há problemas no iPhone OS 2.x em que o cache de imageNamed: não era apagado, mesmo após um aviso de memória. Ao mesmo tempo, + imageNamed: foi muito utilizado, não pelo cache, mas pela conveniência, o que provavelmente ampliou o problema mais do que deveria.

enquanto avisa que

Quanto à velocidade, existe um mal-entendido geral sobre o que está acontecendo. A maior coisa que + imageNamed: faz é decodificar os dados da imagem do arquivo de origem, o que quase sempre aumenta significativamente o tamanho dos dados (por exemplo, um arquivo PNG do tamanho de uma tela pode consumir algumas dezenas de KBs quando compactado, mas consome mais de meio MB descomprimido - largura * altura * 4). Em contraste, + imageWithContentsOfFile: descompactará a imagem sempre que os dados da imagem forem necessários. Como você pode imaginar, se precisar dos dados da imagem apenas uma vez, você não ganhou nada aqui, exceto por ter uma versão em cache da imagem perdida e provavelmente por mais tempo do que você precisa. No entanto, se você tem uma imagem grande que precisa redesenhar com frequência, existem alternativas, embora a que eu recomendaria principalmente é evitar redesenhar essa imagem grande :).

Com relação ao comportamento geral do cache, ele faz cache com base no nome do arquivo (portanto, duas instâncias de + imageNamed: com o mesmo nome devem resultar em referências aos mesmos dados em cache) e o cache crescerá dinamicamente conforme você solicita mais imagens via + imageNamed :. No iPhone OS 2.xa bug impede que o cache seja reduzido quando um aviso de memória é recebido.

e

Meu entendimento é que o + imageNamed: cache deve respeitar os avisos de memória no iPhone OS 3.0. Teste-o quando tiver oportunidade e relate os bugs se achar que não é o caso.

Então, aí está. imageNamed: não vai quebrar suas janelas ou matar seus filhos. É muito simples, mas é uma ferramenta de otimização. Infelizmente, ele é mal nomeado e não há equivalente que seja tão fácil de usar - portanto, as pessoas o abusam e ficam chateadas quando ele simplesmente faz seu trabalho

Eu adicionei uma categoria ao UIImage para corrigir isso:

// 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 também incluiu alguns códigos de exemplo para construir sua própria versão otimizada. Não consigo ver que vale a pena a manutenção, mas aqui é para ser completo.

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

A desvantagem desse código é que a imagem decodificada usa mais memória, mas a renderização é mais rápida.

Rog
fonte
Parece que no iOS11, [UIImage imageNamed:] não está removendo imagens do cache se as imagens vierem do catálogo xcasset. Isso leva a travamentos por falta de memória.
Juraj Antas
5

Na minha experiência, o cache de imagens criado por imageNamed não responde a avisos de memória. Tive dois aplicativos que eram tão enxutos quanto eu poderia chegar até o gerenciamento de mem, mas ainda estavam travando inexplicavelmente devido à falta de mem. Quando parei de usar imageNamed para carregar as imagens, os dois aplicativos se tornaram dramaticamente mais estáveis.

Admito que ambos os aplicativos carregaram imagens um tanto grandes, mas nada que fosse totalmente fora do comum. No primeiro aplicativo, eu simplesmente pulei o armazenamento em cache porque era improvável que um usuário voltasse para a mesma imagem duas vezes. No segundo, construí uma classe de cache realmente simples fazendo exatamente o que você mencionou - mantendo UIImages em um NSMutableDictionary e, em seguida, liberando seu conteúdo se recebesse um aviso de memória. Se imageNamed: fosse armazenado em cache assim, eu não deveria ter visto nenhuma atualização de desempenho. Tudo isso estava rodando no 2.2 - não sei se há alguma implicação no 3.0.

Você pode encontrar minha outra pergunta sobre esse problema no meu primeiro aplicativo aqui: StackOverflow question about UIImage cacheing

Uma outra observação - InterfaceBuilder usa imageNamed nos bastidores. Algo para ter em mente se você tiver esse problema.

Bdebeez
fonte
Eu vi exatamente o mesmo comportamento e a leitura do arquivo resolveu isso diretamente. Além disso, isso acontece quando tento carregar uma imagem muito grande - 11.456 x 3.226 px, 15 MB. Minha teoria é que o sistema fica sem memória em parte durante a operação de cache ImageNamed. E não há manuseio embutido para este caso.
Dogweather de