Renaming classes, types, methods, functions, and variables
Reorganizing code and restructuring classes
Modernizing your code
Despite our best efforts, class and data structures don't always endure. New features, requirements, operating system evolution, and improvements elsewhere in your application may cause you to revisit the design of your existing class and data architecture. You may need to reorganize the hierarchy of your classes, migrate functionality from one class to another, take advantage of new language features, or simply rename a variable so that it more accurately reflects its purpose. These kinds of changes are referred to as refactoring your code.
Making structural changes to your code using the editor and the search and replace tools can often be tedious, time consuming, and error prone. In the days before refactoring, I would often rename or relocate an instance variable by simply changing its declaration, and then hunting down and correcting all of the compilation errors that resulted — not the most productive way to spend an afternoon.
Fortunately, Xcode now provides a refactoring tool that helps you make many common structural changes to your application, quickly and succinctly. It uses the Code Sense database and an inherent knowledge of C and Objective-C syntax to intelligently rename symbols, reorganize classes, and streamline code. This chapter describes the kinds of changes the refactoring tool performs and the steps required.
Refactoring is formally defined as a change to the internal structure of your program that doesn't change its external, or functional, behavior. The reality is that any change, no matter how careful, can have unintended consequences. The section "Refactoring Pitfalls" discusses some of these potential problems, how to address them, and some best practices.
To refactor something in your project, follow these general steps:
Select the symbol or code fragment to refactor.
Choose the Edit
Select the type of refactoring to perform, provide whatever additional information is required, and choose options.
Preview the refactoring.
Commit the refactoring.
The process begins by selecting the specific symbol name or code fragment in the editor that represents what you want to refactor. Once selected, choose the Edit
The refactor tool determines which refactoring methods, called transformations, can be applied to the selected text. Select your desired transformation from the pop-up menu on the left, as shown in Figure 10-1. What transformations are available will depend entirely on the nature and context of the selection. Xcode offers to rename or create a superclass if a class name is selected. A selected block of code could be extracted or possibly modernized.
Refactoring uses the Code Sense index. This means that Code Sense must be enabled, up-to-date, and it must be able to make sense of your code. As a general rule, don't refactor code that won't compile — especially code that contains syntax errors that might prevent Code Sense from interpreting its meaning. See Chapter 7 if you need to turn Code Sense on or reindex your project.
Once the transformation is chosen, supply whatever additional information, if any, the transformation requires. For a rename transformation, you'll need to supply the symbol's new name. Other transformations might require several pieces of information.
Some transformations present options, typically used to control what additional, non-code changes are made. For example, renaming a class presents two options: one will also rename KVC references to that class found in Interface Builder documents and Core Data models. The other option will automatically rename the source files that contain the class's definition so that they continue to match the class name.
After you've prepared the transformation, start the process by clicking the Preview button. The process scans your entire project and prepares a list of proposed changes. At this point no changes have actually been made. You can preview the changes using the differences browser, as shown in Figure 10-2. It may also present a list of warnings that should be considered.
You can elect which of the changes you're going to allow the refactoring tool to make using the check boxes next to each file in the list. Uncheck a file, and Xcode will not make any of the proposed changes contained therein.
You also have the option of an added safety net, in the form of a project snapshot. Checking the Snapshot option makes a snapshot of your entire project before making any of the proposed changes. This also has the advantage of being able to selectively reverse unwanted changes on a change-by-change basis. See Chapter 11 for more about snapshots.
Once you've defined and previewed the transformation, click the Apply button to commit the changes. When finished, the Refactor window closes and returns you to your (now modified) project.
As of this writing, the refactoring tool only refactors C and Objective-C source code; C++ and Java programmers are just out of luck. The next few sections describe the specific transformations that the refactoring tool provides, along with some examples.
The rename transformation is the simplest and most flexible. It will rename any of the following:
Global, instance, parameter, and automatic variable names
Function names
Typdef, structure tag, and enum names
Class names
Method signatures
To rename a symbol, follow these steps:
Select a symbol name or at least one token of an Objective-C message name.
Choose the Edit
Select the Rename transformation.
Supply a replacement name and choose any additional option.
All of the rename transformations occur within the symbol's scope. For example, renaming an integer variable from c
to i
, as shown in Figure 10-3, only renames references that refer to the selected variable. Refactor is not confused by the unichar c
variable, even though it is nested within the block of code where int c
is declared. In this respect, refactoring is more intelligent than the Edit All in Scope command described in the "Editing Symbol Names" section of Chapter 7. Edit All in Scope only analyzes the textual context, and would have renamed both c
variables to i
.
Renaming method signatures is particularly accurate and flexible. For example, when renaming an Objective-C method name, Xcode picks up the entire method signature and asks that you supply a replacement. You can change any, or all, of the method name tokens and the refactoring tool will make the appropriate changes in the code. In the example shown in Figure 10-4, the method scheduleDate:time:room:
is being renamed to scheduleStartingAt:duration:room:
. The replacement method name must match the number and order of the original arguments.
When renaming a class, you are given the additional option of renaming any source files with the same name. By tradition, the files that define the Hue
class are named Hue.h
and Hue.m
. Renaming the Hue
class to Color
would also rename the files to Color.h
and Color.m
, and update any #include "Hue.h"
and #import "Hue.m"
statements to match.
The Rename Related KVC/Core Data Members option extends the refactoring beyond the scope of your source code. It scours NIB documents and Core Data models for any references to the class, method, or variable being remained. This option will update class inheritance, member names, outlet connections, actions, and bindings. If you have any NIB document or Core Data models that refer to your class, I highly recommend checking this option.
Reusable, modular code is the hallmark of efficient programming. Often, however, code that could be organized into its own method or function is originally embedded in a larger function — often because you don't realize its utility until later. Take the situation shown in Listing 10-1. Here, the programmer is writing a new rescheduleStartingAt:duration:
method, when she realizes that the loop she is about to write is identical to the one in scheduleStartingAt:duration:inRoom:
.
Example 10-1. Repetitious code
- (void)scheduleStartingAt:(NSDate*)date duration:(NSTimeInterval)minutes inRoom:(RoomIdentifier)roomID { start = date; duration = minutes*60.0; room = roomID; NSEnumerator *e = [participants objectEnumerator]; Person *participant; while ( (participant=[e nextObject])!=nil ) { [participant updateCalendarEvent:self]; } } - (void)rescheduleStartingAt:(NSDate*)date duration:(NSTimeInterval)minutes { start = date; duration = minutes*60.0; NSEnumerator *e = }
Ideally, the loop that sends update events to all of the meeting participants should be isolated in its own method. Then, rescheduleStartingAt:duration:
and scheduleStartingAt:duration:inRoom:
can reuse the logic. This transformation is called an extraction.
To extract a block of code, follow these steps:
Figure 10-5 illustrates our programmer exacting the enumeration loop in Listing 10-1 into its own method.
The extract transformation lifts a local fragment of code and puts it into its own Objective-C method or C function (your choice). In the example shown in Figure 10-5, our programmer is extracting the enumeration loop into a new method named -(void)updateParticipants
. The original code is replaced with a message that invokes the new method. After the extraction, the rescheduleStartingAt:duration:
can now be completed with a single [self updateParticipants];
statement.
The formatting of extracted code can be a bit erratic. After extraction, select the new method and reformat it by typing Control+I.
When extracting code, Xcode analyzes the scope of all of the variable references within the code block. Any variable that isn't in scope when the code is relocated to its own method (or function) is converted into a parameter. In the previous example, both the participants
and self
instance variables are still accessible in the new updateParticipants
method. If you choose to extract this code into a C function, or the loop refers to any automatic variables declared earlier in the method, those references will be converted into parameters, as shown in Figure 10-6.
The function notify_participants(...)
in Figure 10-6 has no object context, so the collection of participants and the receiving object were converted to parameters.
The encapsulate transformation creates getter and setter methods for class instance variables. Figure 10-7 shows the tasks
variable being encapsulated.
To encapsulate an instance variable, follow these steps:
Select the name of the instance variable.
Choose the Edit
Choose the Encapsulate transformation.
Optionally edit the getter and setter method names. Xcode generates a matched pair of KVC-compliant method names, but you can change them if you want.
The encapsulate transformation generates a getter and setter method for the instance variable and then replaces any existing references to the variable with messages (that is, ivar
with [object ivar]
and ivar=...
with [object setIvar:...]
).
As of this writing, a number of unfortunate limitations keep the encapsulate transformation from being truly useful, except in a narrow set of circumstances. For the specifics, see the "Encapsulate" portion of the "Refactoring Pitfalls" section, later in this chapter.
Because of these issues, I recommend using the encapsulate transformation only when:
You are not using Objective-C 2.0.
You are using managed memory (not garbage collection).
You have just added an instance variable and want to create getter and setter methods for it, but have not yet used the variable in any code. If you've already used the variable in your code, you will need to carefully review the memory management of the object afterwards.
Your property does not need to be thread-safe.
In any other circumstances, I'd recommend using Objective-C @property
and @synthesize
directives instead. See the "Updating a Project to Objective-C 2.0" section later in this chapter.
The create superclass transformation creates a new class definition and inserts it between an existing class and its original superclass. This is most useful when you need to create a more abstract superclass for an existing class whose functionality needs to be partitioned. To create a superclass, follow these steps:
Select the name of the existing class.
Choose the Edit
Choose the Create Superclass transformation.
Supply a new class name and choose whether the new class should be defined in the existing file or defined in a new set of source files with the same name.
Xcode changes the inheritance of the existing class so that it is now a subclass of the new class, which is a subclass of the original superclass. If you asked Xcode to generate new class files, those files are added to the project and appropriate #import
directives are inserted into the original files, as shown in Figure 10-8.
In the hypothetical project management program, you decide that you need an intermediate class between the very abstract Event class and the very specific Meeting class. The create superclass transformation quickly defines a new ScheduledMeeting class, changes the superclass of the Meeting class, and then creates and adds all the necessary project files.
After applying a create superclass transformation, or simply creating a new subclass, you may want to migrate properties or functionality from a class to its superclass or vice versa. That's what the move up and move down transformations do.
To move an instance variable or method to a superclass or one of its subclasses, follow these steps:
Select the instance variable declaration or method name.
Choose the Edit
Choose either the Move Up or Move Down transformation.
Select the desired options:
If moving an instance variable up, you may also elect to move related methods — any method that refers to the variable — to the superclass.
If moving down, the Refactor tool presents a list of the immediate subclasses of the class; choose which subclasses will get a copy of the variable or method.
Using the project management example again, the start
and duration
properties in the Meeting class need to be migrated to the new ScheduledEvent superclass, as shown in Figure 10-9.
This transformation is rarely without consequences; you must migrate the instance variables start
and duration
, along with any related methods, one at a time. This will also require that some of the methods be recoded, and you'll probably want to create an abstract scheduleStartingAt:duration:
method in the new superclass. You could do this using two transformations: extract the code that sets the schedule in scheduleStartingAt:duration:inRoom:
into its own method, then perform a move up transformation to migrate it to the ScheduledEvent superclass.
Like encapsulate, the move up and move down transformations know nothing about Objective-C 2.0 properties. If you move an instance variable up or down, you'll have to manually relocate any @property
and @synthesize
directives.
Objective-C introduces the much-appreciated fast enumeration syntax. Fast enumeration provides a succinct syntax for processing the objects in a collection that's both fast and robust. Prior to Objective-C 2.0, programmers used a variety of for
and while
loop patterns. The modernize loop transformation attempts to convert a legacy for
or while
loop into its equivalent using fast enumeration syntax.
To modernize a single loop, follow these steps:
Select an entire for
or while
statement, including the statement or block of code that it controls. Do not include any statements before or after the loop, even if they include loop initialization.
Choose the Edit
Choose the Modernize Loop transformation.
The process begins by selecting the entire for loop statement and block, as shown in Figure 10-10.
Loop modernization only transforms for
loops that meet these criteria:
The loop iterates through the contents of an NSArray.
Uses the message [
collection
objectAtIndex:
index
]
to obtain each object.
Starts with the first object in the collection.
Each object is processed in order, without skipping objects (this does not preclude exiting the loop early using a break
statement).
Each iteration processes a single object, without trying to access any other objects in the collection at the same time.
When the modernize loop transformation updates a while
loop, it replaces the traditional NSEnumerator pattern shown in Figure 10-11 with its modern equivalent.
Loop modernization only transforms while
loops that meet these criteria:
The while
loop iterates through the objects in a collection using an NSEnumerator object.
Uses the message [enumerator nextObject]
to obtain each object.
Starts with the first object in the collection.
Processes each object, in order, without skipping objects (this does not preclude exiting the loop early using a break
statement).
The second and third points mean that you can't use refactoring to modernize a loop that uses -reverseObjectEnumerator
.
An often-overlooked feature of fast enumeration is that the modern NSEnumerator class also conforms to NSFastEnumeration. The implication is that any enumerator object can be treated as the collection object in a fast enumeration statement. Consider the following code:
NSEnumerator* e = [collection reverseObjectEnumerator]; for ( id obj in e ) { ... }
The enumerator acts as a proxy for its collection. The statement loops through the objects returned by the enumerator. This is particularly useful with enumerators that return a subset of a collection or process objects in a different order, like -[NSDictionary keysSortedByValueUsingSelector:]
.
It should be noted that this trick does not improve the performance of your application; a fast enumeration using an enumerator object isn't any faster than using the enumerator object directly. In other words, you don't get the "fast" part of fast enumeration; you just get the convenience of a concise syntax.
The Edit
To update your entire project to Objective-C 2.0, follow these steps:
The modernize loops transformation is the same one described in the previous section, just applied globally to every Objective-C source file in your project.
The use properties transformation converts simple instance variables to Objective-C 2.0 properties. Is does this by
Adding a @property
directive for each instance variable.
Adding a @synthesize
directive for each instance variable that does not already have KVC-compliant getter and setter methods with the same name.
As mentioned at the beginning of the chapter, any change to an application — no matter how seemingly inconsequential — can occasionally have profound effects on its behavior. Although the refactoring logic is quite intelligent, it isn't omniscient. It makes mechanical replacements that are reasonably expected to be functionally equivalent, but may not be.
Many of these pitfalls can be avoided by refactoring your application defensively and with the assumption that any transformation could introduce a bug. Try to follow these best practices when refactoring:
Your project should build successfully and function correctly before beginning any refactoring.
Make use of the refactoring preview; diligently review each proposed change before committing the transformation. Disable any changes that aren't appropriate.
Always make a project snapshot. They are easy to discard later.
After the transformation, make sure the project still builds. Fix any compilation errors that appear.
If you have any unit tests defined, run them.
Each transformation carries some specific pitfalls. The next few sections highlight common issues that you're likely to encounter when refactoring.
Renaming a method only renames the occurrences of that method defined by a single class. If that method overrides an inherited method, the inherited method in the superclass is not renamed. If you need to rename an inherited method, you must rename the same method in all other classes.
Conversely, renaming a method could change it from a unique method to one that now overrides a method in its superclass.
Encapsulate only generates getter and setter methods. It does not produce @property
or @synthesize
directives. If you're using Objective-C 2.0, just write @property
or @synthesize
directives instead of trying to use encapsulate.
The getter and setter methods assume that you are using traditional managed memory, not garbage collection. Though this will work in a garbage collection environment, it still generates more code than is optimal. Again, @property
or @synthesize
directives are a better solution. The @synthesize
directive automatically adapts to your chosen memory management model.
Existing memory management is overlooked, which can create over-retain and over-release issues. For example, encapsulating the ivar
variable will replace the two statements [ivar release]; ivar=nil;
with the statements [[self ivar] release]; [self setIvar:nil];
. This results in ivar
being released twice: once in the code and again in the setter.
The visibility of the original instance variable remains unchanged. If it were @public
before the transformation, it's still public afterwards. After the transformation, change the visibility of the instance variable to @protected
or @private
.
The setter method generated is not thread-safe.
Moving an instance variable to its superclass does not migrate its @property
or @synthesize
directives.
The visibility of a variable is not retained. A @private
variable could become @protected
or @public
.
If you migrate related methods, you could migrate methods that now refer to properties defined only in the subclass. This requires either migrating those other properties as well, or redesigning the methods.
The refactoring tool warns you if the superclass already has a variable or method with the same name, but it won't prevent you from applying the transformation. The result will be a class with duplicate methods.
You can only migrate a variable or method to its immediate superclass.
Moving an instance variable to one of its subclasses does not migrate its @property
or @synthesize
directives.
The visibility of a variable is not retained. A @private
variable could become @protected
or @public
.
The refactoring tool warns you if any of the subclasses already have a variable or method with the same name, but it won't prevent you from applying the transformation. The result will be classes with duplicate methods.
You can only migrate a variable or method to one or more of its immediate subclasses.
The modernize loop transformation is generally safe, because it simply refuses to be applied unless the control loop meets its rather strict prerequisites. It is still possible, however, to trick the refactoring tool into transforming a loop that contains abnormal behavior hidden in preprocessor macros.
The use properties transformation may produce @property
directives whose attributes (that is, assign, retain, copy, nonatomic
) do not agree with the implementation of the existing getter or setter methods. Review each new @property
directive and confirm that your getter and setter fulfill the property contract, correcting one or the other as appropriate.
The transformation will not create @synthesize
directives for properties that already have KVC getter and setter methods, even if the getter and setter methods use generic patterns. You may want to delete simplistic getter and setter methods before converting your project and let the transformation insert the more modern @synthesize
directives.
Unlike the encapsulate transformation, use properties will not replace direct references to instance variables (ivar=...
) with property "dot" syntax (self.ivar=...
) or getter and setter messages ([self setIvar:...]
). You will need to search your code for direct instance variable references and determine which should be replaced with property accessors.
No attempt is made to reduce the visibility of the existing instance variables. After the transformation, consider making the instance variables @private
. This will also help identify direct references to the variable outside the class.
Changing the structure of your application isn't always as simple as moving a definition or performing a global search and replace. The refactoring tool makes short work of many common code changes, quickly and as intelligently as possible. This doesn't relieve you of the need to confirm the veracity of those changes, but it does relieve you of much of the tedium involved in making them.
Before every transformation, the refactoring tool gives you the option of making a snapshot of your project. The next chapter explains what snapshots are and how to use them in other circumstances.