We’ve
explored the notion of classes, methods, and instance variables, but
you probably still don’t have a real idea of how to go about making a
program do something. ‘So, this section reviews several key programming
tasks that you’ll be using to implement your methods:
Declaring Variables
Earlier we documented
what instance variables in your interface file will look like, but we
didn’t really get into the process of how
you declare (or “define”) them (or use them). Instance variables are
also only a small subset of the variables you’ll use in your projects.
Instance variables store information that is available across all the
methods in your class—but they’re not really appropriate for small
temporary storage tasks, such as formatting a line of text to output to
a user. Most commonly, you’ll be declaring several variables at the
start of your methods, using them for various calculations, and then
getting rid of them when ‘you’ve finished with them.
Whatever the purpose, you’ll declare your variables using this syntax:
The type is either a primitive data type or the name of a class that you want to instantiate and use.
Primitive Data Types
Primitive data types are
defined in the C language and are used to hold very basic values.
Common types you’ll encounter include the following:
int Integers (whole numbers such as 1, 0, and -99)
float Floating-point numbers (numbers with decimal points in them)
double Highly precise floating-point numbers that can handle a large number of digits
For example, to declare an integer variable that will hold a user’s age, you might enter the following:
After a primitive data type is
declared, the variable can be used for assignments and mathematical
operations. The following code, for example, declares two variables, userAge and userAgeInDays, and then assigns a value to one and calculates the other:
int userAge;
int userAgeInDays;
userAge=30;
userAgeInDays=userAge*365;
Pretty easy, don’t you
think? Primitive data types, however, will make up only a very small
number of the variables types that you use. Most variables you declare
will be used to store objects.
Object Data Types and Pointers
Just about everything that
you’ll be working with in your iPhone applications will be an object.
Text strings, for example, will be instances of the class NSString. Buttons that you display on the screen are objects of the class UIButton.
You’ll learn about several of the common data types in the next hour’s
lesson. Apple has literally provided hundreds of different classes that
you can use to store and manipulate data.
Unfortunately for us, for
a computer to work with an object, it can’t just store it like a
primitive data type. Objects have associated instance variables and
methods, making them far more complex. To declare a variable as an
object of a specific class, we must declare the variable as a pointer
to an object. A pointer references the place in memory where the object
is stored, rather than a value. To declare a variable as a pointer,
prefix the name of the variable with an asterisk. For example, to
declare a variable of type NSString with the intention of holding a user’s name, we might type this:
Once declared, you can use the
variable without the asterisk. It is only used in the declaration to
identify the variable as a pointer to the object.
By the Way
When a variable is a pointer to an object, it is said to reference or point to the object. This is in contrast to a variable of a primitive data type, which is said to store the data.
Even after a variable has
been declared as a pointer to an object, it still isn’t ready to be
used. Xcode, at this point, only knows what object you intend the
variable to reference. Before the object actually exists, you must
manually prepare the memory it will use and perform any initial setup
required. This is handled via the processes of allocation and
initialization—which we review next.
Allocating, Initializing, and Releasing Objects
Before an object can be used,
memory must be allocated and the contents of the object initialized.
This is handled by sending an alloc message to the class that you’re going to be using, followed by an init message to what is returned by alloc. The syntax you’ll use is this:
[[<class name> alloc] init];
For example, to declare and create a new instance of UILabel class, you could use the following code:
UILabel *myLabel;
myLabel=[[UILabel alloc] init];
Once allocated and initialized, the object is ready to use.
By the Way
We haven’t covered
the method messaging syntax in Objective-C, but we’ll do so shortly.
For now, it’s just important to know the pattern for creating objects.
Convenience Methods
When we initialized the UILabel instance, we did create a usable object, but it doesn’t yet have any of the additional information that makes it useful.
Properties such as what the label should say or where it should be
shown on the screen have yet to be set. We would need to use several of
the object’s other methods to really make use of the object.
These
configuration steps are sometimes a necessary evil, but Apple’s classes
often provide a special initialization method called a convenience method. These methods can be invoked to setup an object with a basic set of properties so that it can be used almost immediately.
For example, the NSURL class, which you’ll be using later on to work with web addresses, defines a convenience method called initWithString.
To declare and initialize an NSURL object that points to the website http://www.teachyourselfiphone.com, we might type the following:
NSURL *iPhoneURL;
iPhoneURL=[[NSURL alloc] initWithString:@"http://www.teachyourselfiphone.com/"];
Without any additional work, we’ve allocated and initialized a URL with an actual web address in a single line of code.
Did You Know?
In this example, we actually created another object, too: an NSString. By typing the @
symbol followed by characters in quotes, you allocate and initialize a
string. This feature exists because strings are so commonly used that
having to allocate and initialize them each time you need one would
make development quite cumbersome.
Using Methods and Messaging
You’ve already seen the
methods used to allocate and initialize objects, but this is only a
tiny picture of the methods you’ll be using in your apps. Let’s start
by reviewing the syntax of methods and messaging.
Messaging Syntax
To send an object a message,
give the name of the variable that is referencing the object followed
by the name of the method—all within square brackets. If you’re using a
class method, just provide the name of the class rather than a variable
name:
[<object variable or class name> <method name>];
Things start to look a little more complicated when the method has parameters. A single parameter method call looks like this:
[<object variable> <method name>:<parameter value>];
Multiple parameters look even more bizarre:
[<object variable> <method name>:<parameter value> additionalParameter:<parameter value>];
An actual example of using a multiple parameter method looks like this:
[userName compare:@"John" options:NSCaseInsensitive];
Here an object userName (presumably an NSString) uses the compare:options method to compare itself to the string "John" in a non-case-sensitive manner. The result of this particular method is a Boolean value (true or false),
which could be used as part of an expression to make a decision in your
application. (We’ review expressions and decision making next!)
Did You Know?
Throughout the lessons,
methods are referred to by name. If the name includes a colon (:), this
indicates a required parameter. This is a convention that Apple has
used in their documentation and that ’has been adopted for this book.
Did You Know?
A useful predefined value in Objective-C is nil. The nil value indicates a lack of any value at all. You’ll use nil in some methods that call for a parameter that you don’t have available. A method that receives nil in place of an object can actually pass messages to nil without creating an error—nil simply returns another nil as the result.
‘This is used a few times
later in the book, and should give you a better clearer picture of why
this behavior is something we’d actually want to happen!
Nested Messaging
Something that you’ll see
when looking at Objective-C code is that the result of a method is
sometimes used directly as a parameter within another method. In some
cases, if the result of a method is an object, a developer will send a
message directly to that result.
In both of these
cases, using the results directly avoids the need to create a variable
to hold the results. Want an example that puts all of this together?
We’ve got one for you!
Assume you have two NSString variables, userFirstName and userLastName, that you want to capitalize and concatenate, storing the results in another NSStringfinalString. The NSString instance method capitalizedString returns a capitalized string, while stringByAppendingString
takes a second string as a parameter and concatenates it onto the
string invoking the message. Putting this together (disregarding the
variable declarations), the code looks like this: called
tempCapitalizedFirstName=[userFirstName capitalizedString];
tempCapitalizedSecondName=[userLastName capitalizedString];
finalString=[tempCapitalizedFirstName
stringByAppendingString:tempCapitalizedSecondName];
Instead of using these temporary variables, however, you could just substitute the method calls into a single combined line:
finalString=[[userFirstName capitalizedString] stringByAppendingString:[userLastName
capitalizedString]];
This can be a powerful way
to structure your code, but can also lead to long and rather confusing
statements. Do what makes you comfortable—both approaches are equally
valid and have the same outcome.
By the Way
A confession. I have a
difficult time referring to using a method as sending a “message to an
object.” Although this is the preferred terminology for OOP, all we’re
really doing is executing an object’s method by providing the name of
the object and the name of the method.
Blocks
Although most of your
coding will be within methods, Apple recently introduced “blocks” to
the iOS frameworks. Sometimes referred to as a handler block
in the iOS documentation, these are chunks of code that can be passed
as a value when calling a method. They provide instructions that the
method should run when reacting to a certain event.
For example, imagine a personInformation object with a method called setDisplayName that would define a format for showing a person’s name. Instead of just showing the name, however, setDisplayName might use a block to let you define, programmatically, how the name should be shown:
[personInformation setDisplayName:^(NSString firstName, NSString lastName)
{
// Implement code here to modify the first name and last name
// and display it however you want.
}];
Interesting, isn’t it?
Blocks are new to iPhone development and will very rarely be used in
the book. When you start developing motion-sensitive apps, for example,
you will pass a block to a method to describe what to do when a motion
occurs.
Where blocks are
used, we’ll walk through the process. If you’d like to learn more about
these strange and unusual creatures, read Apple’s “A Short Practical
Guide to Blocks” in the Xcode documentation.
Expressions and Decision Making
For an application to react
to user input and process information, it must be capable of making
decisions. Every decision in an app boils down to a “yes” or “no”
result based on evaluating a set of tests. These can be as simple as
comparing two values, to something as complex as checking the results
of a complicated mathematical calculation. The combination of tests
used to make a decision is called an expression.
Using Expressions
If you recall your high
school algebra, you’ll be right at home with expressions. An expression
can combine arithmetic, comparison, and logical operations.
A simple numeric comparison checking to see whether a variable userAge is greater than 30 could be written as follows:
When working with objects, we
need to use properties within the object and values returned from
methods to create expressions. To check to see whether a string stored
in an object userName is equal to ""John"”, we could use this:
[userName compare:@"John"]
Expressions aren’t
limited to the evaluation of a single condition. We could easily
combine the previous two expressions to find a user who is over 30 and
named John:
userAge>30 && [userName compare:@"John"]
() Groups expressions together, forcing evaluation of the innermost group first == Tests to see whether two values are equal (e.g., userAge==30) != Tests to see whether two values are not equal (e.g., userAge!=30) && Implements a logical AND condition (e.g., userAge>30 && userAge<40) || Implements a logical OR condition (e.g., userAge>30 || userAge<10) ! Negates the result of an expression, returning the opposite of the original result (e.g., !(userAge==30) is the same as userAge!=30)
For a complete list of C expression syntax, you may want to refer to http://www.cs.drexel.edu/~rweaver/COURSES/ISTC-2/TOPICS/expr.html.
|
As ’mentioned repeatedly,
you’re going to be spending lots of time working with complex objects
and using the methods within the objects. You can’t make direct
comparisons or between objects as you can with simple primitive data
types. To successfully create expressions for the myriad objects you’ll
be using, ’you must review each object’s methods and properties.
Making Decisions with if-then-else and switch Statements
Typically, depending on the
outcome of the evaluated expression, different code statements are
executed. The most common way of defining these different execution
paths is with an if-then-else statement:
if (<expression>) {
// do this, the expression is true.
} else {
// the expression isn't true, do this instead!
}
For example, consider the comparison we used earlier to check a userName NSString
variable to see whether its contents were set to a specific name. If we
want to react to that comparison, we might write the following:
If ([userName compare:@"John"]) {
userMessage=@"I like your name";
} else {
userMessage=@"Your name isn't John, but I still like it!";
}
Another approach to
implementing different code paths when there are potentially many
different outcomes to an expression is to use a switch statement. A switch statement checks a variable for a value, and then executes different blocks of code depending on the value that is found:
switch (<numeric value>) {
case <numeric option 1>:
// The value matches this option
break;
case <numeric option 2>:
// The value matches this option
break;
default:
// None of the options match the number.
}
Applying this to a situation where we might want to check a user’s age (stored in userAge) for some key milestones and then set an appropriate userMessage string if they are found, the result might look like this:
switch (userAge) {
case 18:
userMessage=@"Congratulations, you're an adult!";
break;
case 21:
userMessage=@"Congratulations, you can drink champagne!";
break;
case 50:
userMessage=@"You're half a century old!";
break;
default:
userMessage=@"Sorry, there's nothing special about your age.";
}
Repetition with Loops
Sometimes you’ll have a
situation where you need to repeat several instructions over and over
in your code. Instead of typing the lines repeatedly, you can loop
over them. A loop defines the start and end of several lines of code.
As long as the loop is running, the program executes the lines from top
to bottom, and then restarts again from the top. The loops you’ll use
are of two types: count based and condition based.
In a count-based loop,
the statements are repeated a certain number of times. In a
condition-based loop, an expression determines whether a loop should
occur.
The count-based loop you’ll be using is called a for loop, with this syntax:
for (<initialization>;<test condition>;<count update>) {
// Do this, over and over!
}
The three “unknowns” in the for
statement syntax are a statement to initialize a counter to track the
number of times the loop has executed, a condition to check to see
whether the loop should continue, and finally, an increment for the
counter. An example of a loop that uses the integer variable count to loop 50 times could be written as follows:
int count;
for (count=0;count<50;count=count+1) {
// Do this, 50 times!
}
The for loop starts by setting the count variable to 0. The loop then starts and continues as long as the condition of count<50 remains true. When the loop hits the bottom curly brace (}) and starts over, the increment operation is carried out and count is increased by 1.
Did You Know?
In C and C-like languages, like Objective-C, integers are usually incremented by using ++ at the end of the variable name. In other words, rather than using count=count+1, most frequently you’ll encounter count++, which does the same thing. Decrementing works the same way, but with --.
In a condition-based
loop, the loop continues either while an expression remains true. There
are two variables of this loop type that you’ll encounter, while and do-while:
while (<expression>) {
// Do this, over and over, while the expression is true!
}
and
do {
// Do this, over and over, while the expression is true!
} while (<expression>);
The only difference between these two loops is when the expression is evaluated. In a standard while loop, the check is done at the beginning of the loop. In the do-while loop, however, the expression is evaluated at the end of every loop.
For example, suppose you
are asking users to input their name and you want to keep prompting
them until they type John. You might format a do-while loop like this:
do {
// Get the user's input in this part of the loop
} while (![userName compare:@"John"]);
The assumption is that the name is stored in a string object called userName. Because you wouldn’t have requested the user’s input when the loop first starts, you would use a do-while loop to put the test condition at the end. Also, the value returned by the string compare method has to been negated with the ! operator, because you want to continue looping as long as the comparison of the userName to “John" isn’t true.
Loops are a very useful
part of programming, and, along with the decision statements, will form
the basis for structuring the code within your object methods. They
allow code to branch and extend beyond a linear flow.
Although ’an
all-encompassing picture of programming is beyond the scope of this
book, this should give you some sense of what to expect in the rest of
this book. ’Let’s now close out the hour with a topic that causes quite
a bit of confusion for beginning developers: memory management.