Basics of Objective-C

Instance Variables

int age;
float percent;
NSButton *aButton; // pointer to an object. Note the '*'.

Creating a New Class

The class declaration goes in the interface (aka header) file. This is the .h file. Here's what the class declaration looks like:
// this class is called "MyClass" and it inherits from the "NSObject" class.
@interface MyClass : NSObject
{
// class code here
}

@end



The class implementation (aka the actual source code) goes in the implementation file. This is the .m file. Here's what a class implementation could look like:

// this class is called "MyClass" and it inherits from the "NSObject" class.
@implementation MyClass : NSObject
{
// class code here
}

@end

Boolean Variables

A boolean can have two values: YES or NO.

BOOL myVar;

Interface (aka Header) Files

In Objective-C, a header file is called an implementation file. It also has a .h file extension. The implementation file is where you put all your declarations, same as C. You include it in an implementation file with an "import" (same as C's "include") statement:


// notice the lack of semicolons:
#import <Cocoa/Cocoa.h> // standard import
#import "MyClass.h" // import one of your own

Functions / Methods

You just declare a method in the interface file. You write the actual method in the implementation file. Here's what a simple method declaration looks like:

+ (void)doSomething:(NSString *)aString atThisLocation:(int)xPos;

Sending Messages To Objects (aka Calling a Method)

Here's the syntax for calling a method on an object:

[object method];
[numberField1 doubleValue]; // an example


Now if you want to pass parameters, here's where those labels that we wrote in the function declaration come in.

With just one parameter:

[object method: first_param];
[numberField1 addToBankAccount: 100]; // 100 is the parameter being passed.


With more than one parameter:

[numberField1 addToBankAccount: 100 accountNumber:20010]; // accountNumber was the label for a parameter.


The same thing in C++ might be:

numberField1.addToBankAccount(100,20010);


Returning Values
currentBalance = [numberField1 addToBankAccount: 100 accountNumber:20010];


"this" in Objective-C
Suppose you want to call a method from within the class itself. In most languages you might do either of these:

this.methodName();
methodName();


In Objective-C, you say:

[self methodName];

Instantiating an Object

Step 1:
Allocate memory for the object and create it:

MyClass* anInstance = [MyClass alloc];


Step 2:
Initialize the object:

[anInstance init];



Usually, both are done in one statement like so:

MyClass* anInstance = [[MyClass alloc] init];



Step 3
After you're done using the object, free the memory:

[anInstance release];

Memory Management in Objective-C

Allocate Memory (Obj-C's version of malloc)
Use 'alloc':

SomeClass *myObject = [[SomeClass alloc] init];


Deallocate Memory (Obj-C's version of free)
Use 'release':

[someObject release];

this and super in Objective-C

this = self.
super = super.

The Equivalent Of C's 'main' Function

In C, main is the function that gets called by default. In Obj-C, it's awakeFromNib. After all outlets and actions are connected, the nib loader sends awakeFromNib to every object in the nib. This is where you can access outlets to set up default values or do configuration in code. For example:


-(void)awakeFromNib {
account = [[BankAccount alloc] init]; // creating a new BankAccount object
[display setFloatValue:[account balance]]; // sending a float value to an outlet
}

Strings in ObjC

The type is 'NSString', and you have to prepend the actual string with a '@':

NSString *name = @"Sherlock Holmes";

Constructors and Destructors

Constructor = init
Destructor = dealloc

// Constructor
- (objClass) init {
if ( self = [super init] ) {
// code here
}
return self;
}

// Destructor
- (void) dealloc {
// code here
[super dealloc];
}

Compiling Objective-C on the Command Line

Here's an example that uses the Foundation framework (i.e. has an import line that reads: #import <Foundation/Foundation.h>):

gcc -framework Foundation ex2.m -o ex2

Type Casting

Here's an example casting a float to an int:

int myInt = (int) 29.5;

Static Variables

You can have a local variable retain its value through multiple invocations of a method by using the 'static' keyword:

static int counter = 0;


For example, suppose you want to count how many times a particular method has been called:

-(void) showPage
{
static int pageCount = 0; // this line only runs once to initialize
pageCount++;
}


The 'static int pageCount = 0;' will only run the first time the method is called, and on future calls it will increment. Since it's a static variable, it is incremented whenever ANY object calls showPage...they all share the variable. It's not like each object instance has it's own copy of counter (like an instance variable).

The @class Directive

Suppose you have a class ClassA, and you're using objects in it of type ClassB:

@interface ClassA: NSObject
{
  ClassB *myObj;
}
@end


You need to include ClassB in ClassA somehow. This is how you do it:

#import "ClassB.h"


Another way to do it (which is NOT equivalent...read on) is:

@class ClassB;


Advantages of @class
The compiler doesn't need to process the entire ClassB.h file; it just needs to know that ClassB is the name of a class.

Disadvantages of @class
All the @class directive tells the compiler is "hey, I have some variables in here of type ClassB. If you want to do ANYTHING to these variables, such as call a method, assign a value etc, you need to use #import instead. So most of the time, you'll be using #import.

Polymorphism and the id Data Type

If you want to use polymorphism, id is the most generic data type you can use:

id someVar;


Suppose you have two data types 'Integer' and 'Real', and both have a 'print' method that prints out the value. You can do this:

id generic; // notice no '*'
Integer *myInt = 1;
Real *myReal = -32.5;

generic = myInt;
[generic print];

generic = myReal;
[generic print];


Why don't we use a '*' in front of id? Because it's already a pointer. Read about it here

Try/Catch Statement

@try {

}@catch (NSException *exception)
{

}

Categories

Suppose you're using a class from the Foundation library, say NSArray, and you wish it had some method that it doesn't have right now. One way to add that method is to just subclass NSArray. Another way is to use categories to add a new method to NSArray itself:

#import

@interface NSArray (aditsMethods)
-(void) aNewMethod;
-(void) anotherMethod;
@end


This adds a new category, "aditsMethods", to NSArray. Notice that you do need to import the original header file that defines NSArray for this to work. This literally just adds a few methods to the existing NSArray class. But this is just the declaration of the methods...you do still need to write the definitions in some implementation file (or the same file, if you want):

@implementation NSArray (aditsMethods)
-(void) aNewMethod
{
// some code here
}

-(void) anotherMethod
{
// some code here
}
@end


Other important points
- A category has access to the instance variables of the original class, but it can't add its own. If you need to do that, consider subclassing.

- A category can override another method in the class, but this is generally considered bad programming practice. If you override the method, you no longer have access to the original method. You also need to duplicate all the functionality of the overridden method. Compare this to subclassing: if you override a method in a subclass, you can still reference the parent's method by sending a message to 'super'.

- Remember that extending a class affects not only that class, but all its subclasses as well, because they now inherit your methods.

Protocols

Protocols in Objective-C are the same as interfaces in Java; a protocol just defines a group of methods that a class must implement if it is to conform to / adopt the protocol.

Here's how you define a protocol:
@protocol myProtocol
- (void) myMethod;
- (void) anotherMethod;
@end


If another class adopts myProtocol, this is how the interface would be declared:

@interface myClass: NSObject <myProtocol>
@interface myClass2: NSObject <myProtocol, anotherProtocol> // here's a class that adopts multiple protocols


You can also specify part of the protocol to be optional:

@protocol myProtocol
- (void) myMethod;
- (void) anotherMethod;

@optional
// everything from here down is optional.
- (void) optionalMethod1;
- (void) optionalMethod2;
@end


Protocols and variables
Maybe you want to make sure that a particular variable contains objects that conform to a certain protocol. For example, here's a variable:
id myObj;


Since it's of type 'id', it can contain any object. If you only want it to contain objects that implement 'myProtocol', use this instead:

id myObj;
id myObj; // Here's a variable that only contains objects that conform to BOTH myProtocol and anotherProtocol.

Quit an Application

NSApp is a global variable. You don't need to worry about declaring/initializing/etc.

[NSApp terminate: sender];

// For Example:
- (IBAction)closeApp:(id)sender {
[NSApp terminate: sender];
}

Loading Data From a File / Opening a File

NSFileManager *fm = [NSFileManager defaultManager];
// The path specified here CAN be relative, but then make sure it's relative to the compiled application, which is usually compiled into a different directory than the source code.
NSData *myData = [fm contentsAtPath:@"/absolute/or/relative/url/to/file/test.txt"];
// Put the data in a string
NSString *myString = [NSString stringWithUTF8String:[myData bytes]];
// Place the string in a textbox
[myTextbox setStringValue:myString];

Save Data To a File

// get the data from some textbox
NSString *myString = [textbox stringValue];
// The path specified here CAN be relative, but then make sure it's relative to the compiled application, which is usually compiled into a different directory than the source code.
[myString writeToFile:@"/abs/or/rel/path/to/file.txt" atomically:NO encoding:1 error:NULL];

Convert an NSString to a regular C-style string (char *)

char * c_string = [someNSString UTF8String];

Show Open Files Dialog

  int i;
  // Create the File Open Dialog class.
  NSOpenPanel* openDlg = [NSOpenPanel openPanel];
  
  // Enable the selection of files in the dialog.
  [openDlg setCanChooseFiles:YES];
  
  // Multiple files not allowed
  [openDlg setAllowsMultipleSelection:NO];
  
  // Can't select a directory
  [openDlg setCanChooseDirectories:NO];
  
  // Display the dialog. If the OK button was pressed,
  // process the files.
  if ( [openDlg runModalForDirectory:nil file:nil] == NSOKButton )
  {
   // Get an array containing the full filenames of all
   // files and directories selected.
   NSArray* files = [openDlg filenames];

   // Loop through all the files and process them.
   for( i = 0; i < [files count]; i++ )
   {
   NSString* fileName = [files objectAtIndex:i];
   }

Show Save File Dialog

// Create the File Open Dialog class.
NSSavePanel *saveDlg = [NSSavePanel savePanel];

int result = [saveDlg runModal];
if (result == NSOKButton){
  NSString *outputFile = [saveDlg filename];
  [filenameOut setStringValue:outputFile];
}    

A List of Global Variables

NSApp: your application.

Delegates

Delegates are objects that act on behalf of other objects. For example, a NSTextView will do something when the text inside it gets selected: it selects and highlights the text. What if you wanted to change this? You could subclass NSTextView, but if that's all you want to do, a smarter idea might be to create a delegate for the textview that takes over if there is some text selection to be done.

Creating a delegate
A delegate is just a class...you create it like any other class:

1. In Interface Builder, go to the MainMenu.nib window and click on 'Classes'.

2. Right-lick on NSObject and choose "subclass...".

3. Name your class.

4. Right-click and choose 'instantiate...'.

Connecting an object to the delegate

1. Click on the object and hit apple-shift-2 to open up the inspector with the 'Connections' tab opened. Make sure the 'outlets' button is selected.

2. You should see delegate as one of the options. If you don't, you might need to double click in the object. Or you might have come across an object that can't be delegated.

3. Right click the object and connect it to the delegate.

4. In the outlets, choose 'delegate'.

That's it!

Using the delegate to act on behalf of the delegating object

You can look in the documentation to see what methods can be delegated. In the example with the selection that we were talking about above, we can redefine the 'willChangeSelectionFromCharacterRange' method in the delegate to do something crazy:
- (NSRange)textView:(NSTextView *)aTextView willChangeSelectionFromCharacterRange:(NSRange)oldSelectedCharRange toCharacterRange:(NSRange)newSelectedCharRange
{
  NSRange range;
  range.location = 0;
  range.length = 1;
  return range;
}


Try putting that in your new delegate class...then type some text in the textview and see what happens!

Showing Other Nibs

Step 1: File's Owner
Suppose you want to show a Nib "preferences". You need to make your own subclass for the File's Owner of that Nib. Subclass 'NSWindowController'. For our example, let's suppose we subclass 'NSWindowController' and name the new class 'PrefController'. For this subclass you might want to make outlets to things like the main window, any buttons etc so you can access them after the Nib is loaded. Suppose we make an outlet 'window' and then write the files for our new subclass. Here's what our interface looks like:

#import

@interface PrefController : NSWindowController
{
IBOutlet id window;
}
@end


Step 2: The 'init' Method
We need to write the init method for this class. Here's what it looks like:

- (id) init {
self = [super initWithWindowNibName: @"the_name_of_your_nib_without_the_extension"];
return self;
}


This calls the 'initWithWindowNibName' method of NSWindowController to initialize the Nib. We pass in the name of the Nib file as the argument.

Step 3: Calling 'init'
That's it! Now we just need to put in the actual code to create this nib wherever you want this nib created from. First, you need to import the class header:

#import "PrefController.h"


Then here's the actual code for loading a new Nib:

PrefController *myWindow = [[PrefController alloc] init];
  [myWindow showWindow:self];


Notice how we're using that init function we wrote. The showWindow method is another method of NSWindowController that shows the window associated with that Nib.

Tada! Nib loaded. Now you can use whatever functions you defined in the PrefController class on the new 'myWindow' object. And of course you can use any NSWindowController methods as well.

Note: Make sure your window has 'Visible at launch time' checked in Interface Builder. Open the Nib you want to load in Interface Builder. Click on the main window and then hit apple-shift-1. Check 'Visible at launch time' if it isn't checked already. Without this, the nib will be loaded but you won't see anything because the window isn't visible.

NSTask: Objective-C's version of a Fork / Exec

Using fork and exec in Objective-C can lead to problems. NSTask should be used instead:

NSTask task = [[NSTask alloc] init];
[task setLaunchPath: @"/usr/local/bin/lame"]; // abs path to whatever you're trying to exec
    
// here we pass in the arguments.
// unlike exec, the first argument is NOT the name of the program we're trying to exec
// like exec, the last argument IS nil.
NSArray *arguments;
arguments = [NSArray arrayWithObjects: arg1,arg2, nil];
[task setArguments: arguments];

// here we say that when the task is finished, we should call the taskFinished method
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskFinished:) name:NSTaskDidTerminateNotification object:task];

// start the task        
[task launch];    


// here's that taskFinished method that will run
// when our task is finished.

- (void) taskFinished:(NSNotification *)note {
  // some code here
}


Note that instead of waitpid() or wait(), we just tell the task to let us know when it is done, sort of like a SIGCHLD signal handler.

If you really wanted to block until the task is finished like wait() does, you could use this instead:

[task launch];
[task waitUntilExit];


But of course the problem is that your program won't do anything until the task is completed — just like a wait().