1
votes

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

2
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
@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

2 Answers

6
votes

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.

6
votes

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