2011-09-29 3 views
1

Недавно я обнаружил довольно серьезную проблему с производительностью в своем приложении, которая была из-за того, что изображение не было найдено в [UIImage imagenamed:].UIImage imageNamed extension

Мне было интересно, есть ли «drop-in» решение, чтобы каким-то образом зарегистрировались такие «ошибки»? Я начал писать расширения к классу UIImage, что-то вроде этого:

@implementation UIImage (Debug) 
#ifdef DEBUG 
+ (UIImage*) imageNamed:(NSString*) name{ 
    UIImage* img = [UIImage imageNamed:name]; 

    if(!img){ 
     NSLog(@"Error: referencing non-exiting image: %@", name); 
    } 

    return img; 
} 
#endif 
@end 

Но это вызывает бесконечный цикл, так как [UIImage imageNamed: имя], конечно, вызывает метод расширения, чтобы называться снова ...

Любые предложения?

благодаря Томас

+0

Вы получили пару приличных ответы ниже улавливать эти виды ошибок (я рекомендую swizzling реализации BTW), но я здесь, чтобы благовествовать НЕ использовать 'imageNamed:'. Он кэширует и никогда не очищает каждое загружаемое изображение, поэтому, если вы загружаете много изображений, вы быстро будете записывать память. Вы можете совать здесь на StackOverflow для альтернативных схем кэширования. – AndrewS

+0

@AndrewS: в то время как 'imageNamed:' делает кеш (что во многих случаях является увеличением производительности), он также * делает * очищает изображения из памяти, когда приложение получает предупреждение о памяти. См. Например, WWDC 2011 Session 318. В предыдущей версии iOS произошла ошибка, которая помешала этому, но она долго исправлена. – DarkDust

ответ

6

Вы должны не использовать категории, чтобы переопределить существующий метод. Это приведет к непредвиденным результатам (используемая реализация будет зависеть от порядка загрузки загрузочных двоичных изображений/модулей).

Вместо этого вы можете использовать возможности среды выполнения objc для обмена реализациями одного селектора с другим (иногда называемым методом swizzling). Но Я бы отговорил вас сделать это, если вы действительно не знаете значения. (Вызов выгружена метода, если вы хотите вызвать оригинал, чтобы избежать петли вызовов, управление использованием случая, когда метод реализован в родительском классе, но не ребенок, и многий другие тонкостей)


Но если вы хотите только отлаживать и получать предупреждения, когда UIImage не найден использовать контрольные точки символов! (Точки останова не ограничивается быть размещены на данной строке кода!)

Breakpoints являются более мощными, чем вы можете себе представить (я призываю вас, чтобы посмотреть WWDC'11 видеосеанс об «освоении отладки в Xcode «), особенно вы можете место точки останова не на определенной строке в коде, но на конкретном вызове метода (в вашем случае метод -imageNamed:) и добавить условие точки останова так будет только ударить в определенном состоянии (возвращается изображение nil?). Вы можете даже запросить точку останова, чтобы зарегистрировать некоторую информацию (или воспроизвести звук или что-то еще) и/или продолжить выполнение, а не прекратить выполнение кода.

5

То, что вы хотите сделать, это называется swizzling: нужно поменять местами два метода, чтобы ваш собственный метод теперь называют вместо и вы можете получить доступ к оригинальному методу под названием вашего метода. Кажется немного запутанным на первый, но вот как это работает:

#import <objc/runtime.h>  

@implementation UIImage(Debug) 

// Executed once on startup, before anything at UIImage is called. 
+ (void)load 
{ 
    Class c = (id)self; 

    // Use class_getInstanceMethod for "normal" methods 
    Method m1 = class_getClassMethod(c, @selector(imageNamed:)); 
    Method m2 = class_getClassMethod(c, @selector(swizzle_imageNamed:)); 

    // Swap the two methods. 
    method_exchangeImplementations(m1, m2); 
} 

- (id)swizzle_imageNamed:(NSString *)name 
{ 
    // Do your stuff here. By the time this is called, this method 
    // is actually called "imageNamed:" and the original method 
    // is now "swizzle_imageNamed:". 

    doStuff(); 
    // Call original method 
    id foo = [self swizzle_imageNamed:name]; 
    doMoreStuff(); 
    return something; 
} 

@end 
+0

Вы не должны переопределять/переопределять '+ load' в категории. Переопределение существующих методов в категории приводит к неопределенному поведению, описанному в документе Apple (поскольку используемая реализация будет зависеть от того, как Runtime загружает класс и его категории), поэтому это потенциально опасно. _ (Вместо этого вы можете, например, извлечь свой метод '+ load' в фиктивный внешний класс) _ – AliSoftware

+2

@AliSoftware: Спасибо, вы правы в' + load'. Я нашел ответ, который предлагает другое хорошее решение: [__attribute__ ((конструктор))] (http://stackoverflow.com/questions/4668887/objective-c-is-it-safe-to-overwrite-nsobject-initialize/4671741 # 4671741). – DarkDust

+0

Да, хороший, я знал о '__attribute __ ((constructor))' existance, но никогда не думал об использовании этого! Nice);) В любом случае, я предпочитаю использовать '+ load', когда я использую метод swizzling в моем коде и вообще избегаю использовать' __attribute__' напрямую (или я использую его через макросы, например NS_REQUIRES_NIL_TERMINATION или аналогичные), в основном для удобства чтения (и немного для совместимости компилятора, поскольку, возможно, некоторые атрибуты поддерживаются LLVM, но не GCC, или не будут поддерживаться в другом гипотетическом компиляторе будущего ...). _Но я должен признать, что это оригинальное решение, так или иначе;) _ – AliSoftware

Смежные вопросы