Dealing with EXC_BAD_SELECTOR runtime crashes

February 1, 2013 Christopher Larsen

One very powerful ability of ObjC is the dynamic runtime selector (SEL) that allows you to name a function target that can be called on an object. This power is also very dangerous because calling for a function that doesn’t exist for an object results in one of the most common crashes, the EXC_BAD_SELECTOR crash.

– (void)viewDidLoad
{
UIButton* buttonBroken = [[UIButton alloc] initWithFrame: CGRectMake(100, 50, 100, 44)];
[buttonBroken addTarget: self action:@selector(theWrongFunction) forControlEvents:UIControlEventTouchUpInside]; // Oh no, this is not going to go well
[self addSubview: buttonBroken];
}
– (void)theRightFunction
{
// Compiled without warning or issues, never gets called :(
}

Bad selectors are particularly frustrating because they aren’t caught by the compiler at build time, they just crash when a user stumbles across one when using your app on device. [Well, actually IBOutlets in your xib ARE checked. IF you go looking for small yellow triangles you’ll find them quietly lurking in there, waiting to crash your app.]

Sadly, 90% of selectors are known when you code them instead of using any kind of truly dynamic runtime selection, and many standard ObjC functions require them, such as control targets.

Worst case scenario: They are undetectable time bombs that can slip into your production builds very easily.

Here are SIX techniques you can use to keep them from destabilizing your code base:

______________________________________________________________________

1. Turn on the thing that detects bad selectors!

Yes there is a compiler flag, called Undeclared Selector, under the Build Settings tab that checks all selectors at build time to make sure they point to a valid selector. It’s not enabled by default which I find very disconcerting.

Mind-bogglingly easy and effective, this should be the first setting you change when you make a new project.

One caveat, it checks functions against all known selectors, regardless of class, that are known at that point in the compilation process.

Read that last bit again.

If a method exists even in another class, within scope, this compiler won’t warn you because it seems legit, or specifically ‘possible’ is good enough because it doesn’t know at this time what object it will be pointing at.

2. Test your function names using Autocomplete

Autocomplete when typing an @selector gives you the options of known selectors at that point.

Name all function your intend to use with @select to start with something like “select” … you know you’re calling one of your own.

– (void)selectTheRightFunction; // Start typing “select…” and all MY valid functions pop up as autocomplete options

3. Don’t use the standard or common function names, be unique!

@selector(cancel) @selector(update) @selector(reloadData) // Bad bad bad!

@selector(selectCancelSomething) @selector(selectUpdateSomething) // Boss!

Basically if a selector autocompletes BEFORE you make the function, it’s bad. Unique selectors can be detected by our warning flag in #1, so be original.

If you need to call something common, bounce a selector to a local call that gets checked in scope.

– (void)selectBounceUpdate
{
[self update]; // Safe! We exposed update outside a selector so this gets checked against self
}

4. Don’t use them

You can’t do much about some functions like button targets, but if you are comfortable with blocks you can swap out this commonly used bit:

[self performSelector:@selector(selectDelayedFunction) withObject:nil afterDelay:1.0];

with this equivalent which doesn’t use @selector and IS checked for undeclared selectors:

int64_t delay = 1.0;
dispatch_time_t delay_t = dispatch_time(DISPATCH_TIME_NOW, delay*NSEC_PER_SEC);
dispatch_after(delay_t, dispatch_get_main_queue(), ^{
[self selectDelayedFunction]; // This gets checked against self to make sure it’s legit
};

5. Store your SEL ahead of time

SEL selectorStoredFunctionCall = @selector(selectTheRightFunction); // Crashes immediately instead of on button tap
[buttonDieLater addTarget: self action: selectorStoredFunctionCall forControlEvents:UIControlEventTouchUpInside];

Not super useful, but great if you’re paranoid. All @selector does is return an SEL. If you can generate the SEL at load time for your classes, you can cause it to fail straight away, but at least crashing on launch makes them easier to detect.

6. Check all your selectors before you submit to the App Store

In Xcode search for @selector globally, or your custom “select” prefix as above, and click on every target to see where it jumps you to. If Xcode can’t find a target, or it’s not the class you wanted selected, you have a bad selector.

Thanks for @selecting this blog to read all the way to the end!

Cheers!

About the Author

Biography

Previous
Cucumber Step Definitions are teleportation devices, not methods
Cucumber Step Definitions are teleportation devices, not methods

Step definition hell. We’ve all heard of it. We’ve all experienced it. The question is, why? This hell is b...

Next
Scaling Real-time Apps on Cloud Foundry Using Node.js and RabbitMQ
Scaling Real-time Apps on Cloud Foundry Using Node.js and RabbitMQ

In the previous blog Scaling Real-time Apps on Cloud Foundry Using Node.js and Redis, we used Redis as a ‘s...