Chapter 7
In This Chapter
Creating a simple software development process for your project
Identifying use cases and scenarios and creating an object-oriented design for an app
Transferring a design to and implementing it on the iOS framework
Prior chapters in this book focus mostly on explaining OO design, except for a little hands-on work in Chapters 3, 5, and 6 (on Objective-C, Xcode, and an introduction to the iOS Framework, respectively). In this chapter you will see how to take the idea behind your app and design and implement it using OO techniques. That is, you find out how to design an iOS application from scratch.
Before starting on the development of your app, I strongly recommend that you think about and design your development process (also known as your software development lifecycle, or SDLC). In other words, you need to decide how you'll develop the app (rather than what you will develop). First, you must decide how predictive and structured or how flexible and agile the SDLC will be. As I explain in Chapter 2, this choice depends upon the following:
To answer these questions, you must first understand what you're building (you’re building a Tic-Tac-Toe game), why you're building it (to learn how to build apps using iOS), and with whom you're building it (you’re building it with me). Here are some responses to the preceding questions:
Some of these responses suggest an agile process. However, most of them support use of a systematic, structured process. So that’s the process you'll use in this chapter to understand iOS app development — using Tic-Tac-Toe as the example. Specifically, you'll do the following:
You begin with an OO design without considering the implementation platform — iOS. Doing so makes the design process easier and results in a good design from an OO perspective.
Once you have the initial design, you map the design onto the iOS platform. Finally, you implement the design — that is, you write the code for the app.
Your first step is to identify what the app must do. In techie-speak this is known as capturing the app’s functional requirements. Functional requirements are typically captured as use cases. A use case is a description of a specific interaction between an actor (an entity external to the system, such as a user or another system) and the system being designed.
Developing use cases begins with creating a written description of the app. Here is a simple description of Tic-Tac-Toe:
Tic-tac-toe, also spelled tick-tack-toe, or noughts and crosses, as it's known in Britain and some of the Commonwealth countries, is a pencil-and-paper game for two players. These two players take turns marking the empty spaces in a three-by-three grid, one player using the symbols X and the other player using the symbol O. The X player usually goes first. The player who succeeds in placing three of his marks to fill a horizontal, vertical, or diagonal row wins the game.
Now, here's a narrative that extends the preceding description about playing Tic-Tac-Toe on an iOS device.
Tic-Tac-Toe for iOS implements the Tic-Tac-Toe paper game as an iOS app. Users can play the game against the computer. Multiple games can be played in each session, with either the computer playing first or the user playing first on an electronic board shown on the device’s touchscreen. Scores for each session are accumulated. If the user quits the session, scores are reset.
The next step in developing use cases is to identify the actors and write down their interactions with the app. In Tic-Tac-Toe, there are either two actors who are the players of the game or one human actor who is playing against a computer (which is considered a system actor). The interactions of the actors with the game are as follows:
Here's the general format of a use case (once again, for a specific interaction with the system):
Consequently, the use cases for Tic-Tac-Toe are as follows:
Actor: Player.
Assumptions: The app is running. One of the players is presented with a user interface option to start a new session.
Description: This use case describes the interactions that take place when a player creates a new Tic-Tac-Toe session. Starting a session automatically starts a new game.
Outcomes: Scores are set to 0 for both players. A new game starts, and the players see a blank three by three grid to play on.
Actor: Player (could be Player 1 or Player 2).
Assumptions: A game session and a game are active (see Use Case 1).
Description: Player 1 makes the first move. His move consists of placing an X in an empty square on the game grid. Player 2 goes next by placing an O in an empty square. The players then alternate until the game ends.
Outcomes: After each move, an X or an O appears in the appropriate square on the grid.
Actor: Player (could be Player 1 or Player 2).
Assumptions: A game session and a game are active. A player (Player 1 or Player 2) just made a move (see Use Case 2).
Description: If the move results in every cell in a row, column, or diagonal being filled with the same symbol (either an X or an O), the game ends. The last player to make a move wins, and his tally is incremented accordingly. If the move doesn't result in a win but all the cells are filled leaving the next player with no place to play, the game ends in a draw.
Outcomes: The players are notified of the outcome and the updated scores and are asked whether they want to continue the session by playing another game.
Actor: Player (could be Player 1 or Player 2).
Assumptions: A game session is active. A game has just ended (see Use Case 3), and the players are asked whether they want to continue the session by playing another game.
Description: A player elects to continue the session by starting a new game.
Outcomes: A new game begins within the same session.
Actor: Player (could be Player 1 or Player 2).
Assumptions: A game session is active. A game has just ended (see Use Case 3), and the players are asked whether they want to continue the session by playing another game.
Description: A player elects not to play another game, so the session terminates.
Outcomes: The session terminates. Scores are reset. The user is presented with an option to start a new session (see Use Case 1).
I used a simple notation to demonstrate use cases, but there are many formal formats for documenting use cases. You can find one to suit your taste by searching the web. Just enter the keywords Use case template in your favorite search engine. You can also find some resources in Chapter 15.
Now that the use cases have been defined, you're ready to start sketching the user interface for the app. You can use drawing software, if you really want to, but I've found that using pencil and paper is just fine. You can even use a whiteboard or blackboard and then take a picture of the board.
Figure 7-1 shows a sketch of the app's startup screen welcoming you to Tic-Tac-Toe and inviting you to start a new game.
Figure 7-2 shows the user interface when the game is underway. Note that both players have made a few moves and it's Player 1's turn to play.
Figure 7-3 shows what happens when a game ends and the players are asked whether they want to play a new game in the same session.
The screen flow for the game is shown in Figure 7-4 (which is built from Figures 7-1, 7-2, and 7-3). Note that the screen flow is from Figure 7-1 to Figure 7-2 and then to Figure 7-3.
Your next step in designing your app in an object-oriented way is to extract software elements from the text descriptions of the app. That is, you need to extract objects, classes, responsibilities of these classes, and collaborations among these classes, from the app's description and use cases. In the following three sections, I show you how to extract these elements from the description of Tic-Tac-Toe, along with the use cases and sketches from the previous section.
These extracted classes, responsibilities, and collaborations make up your initial design of the app. Once you have this initial design, you can start translating it into code, as I describe in the section, “Implementing an Object-Oriented Design on iOS,” later in this chapter.
Start by extracting nouns from the description of the app and its use cases. These become potential objects, classes, and attributes of your app. Next, you extract verbs from the description and use cases. These become candidate responsibilities (potential methods of classes).
The following list shows how I identified, defined, and extracted nouns for the Tic-Tac-Toe app; then I do the same for verbs.
Next, write down a one-to-two line definition of each noun in the context of the app that you’re trying to build. Then compare these definitions. If you find that two nouns are defined in the same way, remove one of them. You might also decide to merge two definitions (and therefore the corresponding nouns) into one. When you complete this process of definition, removal, and merging, you're left with a set of nouns that will serve as your candidate classes. Following is an example of this process from Tic-Tac-Toe (using a subset of the nouns and verbs, to avoid taxing your patience):
You now have the following potential classes, instances, and responsibilities:
Now it's time to tentatively assign responsibilities to classes as logically as possible:
It's time to take a walk through several use cases to ensure that each use case is supported by a class and its methods (more accurately, an instance of a class and its methods). As part of this process, you also find out about the collaborations between classes. Because the Tic-Tac-Toe app isn't complicated, this exercise is simple and somewhat generalized.
For Tic-Tac-Toe, you can simply create a high-level scenario by combining multiple use cases; however, when you build a complex app, be sure to have detailed scenarios, each exploring a new set of interactions with the app. Here’s your generalized scenario:
Now go through each step to see which class handles it.
Ouch. You run into trouble in the first step! There's no class to support the responsibility of starting a new game session. It looks like this responsibility can belong in the Game, and certainly not in the Board class, so you need to create a new Game Manager class to create a new game session.
Game Manager and Game Session are therefore collaborators — because Game Manager creates an instance of Game Session.
In the following, Steps 2 and 3 describe what happens when a symbol is placed on a board:
You now have to evaluate whether the game ended with a win or a draw. This looks like a Game responsibility and that Game will collaborate with Game Grid to see whether a row, column, or diagonal is complete. Game needs a responsibility, so checkResult and Game Grid will have the responsibilities isRowFilled, isColumn Filled, isLeftToRightDiagonalFilled, and isRightToLeftDiagonalFilled. Also, note that the classes Board, GameGrid, and Game are now collaborators.
At first blush, it looks as though the Game class should be responsible for accumulating scores, but it represents only a single game, and scores are accumulated across games in a session. So Game isn’t suitable, so you try Game Session:
The updated scores must be shown somewhere. What about showing them using the Board class? That doesn’t work because Board shows the Tic-Tac-Toe playing surface — that is, the board. However, you can create a Game View class and add a Show Scores method to it. Game Session will use Game View to show updated scores.
Moreover, Game View can manage the entire visual aspect of the game. So, let it display the score and the players' alternating access to the Board.
Now you have the following classes, responsibilities, and collaborators:
Finally, for each class, run through this checklist for a proper class:
All the Tic-Tac-Toe classes just identified meet these criteria.
You're making good progress. You have classes and methods, and you know which classes collaborate in the game. The next step is to clearly specify (or at least understand) what each method is supposed to do, and what it needs in order to do so. In this section, you go class by class, method by method, starting with Game (although the order in which you address each class doesn't matter).
The play method can return one of two types of values. It could return an error code indicating whether the move was legal or illegal. Or it could return the state of the game after the move — that is, Win, Draw, or Active. Just assume that play will return a Boolean, either true for success or false for failure (for example, if someone tried to play a square that was already filled). You also create three additional methods that will indicate whether the game ended with a win or a draw, or if it's still active.
Going systematically through all the classes gives you this set of methods and signatures (I’ve used a notation similar to that of Objective-C):
So, there they are — classes and methods with method signatures. You ask, “Am I done with designing? Can I start implementing?” My answer is, unfortunately, not yet, as I explain next.
If you were the Paladin of computing (Have Compiler, Will Travel) developing an application from scratch, you might be able to start writing code. But, sad to say, you aren't. Instead, it's time to put on the mantle of a modern-day "Knight in iOS Armor" and fit your design within the iOS framework. Namely, you need to take advantage of the framework where possible while also compromising where the framework doesn’t quite fit your design (or at least not without lots of extra work). In the next section, you see an example of how to convert your initial OO design to one that fits into the iOS Framework.
If you've been following along, you now have a good OO design; however, it isn’t a design that will work on iOS. In this section, you find out how to transform your initial OO design into one that works with iOS.
First, in Xcode, open the Tic-Tac-Toe project associated with this chapter so that you can follow along as I explain how to implement it (refer to Chapter 5 for instructions on loading an existing project).
The core pattern within any iOS app is the Model-View Controller-View (as I explain in Chapters 4 and 6, this is Apple’s take on the tried-and-true Model-View-Controller, or MVC, pattern). In this pattern, views are the user-interface elements of the application. Models represent the domain logic of the application, and view controllers stitch the two together.
Because the model will likely be the most stable part of a program, implementing at least its core parts early will speed up the rest of the process.
As I explain in Chapters 4 and 6, when you create the model, you do so from the requirements or other descriptions of the app. You then validate the model by walking through it using scenarios that describe how the app is intended to be used.
Now that you know what a model is, it's time to create one. To identify the classes in the model, look at the list of classes you identified during the OO design process (as discussed in the earlier section, “Illustrating Object-Oriented Design”) and identify the subset of classes that are core to the app. Incidentally, model classes won't have display or user-interface elements. The core elements of Tic-Tac-Toe are Game, Grid, and Symbol. These taken together implement the domain logic of Tic-Tac-Toe, and so make up the model.
Take a look at how each of the model classes appears when implemented, starting with the largest and most important class in the model — the Game
class (called TTTGame
in the app, per the iOS convention of keeping class names unique in the absence of namespaces).
Here is the interface file of the Game class (TTTGame.h
):
//
//
...
#import <Foundation/Foundation.h>
#import "TTTSymbol.h"
#import "TTTGameGrid.h"
typedef enum {
Inactive, Active, Won, Draw
} STATE;
typedef enum {Player1, Player2} PLAYER;
@interface TTTGame : NSObject{
@private STATE gameState;
@private TTTSymbol *currentSymbol;
@private PLAYER currentPlayer;
@private PLAYER winningPlayer;
@private NSString *PlayerOneName, *PlayerTwoName;
@private TTTGameGrid *gameGrid;
@private int playCount;
}
-(id) init;
- (TTTGameGrid *) getGameGrid;
- (void) setPlayerNames :(NSString *) FirstPlayer
:(NSString *) SecondPlayer;
- (NSString *) getPlayerOneName;
- (NSString *) getPlayerTwoName;
- (NSString *) getCurrentPlayerName;
- (NSString *) getWinningPlayerName;
- (TTTSymbol *) getCurrentSymbol;
- (void) checkResultAndSetState;
- (BOOL) play :(int) x :(int) y;
- (BOOL) isActive;
- (BOOL) isWon;
- (BOOL) isDrawn;
- (int) getPlayCount;
@end
The methods in the design are almost identically reflected in the implementation. The differences between the methods in the design and the methods in the implementation are as follows:
play
method in the actual code is different from the signature in the design.init
.To find out why those differences exist, begin by determining why all the accessor methods are there. Notice that the Game
class doesn't have visual elements and display responsibilities, which is exactly right for a model class. The views, however, need to display the Tic-Tac-Toe grid and the play-by-play progress of the game. Therefore, the view controllers need to pass the symbol currently being placed, the names of the players, and the grid from the Game
class on to the views. To enable the view controllers to pass the necessary data to the views, accessor methods are provided for the model classes.
Now in the play
method in the implementation has 2 parameters (the x
and y
coordinates) while its counterpart in the had 4 parameters (grid
, symbol
and the coordinates x
and y
).
grid
isn't needed because it's a member variable of the game and doesn’t need to be passed in. symbol
is also not needed. A quick look at the following implementation of play
reveals why symbol
doesn't need to be passed as a parameter. The implementation of the Tic-Tac-Toe game always uses X for the starting symbol (for Player One) and O for Player 2’s symbol. Therefore, you can hardwire the code to always start with X and then alternate between O and X. Note that although the method becomes simpler, the class becomes a little more complex because it "secretly" uses a characteristic of Tic-Tac-Toe.
- (BOOL) play :(int) x :(int) y {
BOOL successfulPlay=false;
if ([gameGrid getSymbolAtLocation :x :y] == [TTTSymbol SymbolBlankCreate]){
successfulPlay = true;
playCount++;
[gameGrid setSymbolAtLocation :x :y :currentSymbol];
[self checkResultAndSetState];
if(gameState == Active){// if the game is still active
// Swap symbols and players
if(currentSymbol == [TTTSymbol SymbolXCreate]){
currentSymbol= [TTTSymbol SymbolOCreate];
} else {
currentSymbol= [TTTSymbol SymbolXCreate];
}
if(currentPlayer == Player1) currentPlayer = Player2;
else currentPlayer = Player1;
}
return successfulPlay;
}
You may be wondering whether having extra accessors, particularly the gameGrid
accessor, is a bad thing. And is incorporating a secret in your code poor design? If so, just hold those questions until I discuss them and other design decisions in the section, “Analyzing the OO and Design Principles Used in Tic-Tac-Toe,” later in this chapter.
The final difference is the method called init
. This method initializes the game — creates a GameGrid
, sets the state of the game to Active
, the current player to Player1
, and the current symbol to an X. Here is the code for init
:
-(id) init { //Constructor
gameGrid = [[TTTGameGrid alloc] init];
gameState = Active;
currentSymbol = [TTTSymbol SymbolXCreate];
currentPlayer = Player1;
return self;
}
It's time to move on to the Game Grid
class, called TTTGameGrid
in the implementation. Here's its interface:
//
// TTTGameGrid.h
...
#import <Foundation/Foundation.h>
#import "TTTSymbol.h"
#define GAMEGRIDSIZE 3
@interface TTTGameGrid : NSObject{
@private TTTSymbol *grid[GAMEGRIDSIZE][GAMEGRIDSIZE];
}
-(id) init;
-(void) setSymbolAtLocation: (int) x :(int) y :(TTTSymbol *) value;
-(TTTSymbol *) getSymbolAtLocation: (int) x :(int) y;
-(BOOL) isRowFilled: (int) row;
-(BOOL) isColumnFilled: (int) column;
-(BOOL) isLeftToRightDiagonalFilled;
-(BOOL) isRightToLeftDiagonalFilled;
@end
The methods of the Game Grid class methods are exactly as designed. The implementation of the class (in TTTGameGrid.m
) isn't complicated either, which is why the method implementations aren't inserted here. Just for grins, though, here’s checkResultAndSetState
, the most complex method in TTTGameGrid
:
- (void) checkResultAndSetState{
if([gameGrid isRowFilled:0]||
[gameGrid isRowFilled:1]||
[gameGrid isRowFilled:2]||
[gameGrid isColumnFilled:0]||
[gameGrid isColumnFilled:1]||
[gameGrid isColumnFilled:2]||
[gameGrid isLeftToRightDiagonalFilled]||
[gameGrid isRightToLeftDiagonalFilled]){
winningPlayer = currentPlayer;
gameState = Won;
}else if (playCount==9){
gameState = Draw;
} /* else, leave state as is */
}
checkResultAndSetState
and the other methods in GameGrid
are pretty straightforward. Although the methods aren't complex, GameGrid
clearly illustrates the concept of abstraction and information hiding. For example, you can change the implementation of GameGrid
without the classes outside it — like Game
— having to change how they use GameGrid
. (You see more on this and other OO design points in the section, “Analyzing the OO and Design Principles Used in Tic-Tac-Toe,” later in this chapter.)
Now, move on to Symbol
. Here is the interface file of the Symbol
class (named TTTSymbol
):
// TTTSymbol.h
...
#import <Foundation/Foundation.h>
#define MARKBLANK 0
#define MARKX 1
#define MARKO 2
@interface TTTSymbol : NSObject{
@private int value;
}
+(TTTSymbol*) SymbolXCreate;
+(TTTSymbol*) SymbolOCreate;
+(TTTSymbol*) SymbolBlankCreate;
-(NSString *) toString;
-(UIImage *) getBitmapForSymbol;
@end
This class encapsulates the Tic-tac-Toe symbols X and O. Note the two variations of the Singleton pattern applied in the implementation of this class.
SymbolBlankCreate
, SymbolXCreate
, and SymbolOCreate
.getBitmap
ForSymbol
.
You find more on using the Singleton pattern in the section, “Analyzing the OO and Design Principles Used in Tic-Tac-Toe,” later in this chapter.
The last class in the Tic-Tac-Toe model is Square
(named TTTSquare
). Here is its interface file:
//
...
//
#import <Foundation/Foundation.h>
@interface TTTSquare : NSObject{
@private int x;
@private int y;
}
-(id) initWithXY: (int) initX :(int) initY;
-(int) x;
-(int) y;
@end
You use this class as a data structure to represent a square within the game grid. You also use this class to represent each square that is returned when a collection of the empty squares in the grid is requested. This class implements a pattern known as Data Transfer Object, which I explain in the last section of this chapter.
Now that the model is ready, you can build the rest of the application. So read on!
One of the very convenient features Apple added to Xcode 4 is the Storyboard feature, which is a declarative (that is a non-programming) means of specifying (but not implementing) the screen flow of an app. The Storyboard feature can also be used to define most of the views of the app. A storyboard consists of a representation of the screens in an app and the transitions between them. The individual screens are called scenes, and the transitions between the screens are called segues.
For more on how to work with storyboards, scenes, and segues, check the Storyboards and Scenes link in the web resources for the chapters in this book at www.dummies.com/go/iosprogramminglinks.
As you can see from the screen flow diagram of the app (refer to Figure 7-4), the Tic-Tac-Toe app transitions between two main states:
These states also correspond to the start and end of Use Case 1. These two screens will be the basis of the two scenes in the storyboard of the app, with a segue named segueToGameSession
from Scene 1 to Scene 2. You can see all of this in Figure 7-5.
Within each scene are views. Scene 1 is the main screen of the app, and it has two buttons, Start a New Tic-Tac-Toe Session and Exit. The gameplay screen (Scene 2) shows a drawing area for the Tic-Tac-Toe board (highlighted in blue in Figure 7-6) and two text fields where the status of the game is displayed.
In Figures 7-1 and 7-2, shown earlier in this chapter, you can see that a good deal of the Tic-Tac-Toe user interface is done declaratively. Of course, you'll need more code to complete the implementation of the views and to orchestrate the flow of the user interface from scene to scene, which you read about in the next section.
So far in this section, you've read about the app's model and should have a good idea about what the app's user interface look like (that is, its views). But views and a model doth not an app make. View controllers must stitch models and views together and make the app actually work.
Following the object-oriented design principle of cohesion, I created separate view controllers for the two scenes. (By cohesion, I mean ensuring that the things a class does relate to each other and to the intent of the class. Refer to Chapter 2 for more on cohesion.)
I call these two view controllers the Game Options view controller and the Game Session view controller. Both view controllers are subclasses of UIViewController
, the base class in the iOS Framework from which all view controllers are derived. Note that they also bound the classes of these view controllers to their respective scenes through the Identity inspector in Xcode’s Utilities area (refer to Chapter 5). Also, events in these scenes are bound to actions in their respective view controllers.
You implement the Game Options view controller (class name TTTGameOptionsViewController
). The Game Options view controller is also designated the root view controller, and the iOS runtime launches it when the app starts up. Along with this view controller, the view it manages (that is, Scene 1) is also created and presented.
The primary role of the Game Options view controller is to implement the first part of “Use Case 1: Player starts a game session.” As a result, the Touch Down
event in the Start a New Tic-Tac-Toe Session
button is bound to the IBAction
annotated startNewGame
method (see TTTGameOptionsViewController.m
). Clicking Start a New Tic-Tac-Toe Session
invokes this method, as shown here:
- (IBAction)startNewGame{
[self performSegueWithIdentifier:@"segueToGameSession" sender:self];
}
startNewGame
is known as an action method because its purpose is to respond to a user action, such as touching a button (I explain action methods in Chapter 9). The method is simplicity itself. It just segues the app from Scene 1 to Scene 2, causing the Game Session view to come up, showing the Tic-Tac-Toe grid and the two status display areas. This action launches the Game Session view controller (implemented by the class TTTGameSessionViewController
) along with its view.
The Game Session view controller is the workhorse of the Tic-Tac-Toe app because it implements the remaining part of Use Case 1 and all the other use cases. Its view comprises three parts:
Figure 7-7 shows the views that the Game Session view controller manages.
The board is a custom view that's implemented by the TTTBoard
class, which is a subclass of UIView
. Here is the interface file for the TTTBoard
class:
//
//
...
#import <UIKit/UIKit.h>
#import "TTTGameSessionViewController.h"
@class TTTGameGrid;
@interface TTTBoard : UIView{
@private BOOL enabled;
@private TTTGameSessionViewController *gameSession;
@private TTTGameGrid *grid;
@private float width; // width of one block
@private float height; // will be same as width;
}
- (void)drawRect:(CGRect)rect;
- (void) setGrid: (TTTGameGrid *) aGrid;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void) setGameSession: (TTTGameSessionViewController *) aGameSession;
- (float) getWidth;
- (float) getHeight;
- (void) invalidateBlock: (int) x :(int) y;
- (BOOL) isInputEnabled;
- (void) disableInput;
- (void) enableInput;
@end
Next, I explain what the important methods in this class do, and how they work:
TTTBoard
is specialized from UIView
in that it overrides the drawRect
method from UIView
and implements its own method. This drawRect
method is the one that draws the Tic-Tac-Toe grid along with all the played symbols when the board changes (that is, after each move).
The drawRect
method is automatically called by the iOS runtime system or, more specifically, by the view manager within iOS when the view needs to be rendered.
TTTBoard
implements several of its own methods, such as accessor methods to get its height, width, and status, to set its grid and view controller and to enable and disable input.TTTBoard
overrides and implements its own touchesBegan
method in order to respond to the placement of symbols.Via the storyboard of the app, this board is wired to and can be referenced by the outlet boardView
in the Game Session view controller. The two text fields in this view are wired to (and therefore can be referenced by) the outlets turnTextField
and scoreTextField
in the Game Session view controller. These three outlets are declared in TTTGameSessionViewController
:
@property (nonatomic, retain) IBOutlet TTTBoard *boardView;
@property (nonatomic, retain) IBOutlet UITextField *scoreTextField;
@property (nonatomic, retain) IBOutlet UITextField *turnTextField;
Here's what the Game Session view controller does:
viewDidLoad
method of Game Session after all its views are successfully loaded and rendered, as shown here:
- (void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self initializeGameSession];
[self playNewGame];
}
viewDidLoad
, in turn, calls initializeGameSession
(which initializes the game session) and playNewGame
, which sets up a new game.
The game is event driven in that the Tic-Tac-Toe program sets up and displays the board and then responds to the placement of symbols. This response begins with the touchesBegan
method of TTTBoard
, as shown here:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
NSSet* touchesInView = [event touchesForView:self];
UITouch* touchInGrid = (UITouch *)[touchesInView anyObject];
CGPoint touchPoint = [touchInGrid locationInView:self];
float x = touchPoint.x-TTTBOARDLOCATIONINVIEWX;
float y = touchPoint.y-TTTBOARDLOCATIONINVIEWX;
[gameSession boardTouchedAt:x :y];
}
touchesBegan
receives the touch event, extracts the window coordinates of the touch points in the event (touchPoint.x
and touchPoint.y
), translates them to grid coordinates (x
and y
), and passes these grid coordinates to the Game Session view controller via the controller’s boardTouchedAt
method.boardTouchedAt
method translates the touch point to Tic-Tac-Toe grid coordinates and calls humanTakesATurn
, which calls methods of the Game
class (named TTTGame
) to execute the logic behind a move.
This logic is pretty straightforward. Once the move is made, methods in the Game
class check to see whether the move causes a row, column, or diagonal to be filled with the same symbol:
If the board is filled, the game is declared a draw.
If the game is won or there's a draw, the game terminates (its state is set to won or drawn, as the case may be). Game Session displays appropriate messages and asks whether another game should be started, in which case the cycle begins anew at playNewGame
.
Finally, if neither of these cases is true, the game stays active and simply waits for the next move.
I created the Tic-Tac-Toe application to illustrate object-oriented (OO) techniques and principles as well as the process of OO design and development. So I'll analyze it from that perspective. (This analysis is arranged according to the order of the OO concepts in Chapter 2.)
Tic-Tac-Toe uses many of the building blocks of OO as implemented in Objective-C. The program uses these classes: Board, Game, Game Grid, Square, and Symbol, and two view controllers — Game Options view controller and Game Session view controller (note that the exact class names have no spaces and are prefixed with TTT
). Objects of each of these classes are instantiated, and the game is implemented as collaborations among these objects, manifested as messages being sent between objects that result in invocations of their methods.
Each of the classes inherits from a base class in the iOS framework — Game, Game Grid, Square, and Symbol from NSObject
, Board
from UIView
, and the two view controllers from UIViewController
. Considering this inheritance, you should expect to see examples of polymorphic behavior in Tic-Tac-Toe, and it doesn’t disappoint. Here are two examples:
TTTBoard
) inherits from UIView
.
Board overrides the drawRect
that it inherits from UIView
. This view is rendered by the iOS framework (specifically, the iOS window manager), which recognizes this view as an instance of UIView
. However, the (custom) drawRect
method is invoked.
touchesBegan
is another method from UIView
that's overridden in Board.
Here again, the iOS window manager thinks it's dealing with an instance of a UIView
class, whereas the object is actually an instance of Board.
Using objects only through their interface enforces a certain degree of information hiding. By accessing objects only through their interface, the using objects know no more than the externally accessible methods and their signatures of the used objects. See how each class in Tic-Tac-Toe, for the most part, can be understood by its interface alone. However, Objective-C does force you to reveal the member variables of a class in an interface file, which doesn't unnecessarily need to be revealed. In general, the OO design process also facilitates proper separation of class responsibilities and enables low coupling among classes and high cohesion within each class.
One interesting example of separation of concerns within the app (but which you don’t see in the design process) is the new memory management paradigm in iOS — Automated Reference Counting (ARC). Thanks to this paradigm, an object doesn't have to worry about how its collaborating objects are dealing with shared information.
Tic-Tac-Toe also uses delegation. For example, the Start a Tic-Tac-Toe Session
button in Scene 1 is delegating handling of the Touch Down
button press event to the method startNewGame
in the Game Options view controller. The framework also uses delegation. For example, it delegates the start up of the app to an instance of an app-specific class — the app’s own Applicaton Delegate
class (named TTTAppDelegate
in the file main.m
).
//
...
//
...
#import <UIKit/UIKit.h>
#import "TTTAppDelegate.h"
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([TTTAppDelegate class]));
}
}
Design patterns are formal ways of documenting a solution to a design problem (I discuss design patterns in Chapter 4). Tic-Tac-Toe uses two patterns: Singleton and Model-View-Controller.
The Singleton pattern is used in the Symbol class. Note how I customized this pattern in the Tic-Tac-Toe application:
+(TTTSymbol*) SymbolXCreate{
@synchronized([TTTSymbol class]){
if (SymbolX == nil){
SymbolX = [[TTTSymbol alloc] init];
SymbolX->value = MARKX;
}
return SymbolX;
}
}
getBitmapForSymbol
to get the images for the X, O, and blank symbols:
- (UIImage *) getBitmapForSymbol{
@synchronized([TTTSymbol class]){
if (!bitMapsInitialized){
NSString* imagePath =
[[NSBundle mainBundle]
pathForResource:
@"Images.bundle/x" ofType:@"png"];
imageX = [[UIImage alloc]
initWithContentsOfFile:imagePath];
imagePath =
[[NSBundle mainBundle]
pathForResource:
@"Images.bundle/o" ofType:@"png"];
imageO =
[[UIImage alloc] initWithContentsOfFile:imagePath];
imagePath =
[[NSBundle mainBundle]
pathForResource:
@"Images.bundle/blank" ofType:@"png"];
imageBlank =
[[UIImage alloc] initWithContentsOfFile:imagePath];
bitMapsInitialized=true;
}
}
UIImage *imageSelected = imageBlank;
if (self == [TTTSymbol SymbolXCreate]) imageSelected = imageX;
else if (self == [TTTSymbol SymbolOCreate])
imageSelected = imageO;
return imageSelected;
}
@synchronized
annotation.Model-View-Controller (MVC), shown in Figure 7-8, is the most important pattern within iOS. It's frequently used in applications, particularly web applications.
The pattern isolates the domain logic and core objects of the application (aka the model) from the application’s user interface. In this way, these important components (that is, the model and the views) can be designed, implemented, and maintained separately. The controller is placed between the model and the user interface. It receives user actions (such as The user clicked here) and translates those commands into actions on the model and then takes the resulting model updates and notifies the user interface to update itself.
Within iOS, controllers are called view controllers.
The Tic-Tac-Toe model consists of the classes Game
, Grid
, and Symbol
. These classes encapsulate the domain logic of the game.
The two major views in Tic-Tac-Toe are represented by Scene 1 and Scene 2 in the Tic-Tac-Toe storyboard (refer to Figure 7-5). Objects that represent these views are instances of UIView
and are created behind the scenes (no pun intended) by the iOS runtime. The buttons, text fields, and Board class that represent the Tic-Tac-Toe grid are subordinate views within these main views.
Finally, note that the Game View class (named TTTGameView
and implemented in the files TTTGameView.h
and TTTGameView.m
) is also a view. It encapsulates the user-interface elements in Scene 2 (the board and the two text fields that show the scores and the game status, respectively).
Corresponding to the two scenes are two view controllers in Tic-Tac-Toe: the Game Options
view controller and the Game
Session
view controller. (Refer to the previous section where I explain how these two view controllers manage the app's logic and tie together the model objects and the views.)
The Data Transfer Object (DTO) pattern is used when several bits of information that need to be transferred from one part of the system to another part are encapsulated into an object. You see the DTO pattern in the method getEmptySquares
, defined in the Game Grid class and used to return the list of empty squares on the board. Every instance of the Square class (see TTTSquare.m
and the corresponding interface file TTTSquare.h
) is a DTO.
You use the Façade pattern to simplify the use of the functionality provided by a set of classes. A façade provides a simpler set of methods to interact with these classes. Rather than you having to learn how to use the individual classes, a program can use these classes through a façade. You see an example of a façade in the Game View class, which simplifies the interaction between the Game Session view controller and the Scene 2-based view it handles.
Other, mostly language-specific, examples of OO concepts are utilized. A simple example of abstraction and loose coupling shows up in the use of #define
and #typedef
constructs to enumerate the values allowed by a variable. You can see #typedef
constructs in the definition and State and Player types in Game
. Here's the code from TTTGame.h
that defines the State type (named STATE
) and the Player type (PLAYER
):
typedef enum {
Inactive, Active, Won, Draw
} STATE;
typedef enum {Player1, Player2} PLAYER;
The enum
construct also provides type safety, as opposed to an approach that might use integer constants to represent both player and state.
Here's an example of #define
statements used to define the size and location of the board drawing area (from TTTBoard.h
):
#define TTTBOARDLOCATIONINVIEWX 10
#define TTTBOARDLOCATIONINVIEWY 10
#define TTTBOARDSIZE 200
By using #defines, the size of the board and its location on the window are defined in one place in the file. You can modify these values simply by editing these constants, rather than by changing the values in all the places in the code where they're used.
As you begin to understand the Tic-Tac-Toe code, you’ll see tradeoffs, compromises, and even flaws in the OO design. Some of these, such as the visibility of member variables in the interface files, are a consequence of using Objective-C.
Other compromises are made by the iOS framework. For example, some actions on events are implemented by delegate objects (such as the callbacks to button clicks). However, rather than implement delegates, you can implement some events by overriding base classes and using inheritance-driven polymorphism, such as processing touch events on the Board and drawing of the board, both done by overriding methods – touchesBegan
and drawRect
, respectively.
I made other compromises; I list them here with the intention of starting a discussion about them:
setGameStatus
and showScores
)placeSymbol
)However, the Board is coupled directly to the Game Session view controller in how it responds to events — such as touches. Take a look at how the touchesBegan
method, shown here, directly invokes boardTouchedAt
on gameSession
:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
NSSet* touchesInView = [event touchesForView:self];
UITouch* touchInGrid = (UITouch *)[touchesInView anyObject];
CGPoint touchPoint = [touchInGrid locationInView:self];
float x = touchPoint.x-TTTBOARDLOCATIONINVIEWX;
float y = touchPoint.y-TTTBOARDLOCATIONINVIEWX;
[gameSession boardTouchedAt:x :y];
}
In order for Game View to be a complete façade over the game play views, Game View should take care of the event handling, as well. Would doing this make the components of the program more cohesive and easier to understand? Or would the addition of another layer of indirection make understanding the program more difficult?
The following lines from playNewGame
within the Game Session view controller illustrate this point:
- (void) playNewGame{
activeGame = [[TTTGame alloc] init];
. . .
TTTGameGrid *gameGrid = [activeGame getGameGrid];
[boardView setGrid:gameGrid];
. . .
}
Here, Board needs the Game Grid to redraw itself and Game needs the same Game Grid to play the game. You could also hold the Game Grid instance in the Game class and provide accessor methods in the Game class to access the cells. I see good and bad points for both approaches. If you like, try this alternative approach and see whether it works for you.
3.15.17.28