2011-09-29 69 views
1

我最近在我的应用程序中发现了一个非常大的性能问题,这是由于在[UIImage imagenamed:]中找不到图像。UIImage imageNamed扩展

我想知道是否有一种“插入”的解决方案,以某种方式记录这种'错误'?我开始写一个扩展的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

你有以下几个像样的答案,对捕捉这些类型的错误(我建议BTW混写的实现),但我在这里传福音不使用'imageNamed:'。它缓存并永远不会清除您加载的每个图像,因此如果您加载大量图像,那么您将很快通过内存烧录。你可以在StackOverflow上找到替代缓存方案。 – AndrewS

+0

@AndrewS:虽然'imageNamed:'缓存(这在很多情况下是一个性能增益),但它也会*在应用程序收到内存警告时清除内存中的图像。请参阅WWDC 2011 Session 318.早期的iOS版本中存在一个阻止此操作的错误,但它很长时间没有修复。 – DarkDust

回答

6

你应该从未使用类别来覆盖现有的方法。这将导致意想不到的结果(所使用的实现取决于运行时加载二进制映像/模块的顺序)

相反,您可以使用objc运行时的可能性将一个选择器的实现与另一个选择器交换(有时称为方法swizzling)。但是如果你不知道的含义,我会劝阻你这样做。 (调用交换方法,如果你想调用原来以避免调用循环,管理,当该方法在父类中实现,但使用情况不是孩子,和更多的细微之处)


如果您只想调试并在未找到UIImage时收到警报使用符号断点! (断点不限于被放置在代码一个给定的线!)

断点是更强大的比你能想象(我建议您观看关于“在Xcode掌握调试WWDC'11视频会议“),尤其是你可以地方断点没有在你的代码在一个特定的方法调用(在你的情况下该方法-imageNamed:)特定的路线,但添加条件断点所以它只会在一定的条件下被击中(返回图像无?)。你甚至可以通过请求断点记录一些信息(或者播放声音,或者其他任何东西)和/或继续执行,而不是停止代码执行。

5

你想要做什么叫做混写:你交换两种方法让你自己的方法现在被称为替代,您可以方法的名称下访问方法。似乎有点起初混淆,但在这里是如何工作的:

#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__((constructor))](http://stackoverflow.com/questions/4668887/objective-c-is-it-safe-to-overwrite-nsobject-initialize/4671741 #4671741)。 – DarkDust

+0

好的,我确实知道'__attribute __((构造函数))'的存在,但从来没有想过要使用它!不错的一个;)无论如何,当我在我的代码中调用方法时,我更喜欢使用'+ load',并且通常我避免直接​​使用'__attribute__'(或者我使用它通过宏,如NS_REQUIRES_NIL_TERMINATION或类似),主要是为了可读性和编译器兼容性有点差别,因为LLVM可能支持某些属性,但不支持GCC,或者在另一个假想的未来编译器中不支持)。 _但我不得不承认这是一个原始的解决方案;)_ – AliSoftware