Подтвердить что ты не робот

Использовать xcassets без imageNamed для предотвращения проблем с памятью?

в соответствии с документацией на яблоко рекомендуется использовать xcassets для приложений iOS7 и ссылаться на эти изображения поверх imageNamed.

Но насколько мне известно, всегда были проблемы с imageNamed и памятью.

Итак, я сделал короткое тестовое приложение - обратившись к изображениям из каталога xcassets с помощью imageNamed и начал профайлер... результат был таким, как ожидалось. Когда выделенная память не была снова выпущена, даже после того, как я удалил ImageView из супервизора и установил его на нуль.

В настоящее время я работаю над iPad-приложением со многими большими изображениями, и это странное поведение imageView приводит к предупреждениям о памяти.

Но в моих тестах я не смог получить доступ к изображениям xcassets над imageWithContentsOfFile.

Итак, каков наилучший подход к работе с большими изображениями на iOS7? Есть ли способ получить доступ к изображениям из каталога xcassets в другом (более эффективном) способе? Или я не должен использовать xcassets вообще, чтобы я мог работать с imageWithContentsOfFile?

Спасибо за ваши ответы!

4b9b3361

Ответ 1

ОБНОВЛЕНИЕ: Выселение кеша работает штрафы (по крайней мере, начиная с iOS 8.3).

Я решил пойти с "новыми Images.xcassets" из Apple. Все стало плохо, когда у меня было около 350 мб изображений в приложении и приложении, которые постоянно разбивались (на iPad Retina, вероятно, из-за размера загруженных изображений).

Я написал очень простое тестовое приложение, в котором загружаю изображения в трех разных типах (смотрю профайлер):

  • imageNamed: загружается из актива: изображения никогда не освобождаются и приложение падает (для меня я мог загружать 400 изображений, но это действительно зависит от размера изображения)

  • imageNamed: (обычно включается в проект): использование памяти высок и время от времени ( > 400 изображений) Я вижу вызов didReceiveMemoryWarning:, но приложение работает нормально.

  • imageWithContentsOfFile([[NSBundle mainBundle] pathForResource:...): использование памяти очень низкое (< 20mb), потому что изображения загружаются только один раз за раз.

Я действительно не стал бы обвинять кэширование метода imageNamed: для всего, поскольку кэширование - хорошая идея, если вы должны показывать свои изображения снова и снова, но грустно, что Apple не реализовала его для активов (или не документировал, что он не реализован). В моем случае использования я перейду к кэшированию imageWithData, потому что пользователь не увидит изображения снова.

Поскольку мое приложение почти окончательно, и мне очень нравится использование механизма загрузки, чтобы найти правильное изображение автоматически, я решил обернуть использование:

  • Я удалил images.xcasset с этапа-цели-копии и добавил все изображения "снова" в проект и фазу копирования (просто добавьте папку верхнего уровня в Images.xcassets напрямую и убедитесь, что отмечен флажок "Добавить в цель xxx" и "Создать группы для любых добавленных папок" (я не беспокоился о бесполезных файлах Contents.json).
  • Во время первой сборки проверьте новые предупреждения, если несколько изображений имеют одно и то же имя (и последовательно переименовывают их).
  • Для значков приложений и изображений запуска установите "Не используйте каталог активов" в общей цели проекта и укажите там их вручную.
  • Я написал оболочку script, чтобы сгенерировать json-модель из всех файлов Contents.json(чтобы информация использовалась как Apples в коде доступа к ресурсам)

Script:

cd projectFolderWithImageAsset
echo "{\"assets\": [" > a.json
find Images.xcassets/ -name \*.json | while read jsonfile; do
  tmppath=${jsonfile%.imageset/*}
  assetname=${tmppath##*/}
  echo "{\"assetname\":\"${assetname}\",\"content\":" >> a.json
  cat $jsonfile >> a.json; 
  echo '},' >>a.json
done
echo ']}' >>a.json
  • Удалите последнюю "," запятую из json-вывода, так как я не стал здесь делать это вручную.
  • Я использовал следующее приложение для генерации кода доступа к json-модели: https://itunes.apple.com/de/app/json-accelerator/id511324989?mt=12 (в настоящее время бесплатно) с префиксом IMGA
  • Я написал красивую категорию, используя метод swizzling, чтобы не изменять текущий код (и, надеюсь, очень быстро удалил мой код):

(реализация не завершена для всех устройств и резервных механизмов!)

#import "UIImage+Extension.h"
#import <objc/objc-runtime.h>
#import "IMGADataModels.h"

@implementation UIImage (UIImage_Extension)


+ (void)load{
static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        Method imageNamed = class_getClassMethod(class, @selector(imageNamed:));
        Method imageNamedCustom = class_getClassMethod(class, @selector(imageNamedCustom:));
        method_exchangeImplementations(imageNamed, imageNamedCustom);
    });
}

+ (IMGABaseClass*)model {
    static NSString * const jsonFile = @"a";
    static IMGABaseClass *baseClass = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSString *fileFilePath = [[NSBundle mainBundle] pathForResource:jsonFile ofType:@"json"];
        NSData* myData = [NSData dataWithContentsOfFile:fileFilePath];
        __autoreleasing NSError* error = nil;
        id result = [NSJSONSerialization JSONObjectWithData:myData
                                                    options:kNilOptions error:&error];
        if (error != nil) {
            ErrorLog(@"Could not load file %@. The App will be totally broken!!!", jsonFile);
        } else {
            baseClass = [[IMGABaseClass alloc] initWithDictionary:result];
        }
    });
    return baseClass;
}


+ (UIImage *)imageNamedCustom:(NSString *)name{

    NSString *imageFileName = nil;
    IMGAContent *imgContent = nil;
    CGFloat scale = 2;

    for (IMGAAssets *asset in [[self model] assets]) {
        if ([name isEqualToString: [asset assetname]]) {
            imgContent = [asset content];
            break;
        }
    }
    if (!imgContent) {
        ErrorLog(@"No image named %@ found", name);
    }

    if (is4InchScreen) {
        for (IMGAImages *image in [imgContent images]) {
            if ([@"retina4" isEqualToString:[image subtype]]) {
                imageFileName = [image filename];
                break;
            }
        }
    } else {
        if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ) {
            for (IMGAImages *image in [imgContent images]) {
                if ([@"iphone" isEqualToString:[image idiom]] && ![@"retina4" isEqualToString:[image subtype]]) {
                    imageFileName = [image filename];
                    break;
                }
            }
        } else {
            if (isRetinaScreen) {
                for (IMGAImages *image in [imgContent images]) {
                    if ([@"universal" isEqualToString:[image idiom]] && [@"2x" isEqualToString:[image scale]]) {
                        imageFileName = [image filename];
                        break;
                    }
                }
            } else {
                for (IMGAImages *image in [imgContent images]) {
                    if ([@"universal" isEqualToString:[image idiom]] && [@"1x" isEqualToString:[image scale]]) {
                        imageFileName = [image filename];
                        if (nil == imageFileName) {
                            // fallback to 2x version for iPad unretina
                            for (IMGAImages *image in [imgContent images]) {
                                if ([@"universal" isEqualToString:[image idiom]] && [@"2x" isEqualToString:[image scale]]) {
                                    imageFileName = [image filename];
                                    break;
                                }
                            }
                        } else {
                            scale = 1;
                            break;
                        }
                    }
                }
            }
        }
    }

    if (!imageFileName) {
        ErrorLog(@"No image file name found for named image %@", name);
    }

    NSString *imageName = [[NSBundle mainBundle] pathForResource:imageFileName ofType:@""];
    NSData *imgData = [NSData dataWithContentsOfFile:imageName];
    if (!imgData) {
        ErrorLog(@"No image file found for named image %@", name);
    }
    UIImage *image = [UIImage imageWithData:imgData scale:scale];
    DebugVerboseLog(@"%@", imageFileName);
    return image;
}

@end