Getting Started with Objective-C

Posted On // Leave a Comment
Programs created for the iPhone are written in Objective-C. Objective-C is often described as a strict superset of the C language. Others call it C with object oriented concepts applied. The first time I saw it, I have to say I felt like I had no idea what I was looking at. It doesn't look like any other programming language that I know. But after you get through your first few programs, you'll find it to be much easier to follow. Being a strict superset of C, if you already have algorithms that are written in C, you should be able to port them over to the iPhone. The following chart shows some expressions that mean close to the same thing in C and Objective-C:
C/C++Objective-C
#include "library.h"#import "library.h""
thisself
private:@private
protected:@protected
public:@public
Y = new MyClass();Y = [[MyClass alloc] init];
try, throw, catch, finally@try, @throw, @catch, @finally
Some expressions that mean the same (or almost the same) in C/C++ and Objective-C.
Within Objective-C, both your class definitions and objects instantiated from those classes are instances of a struct called 'id'. The definition of id is as follows:
typedef struct  objc_object {
     Class* isa;
} *id;
Since this is the foundation of all objects within Objective-C, you will find an "isa" member on every Objective-C object that refers to its type data. Instances of "id" are all pointers, so you can distinguish one from another by comparing their addresses. There is a special id value named nil whose pointer value is set to zero.

Classes

In Objective-C, you declare a class' interface separate from its implementation. You have probably seen a similar separation of interface and implementation in C++ programs where a class' interface is defined in a *.h file the implementation of which is in a *.cpp file. Likewise, in Objective-C, the interface is defined in a *.h file and its implementation in a *.m file.
Every class you create in Objective-C should either directly or indirectly derive from the NSObject base class. The base class doesn't look to do much at first glance, but it contains functionality that the runtime needs for interacting with the object. You don't want to have to implement this functionality yourself.

Class Interface Declaration

To declare a new class, you start off by declaring the class' interface. The class interface declaration starts with the@interface compiler directive, and ends with the @end directive. You'll see a set of open and close curly-brackets within a class interface declaration. Just remember that the closing bracket is not the end of the class interface definition! Here is a generic class definition:
@interface YourClassName: TheSuperClass
{
     instance members
}
Message Declarations
 
@end
The declaration for the instance members doesn't differ from the one you might expect from one of the other C languages.
BOOL  isValid;
float width;
NSString* name;
A class automatically has access to all the classes in its inheritance ancestry. So you can refer to classes of any of its base types without doing anything special. If you are referring to a class that is not in its inheritance chain, then you will need to make a forward declaration for that class. The following makes a forward reference to two classes:Employee and Accountant.
@class Employee, Accountant;
The declaration of messages is different. A message can either be associated with the class or associated with an instance of that class. Class methods are preceded with a plus (+), while instance methods are preceded with a minus (-). The default return type of class messages are id instances. To change the return type to something else, you'll need to put a return type in parentheses just before the class name. Note that this is the same syntax that you would use to cast a variable from one type to another. Here's an example of a simple message declaration:
- (int)GetSize;
Message parameters immediately follow a message name. They are colon delimited, and their types are indicated with the same syntax that one would use to cast to a different type.
-(float) Power:(float)width:(float)height;

Class Implementation

The class implementation is in a *.m file. Just as you would in C++, you will need to include the class' header file when you are writing the implementation. Instead of using a #include directive to do this, you will use a #importdirective. The syntax for declaring the implementation is similar to the syntax for declaring the interface; only you replace the @interface directive with an @implementation directive. One possible syntax for starting off the implementation follows:
#import "YourClass.h"
@implementation YourClass
   your message implementations go here
@end
Optionally, you could re-declare the instance variables and super class here, but it's not necessary. The message implementations start off the same way as a message. But instead of terminating the declaration with a semicolon(;), you append a curly bracket pair enveloping your implementation.
- (float)square:(float)x
 
{
     return x*x;
}

Methods, Messages, and Object Communication

Most object oriented material you come across will refer to messages being sent among objects. Most of the time, you'll see this implemented through methods. In most cases, the words method and message can be used interchangeably. Objective-C sticks with using the terminology "message". So I'll adhere to that standard within this document.
The syntax for sending a message (which means the same thing as "calling a method") is to enclose the name of the target object and the message to be passed within square brackets. So in C++/C#, you may have send a message using the following syntax:
myObject.SomeMethod();
Within Objective-C, you would accomplish the same thing with the following:
[myObject SomeMethod];
Passing parameters to messages is a little different than what you are used to. For one parameter, things make perfect sense and don't need much of an explanation. Just place a colon after the name of the message followed by the parameter value.
[myObject SomeMethod:20];
When you want to pass more than one argument, things could potentially look messy.
[myObject SomeMethod:20:40];
As more arguments are added to a message, the code could potentially become less readable; I wouldn't expect a developer to memorize the parameters that a method takes. Even if he or she did, then there's still mental effort involved in identifying what parameter is associated with which value. For clarity, you could do something like this:
[myObject SomeMethodA:20 B:40];
In the above, it appears that I've named the parameters 'A' and 'B' and that parameter 'A' is oddly concatenated to the message name. What you may not have realized is that the parameter names and colons are all parts of the message name. So the full name of the message is SomeMethodA:B:.
If a message returns an object as a message, you can pass a message to the returned message through a nested call. In C/C#, you would have code that looks like this:
myObject.SomeMessage().SomeOtherMessage();
Whereas in Objective-C, the same thing would be accomplished with this:
[[myObject SomeMessage] SomeOtherMessage];

Prototypes

Prototypes are much like weakly implemented interfaces. In other programming languages, if a class implements an interface, then within the class' declaration, there is a statement that shows that it is implementing a certain interface. With prototypes, the class only needs to implement certain methods to conform to a prototype. There doesn't need to be any mention of the prototype definition that it is conforming to.

Class Instantiation and Initialization

An instance of a class is instantiated using the alloc method of the class. alloc will reserve the memory needed for the class. After the memory is allocated, you'll want to initialize it. Every class that has instance members will need to have an init method defined.
myObject = [[MyClass alloc] init];
The first time I saw the above pattern, I made the mistake of thinking that it was equivalent to the following:
myObject = [MyClass alloc];
[myObject init];
They are not equivalent. The value returned by init may not be the same as the value returned by alloc. So you should always combine the alloc and init messages when doing the assignment.
When you implement your own initializer for a class, the initializer should either return self to signal that it was successful, or nil to signal that it was unsuccessful. The return type for an initializer should always be id. This is to explicitly show that the type returned by the initializer may be of another type.
If you need to pass a parameter to your initializer, then it will generally have init as the prefix for the initializer name. For example, if I had an initializer that required a date, I may call it initWithNSSDate.

Memory Management

Objective-C classes keep track of a reference count. The count can be read through -(NSUInteger)retainCount;at any time (though other than satisfying curiosity, there aren't many scenarios that call for needing to do this). When the reference count of an object reaches zero, then it is released. For the most part, the details of this reference counting are abstracted away, and the methods related to reference counting are presented as methods for taking ownership of an object (which increments the reference count) or relinquishing ownership of the object (decrementing the reference count).
The general rule is that if you've created an object with any method prefixed with alloc or new, or had copied the object from an existing object, then you are responsible for releasing the object. If you've taken ownership of an object (with retain), then you must also release it. Note: If you don't own it, then don't release it. Calling autoreleaseon an object will add it to a pool of objects to be released and deallocated at some point in the future.

Copying an Object

When copying an object, if that object's member variables contain pointers to other objects, there are two ways to go about copying it. On way is to copy the pointers from the original object to the object being created. This method of copying (also called a shallow copy) will result in both the new object and the original object to share the same instances of objects that make up their members. So if a change occurs within a member object of one class, it will also affect the member within the other since they are really the same instance. The other method of copying is to create new instances of the member objects when the new object is created. The end result of this is that changes to an instance object on one instance of the object will have no impact on the other. The original and copied objects are completely independent in this case. Both approaches have their advantages and disadvantages when one looks at the results of the two copy methods in terms of memory consumption and potential side affects. There can exist scenarios that have a mixture of shallow and deep copy techniques. If you created a class and implemented a deep copy but one of the instance members implements a shallow copy, then you could end up with an instance member of an instance member referring to the same object as an instance member of an instance member in the object you just copied (that's potentially confusing, but I can think of no clearer way to say it).
There should be consistency between how a class' copy method works and how the class' set methods work. If the set method on the class creates new instances when performing assignment, then the copy method should be a deep copy. If the set method saves the instance that is passed to it, then the copy method should be a shallow copy.
-(void)SetMyMemberVariable:(id)newValue
{
    [myMmeberVariable autorelease];
    myMemberVariable = [newValue copy];
}
Implementation of a set method that creates a new instance of an object.
-(void)SetMyMemberVariable:(id)newValue
{
    [myMemberVariable autorelease];
    myMemberVariable = [newValue retain];
}
Implementation of a set method that copies a reference to an existing instance of an object.
Low Memory Conditions
The user interface for your iOS programs will be controlled through a class derived from UIViewController. There are two methods on the class that are involved in low memory handling. One is didReceiveMemoryWarning which was in existence before iOS 3. As of iOS version 3 and later, there is a method named viewDidUnload. When the device is low on memory, the OS will destroy the views if it knows that the views can be reloaded and recreated again. When this method is called, your class should respond by releasing its references to view objects.

0 comments:

Post a Comment

Followers