1

I recently discovered a pretty big performance issue in my app that was due to an image not being found in [UIImage imagenamed:].

I was wondering if there is a "drop-in" solution to have this kind of 'errors' logged in some way? I started writing an extension to the UIImage class, something like this:

@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

But this causes an endless loop since [UIImage imageNamed:name] of course causes the extension method to be called again...

Any suggestions?

thanks Thomas

thomasvsundert
  • 185
  • 1
  • 4
  • 12
  • You've got a couple decent answers below for trapping these kinds of errors (I recommend swizzling implementations btw), but I'm here to evangelize NOT using `imageNamed:`. It caches and never purges every image you load, so if you load a lot of images then you'll burn through memory very quickly. You can poke around here on StackOverflow for alternate caching schemes. – AndrewS Sep 29 '11 at 15:21
  • @AndrewS: while `imageNamed:` does cache (which is a performance gain in many cases) it also *does* purge images from memory when the app receives a memory warning. See for example WWDC 2011 Session 318. There was a bug in earlier iOS version that prevented this, but it's long fixed. – DarkDust Sep 29 '11 at 16:11

2 Answers2

6

You should never use categories to override an existing method. This will lead to unexpected results (the implementation used will depend on the order the runtime loads the binary images/modules).

Instead, you may use the possibilities of the objc runtime to exchange implementations of one selector with another (sometimes called method swizzling). But I would discourage you to do this if you don't really know the implications. (call the swapped method if you want to call the original to avoid call loops, manage the use case when the method is implemented in the parent class but not the child, and much more subtleties)


But if you only want to debug and be alerted when an UIImage is not found use symbol breakpoints ! (Breakpoints are not limited to be placed on a given line of code!)

Breakpoints are more powerful than you can imagine (I encourage you to watch the WWDC'11 video session about "mastering debugging in Xcode"), especially you can place a breakpoint not on a specific line in your code, but on a specific method call (in your case the method -imageNamed:), and add a condition to the breakpoint so it will only be hit in certain condition (returned image nil?). You can even ask the breakpoint to log some info (or play a sound, or whatever) and/or continue execution instead of stopping your code execution.

AliSoftware
  • 32,623
  • 6
  • 82
  • 77
6

What you want to do is called swizzling: you swap two methods so your own method is now called instead and you can access the original method under the name of your method. Seems a little confusing at first, but here is how it works:

#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
Cœur
  • 37,241
  • 25
  • 195
  • 267
DarkDust
  • 90,870
  • 19
  • 190
  • 224
  • You shouldn't override/reimplement `+load` in a category. Overriding existing methods in a category leads to undefined behavior as explained in Apple's doc (because the implementation used will depend on the order the Runtime loads the class and its categories) so this is potentially risky. _(Instead you may extract your `+load` method in a dummy external class for example)_ – AliSoftware Sep 29 '11 at 15:13
  • 2
    @AliSoftware: Thanks, you're right about `+load`. I found an answer that suggests another good solution: [__attribute__((constructor))](http://stackoverflow.com/questions/4668887/objective-c-is-it-safe-to-overwrite-nsobject-initialize/4671741#4671741). – DarkDust Sep 29 '11 at 16:19
  • Yep good one, I did know about the `__attribute__((constructor))` existance but never thought about using it! Nice one ;) Anyway I prefer using `+load` when I do method swizzling in my code and in general I avoid using `__attribute__` directly (or I use it thru macros, like NS_REQUIRES_NIL_TERMINATION or similar), mainly for sake of readability (and a bit for compiler compatibility, as maybe some attributes are supported by LLVM but not GCC, or won't be supported in another hypothetic future compiler...). _But I have to admit thats an original solution anyway ;)_ – AliSoftware Sep 29 '11 at 16:33
  • 1
    +load is actually ALWAYS called for categories. Unlike normal methods where the method called is undefined, +load is called on every class and category specifically so you can do setup. It is exactly the place to do things like this. – Corey Floyd Oct 12 '12 at 01:42