Learning: Objective-C


Overview: Objective-C

Objective-C is the primary language used to write iOS / Mac software. If you’re comfortable with basic object-oriented concepts and the C language, Objective-C will make a lot of sense.

Objects are data structures composed of one or more data types.

Methods are routines, processes and procedures.


Part 1: 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 method];
[object methodWithInput:input];

Methods can return a value:

output = [object methodWithOutput];
output = [object methodWithInputAndOutput:input];

You can call methods on classes too, which is how you create objects. In the example below, we call the string method on the NSString class, which returns a new NSString object:

id myObject = [NSString string];

The id 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:

NSString* myString = [NSString string];

This is now an NSString 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

In many languages, nested method or function calls look like this:

function1 ( function2() );

The result of function2 is passed as input to function1. In Objective-C, nested messages look like this:

[NSString stringWithFormat:[prefs format]];

Avoid nested nesting more than two message calls on a single line, as it easily gets unreadable.

Multi-Input Methods

Some methods take multiple input values. In Objective-C, a method name can be split up into several segments. In the header, a multi-input method looks like this:

-(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;

You call the method like this:

BOOL result = [myData writeToFile:@"/tmp/log.txt" atomically:NO];

These are not just named arguments. The method name is actually writeToFile:atomically: in the runtime system.


Part 2: Accessors

All instance variables are private in Objective-C by default, so you should use accessors to get and set values in most cases. There are two syntaxes. This is the traditional 1.x syntax:

[photo setCaption:@"Day at the Beach"];
output = [photo caption];

The code on the second line is not reading the instance variable directly. 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

The dot syntax for getters and setters is new in Objective-C 2.0, which is part of Mac OS X 10.5:

photo.caption = @"Day at the Beach";
output = photo.caption;

You can use either style, but choose only one for each project. The dot syntax should only be used setters and getters, not for general purpose methods.


Part 3: Creating Objects

Object Instance
There are two main ways to create an object. The first is the one you saw before:

NSString* myString = [NSString string];

This is the more convenient automatic style. In this case, you are creating an 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:

NSString* myString = [[NSString alloc] init];

This is a nested method call. The first is the alloc 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:

NSNumber* value = [[NSNumber alloc] initWithFloat:1.0];


Part 4: Memory Management

If you’re writing an application for Mac OS X, you have the option to enable garbage collection. 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:

// string1 will be released automatically
NSString* string1 = [NSString string];

// must release this when done
NSString* string2 = [[NSString alloc] init];
[string2 release];

For this tutorial, you can assume that an automatic object will go away 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.


Part 5: Class Interface

The Objective-C syntax for creating a class is very simple. It typically 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:

#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@end

First, we import Cocoa.h, 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

Let’s add some getters for the instance variables:

#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}

– caption;
– photographer;

@end

Remember, Objective-C methods typically leave out the “get” prefix. 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:

#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}

– (NSString*) caption;
– (NSString*) photographer;

@end

Now let’s add setters:

#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
– (NSString*) caption;
– (NSString*) photographer;

– (void) setCaption: (NSString*)input;
– (void) setPhotographer: (NSString*)input;

@end

Setters don’t need to return a value, so we just specify them as void.


Part 6: Class Implementation

Let’s create an implementation, starting with the getters:

#import "Photo.h"

@implementation Photo

– (NSString*) caption {
return caption;
}

– (NSString*) photographer {
return photographer;
}

@end

This part of the code starts with @implementation 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:

- (void) setCaption: (NSString*)input
{
;
caption = [input retain];
}

– (void) setPhotographer: (NSString*)input
{
[photographer autorelease];
photographer = [input retain];
}

Each setter deals with two variables. The first is a reference to the existing object, and the second is the new input object. In a garbage collected environment, we could just set the new value directly:

- (void) setCaption: (NSString*)input {
caption = input;
}

But if you can’t use garbage collection, you need to release 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

We can create an init method to set inital values for our instance variables:

- (id) init
{
if ( self = [super init] )
{
[self setCaption:@"Default Caption"];
[self setPhotographer:@"Default Photographer"];
}
return self;
}

This is fairly self-explanatory, though the second line may look a bit unusual. 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

The 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:

- (void) dealloc
{
;
[photographer release];
[super dealloc];
}

On the first two lines, we just send release to each of the instance variables. 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.


Part 8: Logging

Logging messages to the console in Objective-C is very simple. In fact, the NSLog() function is nearly identical to the C printf() function, except there’s an additional %@ token for objects.

NSLog ( @"The current date and time is: %@", [NSDate date] );

You can log an object to the console. The NSLog function calls the description 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.


Part 9: Properties

When we wrote the accessor methods for caption 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:

#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
– (NSString*) caption;
– (NSString*) photographer;

– (void) setCaption: (NSString*)input;
– (void) setPhotographer: (NSString*)input;

@end

Here’s what it looks like once converted to properties:

#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@property (retain) NSString* caption;
@property (retain) NSString* photographer;

@end

The @property 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:

#import "Photo.h"

@implementation Photo

@synthesize caption;
@synthesize photographer;

– (void) dealloc
{
;
[photographer release];
[super dealloc];
}

@end

The @synthesize 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.