Objective-C
you're comfortable with basic object-oriented concepts and the C
language, Objective-C will make a lot of sense. If you don't know C,
you should read the C Tutorial
first.
This tutorial is written and illustrated by
Scott Stevenson
Calling Methods
To get started as quickly as possible, let's look at
some simple examples. The basic syntax for calling a method on an
object is this:
];
[object methodWithInput:
input];
];
output = [object methodWithInputAndOutput:
input];
example below, we call the string
method on the NSString class, which
returns a new NSString object:
myObject = [NSString
string];
type means that the myObject
variable can refer to any kind of object,
so the actual class and the methods it implements aren't known when you compile the app.
In this example, it's obvious the object type will be an NSString
, so we can change the type:
myString = [NSString
string];
variable, so the compiler will warn us if we try to use
a method on this object which NSString doesn't support.
Notice that there's a asterisk to the right of the object type. All Objective-C object variables are
pointers types. The id
type is predefined as a pointer type, so there's no need
to add the asterisk.
Nested Messages
look like this:
Multi-Input Methods
up into several segments. In the header, a multi-input method looks like this:
:(NSString *)path atomically
:(BOOL)useAuxiliaryFile;
:@"/tmp/log.txt" atomically
:NO];
in the runtime system.
Accessors
accessors to get and set values in most cases. There are two syntaxes. This is the traditional 1.x syntax:
:@"Day at the Beach"];
output = [photo caption
];
It's actually calling a method named caption
. In most cases, you don't
add the "get" prefix to getters in Objective-C.
Whenever you see code inside square brackets, you are sending
a message to an object or a class.
Dot Syntax
= @"Day at the Beach";
output = photo.caption
;
The dot syntax should only be used setters
and getters, not for general purpose methods.
Creating Objects
myString = [NSString
string];
autoreleased
object, which we'll look at in more detail later. In many cases,
though, you need to create an object using the manual style:
myString = [[NSString alloc
] init
];
method called on NSString itself. This
is a relatively low-level call which reserves memory and instantiates an object.
The second piece is a
call to init
on the new object. The init implementation usually does basic setup, such as
creating instance variables. The details of that are unknown to you as a client of the class.
In some cases, you may use a different version of init
which takes input:
:1.0];
Basic Memory Management
In general, this means that you don't have to think about memory management until you get to
more complex cases.
However, you may not always be working with an environment that supports garbage collection. In that
case, you need to know a few basic concepts.
If you create an object using the manual alloc
style, you need to release
the
object later. You should not manually release an autoreleased object because your
application will crash if you do.
Here are two examples:
NSString* string1 = [NSString string
];
// must release this when done
NSString* string2 = [[NSString alloc
] init];
[string2 release
];
at the end of the current function.
There's more to learn about memory management, but it will make more sense
after we look at a few more concepts.
Designing a Class Interface
comes in two parts.
The class interface is usually stored in the ClassName.h
file, and defines
instance variables and public methods.
The implementation is in the ClassName.m
file and contains the actual code for these
methods. It also often defines private methods that aren't available to clients of the class.
Here's what an interface file looks like. The class is called Photo, so
the file is named Photo.h
:
@interface
Photo : NSObject {
NSString*
caption;
NSString*
photographer;
}
@end
, to pull in all of the basic classes for a Cocoa app.
The #import
directive automatically guards against including a single
file multiple times.
The @interface
says that this is a declaration of the class Photo
. The colon specifies the superclass, which is NSObject.
Inside the curly brackets, there are two instance variables: caption
and photographer
.
Both are NSStrings, but they could be any object type, including id.
Finally, the @end
symbol ends the class declaration.
Add Methods
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- caption
;
- photographer
;
@end
A single dash before a method name means it's a instance method. A plus
before a method name means it's a class method.
By default, the compiler assumes a method returns an id object, and that
all input values are id. The above code is technically correct, but it's unusual.
Let's add specific types for the return values:
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*
) caption;
- (NSString*
) photographer;
@end
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*) caption;
- (NSString*) photographer;
- (void) setCaption
: (NSString*)input;
- (void) setPhotographer
: (NSString*)input;
@end
.
Class Implementation
@implementation
Photo
- (NSString*) caption {
return
caption;
}
- (NSString*) photographer {
return
photographer;
}
@end
and the class
name, and has @end
, just like the interface. All methods
must appear between these two statements.
The getters should look very familiar if you've ever written code, so let's move
on to the setters, which need a bit more explanation:
{
;
caption = [input retain
];
}
- (void) setPhotographer: (NSString*)input
{
[photographer autorelease
];
photographer = [input retain
];
}
existing object, and the second is the new input object. In a garbage
collected environment, we could just set the new value
directly:
caption = input;
}
the old
object, and retain
the new one.
There are actually two ways to free a reference to an object: release
and
autorelease
. The standard release will remove the reference immediately.
The autorelease method will release it sometime soon, but it will definitely stay
around until the end of the current function (unless you add custom code to
specifically change this).
The autorelease method is safer inside a setter because the variables for the new
and old values
could point to the same object. You wouldn't want to immediately release an object which
you're about to retain.
This may seem confusing right now, but it will make more
sense as you progress. You don't need to understand it all yet.
Init
{
if ( self = [super init
] )
{
[self setCaption:@"Default Caption"];
[self setPhotographer:@"Default Photographer"];
}
return self;
}
This is a single equals sign, which assigns the result of [super init]
to self
.
This essentially just asks the superclass to do its own
initialization. The if
statement is verifying that
the initialization was successful before trying to set default values.
Dealloc
method is called on an object when it is being removed from
memory. This is
usually the best time to release references to all of your child instance variables:
{
;
[photographer release
];
[super dealloc
];
}
We don't need to use autorelease here, and the standard release is a bit faster.
The last line is very important. We have to send the message
[super dealloc]
to
ask the superclass to do its cleanup. If we don't do this, the object will not
be removed, which is a memory leak.
The dealloc method is not called on objects if garbage collection is enabled.
Instead, you implement the finalize
method.
More on Memory Management
to do is keep track of your references, and the runtime does the actual freeing
of memory.
In simplest terms, you alloc
an object, maybe retain
it at some point, then send one release
for each alloc/retain you sent.
So if you used alloc once and then retain once, you need to release twice.
create an object:
1. To keep it as an instance variable
2. To use temporarily for single use inside a function
In most cases, the setter for an instance
variable should just autorelease
the old object, and retain
the new one. You then
just make sure to release it in dealloc
as well.
So the only real work is managing local references inside a function. And there's
only one rule: if you create an object with alloc
or copy
,
send it a release
or autorelease
message at the
end of the function. If you create an object any other way, do nothing.
Here's the first case, managing an instance variable:
{
[totalAmount autorelease
];
totalAmount = [input retain
];
}
- (void) dealloc
{
[totalAmount release
];
[super dealloc];
}
object created with alloc
:
] initWithFloat:8.75];
NSNumber* value2 = [NSNumber numberWithFloat
:14.78];
// only release value1, not value2
[value1 release
];
instance variable:
] initWithFloat:8.75];
[self setTotal
:value1];
NSNumber* value2 = [NSNumber numberWithFloat
:14.78];
[self setTotal
:value2];
[value1 release
];
regardless of whether you're setting them as instance variables or not.
You don't need to think about how the setters are implemented.
If you understand this, you understand 90% of what you will ever need to
know about Objective-C memory management.
Logging
the NSLog()
function is nearly identical to the C printf()
function, except
there's an additional %@
token for objects.
( @"The current date and time is: %@
", [NSDate date] );
method
on the object, and prints the NSString which is returned. You can override the description
method in your class to return a custom string.
Properties
and author
earlier,
you might have noticed that the code is straightforward, and could probably be generalized.
Properties are a feature in Objective-C that allow us to automatically generate
accessors, and also have some other side benefits. Let's convert the Photo class to
use properties.
Here's what it looked like before:
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*) caption;
- (NSString*) photographer;
- (void) setCaption
: (NSString*)input;
- (void) setPhotographer
: (NSString*)input;
@end
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@property
(retain) NSString* caption
;
@property
(retain) NSString* photographer
;
@end
is an Objective-C directive which declares the property. The
"retain" in the parenthesis specifies that the setter should retain
the input
value, and the rest of the line simply specifies the type and the name of the
property.
Now let's take a look at the implementation of the class:
@implementation Photo
@synthesize
caption;
@synthesize
photographer;
- (void) dealloc
{
;
[photographer release
];
[super dealloc];
}
@end
directive automatically generates the setters and getters for
us, so all we have to implement for this class is the dealloc method.
Accessors will only be generated if they don't already exist, so feel free to specify @synthesize
for a property, then implement your custom getter or setter if you want. The compiler will
fill in whichever method is missing.
There are many other options for the property declarations, but those are
outside of the scope of this tutorial.
Calling Methods on Nil
object is the functional equivalent to the NULL
pointer in many other languages. The difference is that you can call methods
on nil without crashing or throwing an exception.
This technique used by the frameworks in a number of different ways, but the
main thing it means to you right now that you usually don't need to check
for nil before calling a method on an object. If you call a method on nil that
returns an object, you will get nil as a return value.
We can also use this to improve our dealloc method slightly:
{
self
.caption = nil
;
self
.photographer = nil
;
[super dealloc];
}
as an instance variable, the setter just retains
nil (which does nothing) and releases the old value. This approach is often better
for dealloc because there's no chance of the variable pointing at random data where
an object used to be.
Note that we're using the self.<var>
syntax here, which means
we're using the setter and picking up the memory management for free. If
we just directly set the value like this, there would be a memory leak:
// use self.caption to go through setter
caption = nil;
Categories
a category allows you to add methods to an existing class without subclassing
it or needing to know any of the details of how it's implemented.
This is particularly useful because you can add methods to built-in objects. If
you want to add a method to all instances of NSString in your application, you
just add a category. There's no need to get everything to use a custom subclass.
For example, if I wanted to add a method to NSString to determine if the contents
is a URL, it would look like this:
@interface
NSString (Utilities
)
- (BOOL) isURL;
@end
there is no super class listed, and there's a name for the category in
parenthesis. The name can be whatever you want, though it should communicate
what the methods inside do.
Here's the implementation. Keep in mind this is not a good implementation
of URL detection. We're just trying to get the concept of categories across:
@implementation
NSString (Utilities
)
- (BOOL) isURL
{
if ( [self hasPrefix:@"http://"] )
return YES
;
else
return NO
;
}
@end
"string1 is a URL" in the console:
NSString* string2 = @"Pixar";
if ( [string1 isURL
] )
NSLog (@"string1 is a URL");
if ( [string2 isURL
] )
NSLog (@"string2 is a URL");
categories to override existing methods in classes, but you should do so
very carefully.
Remember, when you make changes to a class using a category, it affects all instances
of that class throughout the application.
Wrap Up
pretty easy to pick up. There's not much special syntax to learn, and the same conventions are
used over and over again throughout Cocoa.
If you'd like these examples in action,
download the project below and look through the source code:
Xcode 3.0 Project (56k)