Recently I refactored some code and in the process performed static analysis using Xcode's Analyze tool. The guide describes the steps to use Analyze, along with some benefits of performing static analysis.
In general, you can think of static analysis as a way to execute your code and capture a log of issues (null pointers, memory overwrites and so forth). The nice part is that this is done in an automated fashion. You don't actually need to step through each part of the code. Of course, as with any such tool, there's no guarantee it will find every issue or that it is foolproof. It doesn't eliminate the need for testing (more on this later). But, it should help testing go smoother by eliminating some issues earlier in the development cycle.
Most of the code issues Analyzeuncovered were easily addressed, but the particular issue I've listed below proved to be challenging. It centered around a method (imageFromCamera) that returns an image from the camera:
You'll see that theAnalyzeoutput lists three errors. On closer examination you'll also see that the errors are interrelated - centering on object lifecycle for the variable imageRef. As you might expect I began to address these issues by, uh, starting with the first one and working my way down. For dealing with most code issues the top-down approach is often the best idea (as you'll see, in my particular case this turned out not to be optimal). To tackle the first issue I spent time getting up to speed on reference counting, retain attributes and the like, all the while tweaking my code to reflect my newest theory. Eventually I stumbled upon a magical combination thatseemed to make the issue disappear, at least as far as static analysis was concerned. But, as it turned out my changes simply moved the issue to a more problematic area. They caused the app to crash when it accessed the camera! If you take nothing else away from this posting please remember this - while static analysis and performance profiling tools provide valuable assistance with improving code quality and performance, they simply can't ensure your code is crash-free, or that it performs as you've intended. This level of quality can only be obtained through code reviews, unit/functional/system testing. Don't try to use tools to shortcut this process unlessyou're one of those people who enjoy being called at 2am to fix an obscure bug in your production code.
Now feeling deflated and disappointed (after all Analyze said my code was perfect!), I decided to regroup and examine each of the three errors again. It was at this point that I noticed the very strange verbiage of the third issue. Previously I was convinced that I needed to somehow address the declaration of my imageRef variable. But, based on this third error I decided to take a closer look at the way my method returned the object it created. At first I zeroed in on the Object leaked:portion of the error message, completely ignoring the rest -- object allocated and stored intoimageRef is returned from a method whose name does not start with copy, mutableCopy, alloc or new. I was certain that whoever penned the strange text for this particular message must've done so 5 minutes before the end of the workday. Why in the world would the name of my method matter? I mean really -- what next, is Analyze going to check my comments for readability (not a bad idea, by the way). Well, it turned out that I was wrong. It seems that the compiler and static analysis tools do follow certain method naming conventions, grouping methods into categories -- conveniently named Method Families. And Analyze enforces this method naming standard for any methods that create and return objects.
One Big, Happy (Method) Family
The clang open source C Compiler project offers the description below regarding method families. This definition is relevant because the Analyze tool follows this specification and Apple's objective C compiler implements this approach:
An Objective-C method may fall into a method family, which is a conventional set of behaviors ascribed to it by the Cocoa conventions.A method is in a certain family if:
- it has an objc_method_family attribute placing it in the family (see below for example)
- it does not have an objc_method_family attribute (therefore receiving a default family attribute)
- its selector falls into the corresponding selector family and its signature obeys the added restrictions of the method family (i.e. its name begins with a certain prefix)
For my purposes I focused on the last item in this list regarding the selector name, especially since the paragraph below this list in the documentation mentioned method names that were prefixed with alloc, mutableCopy,new and so forth. These were the same prefixes used in the error message I received from Analyze! Still, I was skeptical - surely there was more to this than simply adding new to the beginning of my method name. Regardless of my skepticism, I gave it a try and renamed my method as follows:
To my surprise all three issues were resolved! It really was that simple. It turns out the compiler uses the following family prefixes to determine how your method treats objects returned to the caller:
- alloc methods must return a retainable object pointer type.
- copy methods must return a retainable object pointer type.
- mutableCopy methods must return a retainable object pointer type.
- new methods must return a retainable object pointer type.
- init methods must be instance methods and must return an Objective-C pointer type. Additionally, a program is ill-formed if it declares or contains a call to aninitmethod whose return type is neitheridnor a pointer to a super-class or sub-class of the declaring class (if the method was declared on a class) or the static receiver type of the call (if it was declared on a protocol).
A Method By Any Other Name
If you're a purist (like me), you probably find the notion of using a method name prefix a bit arcane. I was certain there had to be some type ofofficialmethod attribute I could use instead. And of course, there was an attribute. Below is the same method reformulated to use the obc_method_family attribute. Note that you'll want to use this attribute in the method's declaration, not its implementation:
-(CGImageRef) imageFromCamera:(CMSampleBufferRef) camBuffer __attribute__((objc_method_family(alloc)));
In this example the original method name is still intact, but we've told the compiler that our method belongs to the alloc method family and therefore will return an object that we've allocated and must be explicitly freed (in my case I callCGImageReleaseto release the object). The clang document previously cited also contains further details of how to use the objc_method_family attribute.
A Method To the Madness
Since both approaches are essentially the same, you may wonder which to use. According to Apple the preferred approach is to follow the method naming convention. This, of course, has its obvious advantages as far as readability is concerned. But, in the case of refactoring, using an attribute might work better than renaming the method if the method is called in numerous places in your code. You'll need to determine the cost-benefit of each approach, preferably leaning toward readability where possible. In my case it turned out that my method was only referenced a handful of times across a few files. Because of this I choose to go with the more readable approach of renaming my method to newImageFromCamera.
So, the next time you find yourself creating/refactoring methods keep in mind that what you call a method is often just as important as where you call it. Believe it or not!