Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 37 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
37
Dung lượng
1,51 MB
Nội dung
CHAPTER 6: Source File Organization88 The other part of a class’s source is the implementation. The @implementation section tells the Objective- C compiler how to make the class actually work. This section contains the code that implements the methods declared in the interface. Because of the natural split in the definition of a class into interface and implementation, a class’s code is often split into two files along the same lines. One part holds the interface components: the @interface directive for the class, any public struct definitions, enum constants, #defines, extern global variables, and so on. Because of Objective- C’s C heri- tage, this stuff typically goes into a header file, which has the same name as the class with a .h at the end. For example, class Engine’s header file would be called Engine.h, and Circle’s header file would be Circle.h. All the implementation details, such as the @implementation directive for the class, defini- tions of global variables, private structs, and so on, go into a file with the same name as the class and a .m at the end (sometimes called a dot- m file). Engine.m and Circle.m would be the implementation files for those classes. NOTE If you use .mm for the file extension, you’re telling the compiler you’ve written your code in Objective- C++, which lets you use C++ and Objective- C together. Making New Files in Xcode When you build a new class, Xcode makes your life easier by automatically creating the .h and .m files for you. When you choose File ➤ New File in Xcode, you get a window like the one shown in Figure 6-1 that presents you with a list of the kinds of files that Xcode knows how to create. Select Objective- C class, and click Next. You’ll get another window asking you to fill in the name, as shown in Figure 6-2. You can see a bunch of other things in that window. There’s a checkbox you can use to have Xcode create Engine.h for you. If you had multiple projects open, you could use the Add to project pop- up menu to choose which project should get the new files. We won’t discuss the Targets section right now, except to say that complex projects can have multiple targets, each having its own configuration of source files and different build rules. CHAPTER 6: Source File Organization 89 Figure 6-1. Creating a new file in Xcode Figure 6-2. Naming the new files CHAPTER 6: Source File Organization90 Once you click the Finish button, Xcode adds the appropriate files to the project and displays the results in the project window, as shown in Figure 6-3. Figure 6-3. The new files displayed in the Xcode project window Xcode puts the new files into the selected folder in the Groups & Files pane (if you had Source selected before creating the files, the files would go into that folder). These folders (called Groups by Xcode) provide a way to organize the source files in your project. For example, you can make one group for your user interface classes and another for your data- manipulation classes to make your project easier to navigate. When you set up groups, Xcode doesn’t actually move any files or create any directories on your hard drive. The group relationship is just a lovely fantasy maintained by Xcode. If you want, you can set up a group so that it points to a particular place in the file system. Xcode will then put newly created files into that directory for you. Once you’ve created the files, you can double- click them in the list to edit them. Xcode helpfully includes some of the standard boilerplate code, stuff you’ll always need to have in these files, such as #import <Cocoa/Cocoa.h>, as well as empty @interface and @implementation sec- tions for you to fill in. NOTE So far in this book, we’ve had #import <Foundation/Foundation.h> in our programs because we’re using only that part of Cocoa. But it’s OK to use #import <Cocoa/Cocoa.h> instead. That statement brings in the Foundation framework headers for us, along with some other stuff. CHAPTER 6: Source File Organization 91 Breaking Apart the Car CarParts-Split, found in the 06.01CarParts- Split project folder, takes all the classes out of the CarParts- Split.m file and moves them into their own files. Each class lives in its own header (.h) and implementation (.m) files. Let’s see what it takes to create this project ourselves. We’ll start with two classes that inherit from NSObject: Tire and Engine. Choose New File, and then pick Objective- C Class, and enter the name Tire. Do the same with Engine. Figure 6-4 shows the four new files in the project list. Figure 6-4. Tire and Engine added to the project Now, cut Tire’s @interface from CarParts- Split.m, and paste it into Tire.h. The file should look like this: #import <Cocoa/Cocoa.h> @interface Tire : NSObject @end // Tire Next, we’ll cut the Tire @implementation from CarParts- Split.m and paste it into Tire.m. You’ll also need to add an #import "Tire.h" at the top. This is what Tire.m should look like: #import "Tire.h" CHAPTER 6: Source File Organization92 @implementation Tire - (NSString *) description { return (@"I am a tire. I last a while"); } // description @end // Tire The first #import of the file is interesting. It’s not importing the Cocoa.h or Foundation.h header files, as we’ve done before. Instead, it imports the header file for the class. This is standard procedure, and you’ll end up doing this in virtually every project you create. The compiler needs the layout of instance variables in the class so it can generate the proper code, but it doesn’t automatically know there is a header file to go along with this source file. So, we need to inform the compiler by adding the #import "Tire.h" statement. When compiling, if you encounter an error message like “Cannot find interface definition for Tire,” that usually means you forgot to #import the class’s header file. NOTE Notice that there are two different ways of doing imports: with quotation marks and with angle brackets. For example, there’s #import <Cocoa/Cocoa.h> and #import "Tire.h". The version with angle brackets is used for importing system header files. The quotes version indicates a header file that’s local to the project. If you see a header file name in angle brackets, it’s read- only for your project, because it’s owned by the system. When a header file name is in quotes, you know that you (or someone else on the project) can make changes to it. Now, do the same procedure for class Engine. Cut the Engine @interface out of CarParts- Split.m, and paste it into Engine.h. Engine.h now looks like this: #import <Cocoa/Cocoa.h> @interface Engine : NSObject @end // Engine Next, cut the @implementation from Engine, and paste it into Engine.m, which should now look like the following: #import "Engine.h" @implementation Engine - (NSString *) description { CHAPTER 6: Source File Organization 93 return (@"I am an engine. Vrooom!"); } // description @end // Engine If you try to compile the program now, CarParts- Split.m will report errors due to the missing declarations of Tire and Engine. Those are pretty easy to fix. Just add the following two lines to the top of CarParts- Split.m, just after the #import <Foundation/Foundation.h> statement: #import "Tire.h" #import "Engine.h" NOTE Remember that #import is like #include, a command that’s handled by the C preprocessor. In this case, the C preprocessor is essentially just doing cut and paste, sticking the contents of Tire.h and Engine.h into CarParts- Split.m before continuing. You can build and run CarParts- Split now, and you’ll find its behavior unchanged from the original version, which is the one that uses AllWeatherRadials and Slant6: I am a tire for rain or shine I am a tire for rain or shine I am a tire for rain or shine I am a tire for rain or shine I am a slant- 6. VROOOM! Using Cross- File Dependencies A dependency is a relationship between two entities. Issues with dependencies pop up frequently during program design and development. Dependencies can exist between two classes: for example, Slant6 depends on Engine because of their inheritance relationship. If Engine changes, such as by adding a new instance variable, Slant6 will need to be recom- piled to adapt to the change. Dependencies can exist between two or more files. CarParts- Split.m is dependent on Tire.h and Engine.h. If either of those files change, CarParts- Split.m will need to be recompiled to pick up the changes. For instance, Tire.h might have a constant called kDefaultTirePressure with a value of 30 psi. The programmer who wrote Tire.h might decide that the default tire pressure CHAPTER 6: Source File Organization94 should be changed to 40 psi in the header file. CarParts- Split.m now needs to be recompiled to use the new value of 40 rather than the old value of 30. Importing a header file sets up a strong dependency relationship between the header file and the source file that does the importing. If the header file changes, all the files dependent on that header file must be recompiled. This can lead to a cascade of changes in the files that need to be recompiled. Imagine you have a hundred .m files, all of which include the same header file—let’s call it UserInterfaceConstants.h. If you make a change to UserInteraceConstants.h, all 100 of the .m files will be rebuilt, which can take a significant amount of time, even with a cluster of souped- up, Intel- based Xserves at your disposal. The recompilation issue can get even worse, because dependencies are transitive: header files can be dependent on each other. For example, if Thing1.h imports Thing2.h, which in turn imports Thing3.h, any change to Thing3.h will cause files that import Thing1.h to be recompiled. Although compilation can take a long time, at least Xcode keeps track of all dependencies for you. Recompiling on a Need-to- Know Basis But there’s good news: Objective- C provides a way to limit the effects of dependency- caused recompilations. Dependency issues exist because the Objective- C compiler needs certain pieces of information to be able to do its work. Sometimes, the compiler needs to know everything about a class, such as its instance variable layout and which classes it ultimately inherits from. But sometimes, the compiler only needs to know the name of the class, rather than its entire definition. For example, when objects are composed (as you saw in the last chapter), the composi- tion uses pointers to objects. This works because all Objective- C objects use dynamically allocated memory. The compiler only needs to know that a particular item is a class. It then knows that the instance variable is the size of a pointer, which is always the same for the whole program. Objective-C introduces the @class keyword as a way to tell the compiler, “This thing is a class, and therefore I’m only going to refer to it via a pointer.” This calms the compiler down: it doesn’t need to know more about the class, just that it’s something referred to by a pointer. We’ll use @class while moving class Car into its own file. Go ahead and make the Car.h and Car.m files with Xcode, just as you did with Tire and Engine. Copy and paste the @interface for Car into Car.h, which now looks like this: #import <Cocoa/Cocoa.h> @interface Car : NSObject { CHAPTER 6: Source File Organization 95 Tire *tires[4]; Engine *engine; } - (void) setEngine: (Engine *) newEngine; - (Engine *) engine; - (void) setTire: (Tire *) tire atIndex: (int) index; - (Tire *) tireAtIndex: (int) index; - (void) print; @end // Car If we now try using this header file, we’ll get errors from the compiler stating that it doesn’t understand what Tire or Engine is. The message will most likely be error: parse error before "Tire" , which is compiler- speak for “I don’t understand this.” We have two choices for how to fix this error. The first is to just #import Tire.h and Engine.h, which will give the compiler oodles of information about these two classes. But there’s a better way. If you look carefully at the interface for Car, you’ll see that it only refers to Tire and Engine by pointer. This is a job for @class. Here is what Car.h looks like with the @class lines added: #import <Cocoa/Cocoa.h> @class Tire; @class Engine; @interface Car : NSObject { Tire *tires[4]; Engine *engine; } - (void) setEngine: (Engine *) newEngine; - (Engine *) engine; - (void) setTire: (Tire *) tire atIndex: (int) index; CHAPTER 6: Source File Organization96 - (Tire *) tireAtIndex: (int) index; - (void) print; @end // Car That’s enough information to tell the compiler everything it needs to know to handle the @interface for Car. NOTE @class sets up a forward reference. This is a way to tell the compiler, “Trust me; you’ll learn eventually what this class is, but for now, this is all you need to know.” @class is also useful if you have a circular dependency. That is, class A uses class B, and class B uses class A. If you try having each class #import the other, you’ll end up with compilation errors. But if you use @class B in A.h and @class A in B.h, the two classes can refer to each other happily. Making the Car Go That takes care of Car’s header file. But Car.m needs more information about Tires and Engines. The compiler has to see which classes Tire and Engine inherit from so it can do some checking to make sure the objects can respond to messages sent to them. To do this, we’ll import Tire.h and Engine.h in Car.m. We also need to cut the @implementation for Car out of CarParts- Split.m. Car.m now looks like this: #import "Car.h" #import "Tire.h" #import "Engine.h" @implementation Car - (void) setEngine: (Engine *) newEngine { engine = newEngine; } // setEngine - (Engine *) engine { return (engine); } // engine - (void) setTire: (Tire *) tire atIndex: (int) index CHAPTER 6: Source File Organization 97 { if (index < 0 || index > 3) { NSLog (@"bad index (%d) in setTire:atIndex:", index); exit (1); } tires[index] = tire; } // setTire:atIndex: - (Tire *) tireAtIndex: (int) index { if (index < 0 || index > 3) { NSLog (@"bad index (%d) in setTire:atIndex:", index); exit (1); } return (tires[index]); } // tireAtIndex: - (void) print { NSLog (@"%@", tires[0]); NSLog (@"%@", tires[1]); NSLog (@"%@", tires[2]); NSLog (@"%@", tires[3]); NSLog (@"%@", engine); } // print @end // Car You can build and run the program again and get the same output as before. Yep, we’re refactoring again (shh, don’t tell anybody). We’ve been improving the internal structure of our program while keeping its behavior the same. Importation and Inheritance We need to liberate two more classes from CarParts- Split.m: Slant6 and AllWeatherRadial. These are a little trickier to handle because they inherit from classes we’ve created: Slant6 inherits from Engine, and AllWeatherRadial inherits from Tire. Because we’re inheriting [...]... corresponding hunk of code, as in Figure 7-18 You can click in the focus ribbon to collapse chunks of code Say you’re convinced that the if statement and the for loop shown in Figure 7-18 are correct and you don’t want to look at them anymore, so you can concentrate (focus your attention, as it were) on the rest of the code in the function Click to the left of the if statement, and its body will collapse... that’s the logical first choice The colored boxes next to the name indicate what the symbol is: E for an enumerated symbol, f for a function, # for a #define, m for a method, C for a class, and so on If you don’t want to bring up the menu, you can use control-period to cycle through the options or shift-control-period to cycle backward Don’t worry if you don’t catch all the shortcuts as we go along There’s... icon opens the counterpart for this file, just like the shortcut ⌘⌥↑ The last icon on the row, the lock, lets you make a file read only If you’re just browsing a file, you can mark it as read only so you don’t accidentally introduce any changes, in case (for example) the cat jumps up and walks on the keyboard Right below the lock, at the top of the scrollbar, is the split button Click it to split the. .. arrow) ■ control-T: Transpose (swap) the characters on either side of the cursor ■ control-D: Delete the character to the right of the cursor ■ control-K: Kill (delete) the rest of the line This is handy if you want to redo the end of a line of code ■ control-L: Center the insertion point in the window This is great if you’ve lost your text cursor or want to quickly scroll the window so the insertion point... you can see that there are references to the Car class and car local variable You could uncheck the Ignore case checkbox and just change the local variables inside of main You can click Replace All to make the change globally Search and replace functionality is a blunt instrument for doing this kind of surgery, however It does too much if you’re just trying to rename a variable in a function (because... handy cheat sheet at the end of this chapter CHAPTER 7: More About Xcode Figure 7 -4 Possible completions for “all” You can use the completion menu as a quick API reference for a class Consider NSDictionary, which has a method that lets you specify a list of arguments representing the keys and objects used to build a dictionary Is it +dictionaryWithKeysAndObjects, or is it +dictionaryWith➥ ObjectsAndKeys?... information available The first two items bring up the NSString class reference documentation in the documentation window The NSString.h item brings up the header file in the editor Figure 7-26 The Research Assistant 121 122 CHAPTER 7: More About Xcode The Abstract pane describes the class If you had your cursor in the middle of a method call, the Abstract would describe the method, along with related calls... 7-27 The Xcode documentation window That’s a mighty busy window, and it packs in a lot of information In the upper-right corner is a search box, which comes prepopulated with UTF8String The ribbon of buttons underneath the toolbar lets you refine which documentation sets are searched You can look at all documentation CHAPTER 7: More About Xcode or documentation for just Mac OS X or the iPhone Or you can... search by language, so if you’re only editing JavaScript, you don’t need to wade through C+ + information Below the button ribbon is a pane on the left that contains documentation sets and a set of bookmarks You can add bookmarks to specific chunks of documentation by using the trusty ⌘D shortcut These bookmarks are distinct from the project bookmarks you might have placed in your code Documentation... the focus ribbon, and as its name implies, it allows you to focus your attention on different parts of your code Notice the shades of gray in the focus ribbon: the more deeply nested a bit of code is, the darker the gray next to it in the ribbon This color-coding gives you a hint of the complexity of your code at a glance You can hover over different gray regions of the focus bar to highlight the corresponding . CHAPTER 6: Source File Organization88 The other part of a class’s source is the implementation. The @implementation section tells the Objective- C compiler how to make the class actually. This section contains the code that implements the methods declared in the interface. Because of the natural split in the definition of a class into interface and implementation, a class’s code. Basis But there’s good news: Objective- C provides a way to limit the effects of dependency- caused recompilations. Dependency issues exist because the Objective- C compiler needs certain pieces