Objective-C was written in the early 1980s by scientist and software engineer Brad Cox. It was designed as a way of introducing the capabilities of the Smalltalk language into a C programming environment. A majority of the iPhone’s framework libraries are written in Objective-C, but because the language was designed to accommodate the C language, you can use C and C++ in your application as well. Objective-C is used primarily on Mac OS X and GNUstep (a free OpenStep environment). Many languages, such as Java and C#, have borrowed from the Objective-C language. The Cocoa framework makes heavy use of Objective-C on the Mac desktop, which carried over onto the iPhone.
The first thing you’ll notice in Objective-C is the heavy use of brackets. In Objective-C, methods are not called in a traditional sense; instead, they are sent messages. Likewise, a method doesn’t return, but rather responds to the message. Unlike C, where function calls must be predefined, Objective-C’s messaging style allows the developer to dynamically create new methods and messages at runtime. The downside to this is that it’s entirely possible to send an object a message to which it can’t respond, causing an exception and likely program termination.
Given an object named myWidget, a message can be sent to its powerOn method this way:
returnValue = [ myWidget powerOn ];
The C++ equivalent of this might look like:
returnValue = myWidget->powerOn( );
The C equivalent might declare a function inside of its flat namespace:
returnValue = widget_powerOn(myWidget);
Arguments can also be passed with messages, provided that an object can receive them. The following example invokes a method named setSpeed and passes two arguments:
returnValue = [ myWidget setSpeed: 10.0 withMass: 33.0 ];
Notice the second argument is explicitly named in the message. This allows multiple methods with the same name and data types to be declared—polymorphism on steroids.
returnValue = [ myWidget setSpeed: 10.0 withMass: 33.0 ]; returnValue = [ myWidget setSpeed: 10.0 withGyroscope: 10.0 ];
While C++ classes can be defined in Objective-C, the whole point of using the language is to take advantage of Objective-C’s own objects and features. This extends to its use of interfaces. In standard C++, classes are structures, and their variables and methods are contained inside the structure. Objective-C, on the other hand, keeps its variables in one part of the class and methods in another. The language also requires that the interface declaration be specifically declared in its own code block (called @interface) separate from the block containing the implementation (called @implementation). The methods themselves are also constructed in a Smalltalk-esque fashion, and look very little like regular C functions.
The interface for our widget example might look like Example 1, which is a file named MyWidget.h.
#import <Foundation/Foundation.h>
@interface MyWidget : BaseWidget
{
BOOL isPoweredOn;
@private float speed;
@protected float mass;
@protected float gyroscope;
}
+ (id)alloc;
+ (BOOL)needsBatteries;
- (BOOL)powerOn;
- (void)setSpeed:(float)_speed;
- (void)setSpeed:(float)_speed withMass:(float)_mass;
- (void)setSpeed:(float)_speed withGyroscope:(float)_gyroscope;
@end
|
Each of the important semantic elements in this file are explained in the following sections.
The preprocessor directive #import replaces the traditional #include directive (although #include may still be used). One advantage to using #import is that it has built-in logic to ensure that the same resource is never included more than once. This replaces the round-about use of macro flags found routinely in C code:
#ifndef _MYWIDGET_H #define _MYWIDGET_H ... #endif
The interface is declared with the @interface statement followed by the interface’s name and the base class (if any) it is derived from. The block is ended with the @end statement.
Methods are declared outside of the braces structure. A plus sign (+) identifies the method as a static method, while a minus sign (-) declares the method as an instance method. Thus, the alloc method (to allocate a new object) will be called using a reference directly to the MyWidget class, whereas methods that are specific to an instance of the MyWidget class, such as needsBatteries and powerOn, will be invoked on the instance returned by alloc.
Every declared argument for a method is represented by a data type, local variable name, and an optional external variable name. Examples of external variable names in Example 1 are withMass and withGyroscope. The notifier (calling function) that invokes the method refers to external variable names, but inside the method the arguments are referenced using their local variable name. Thus, the setSpeed method uses the local _mass variable to retrieve the value passed as withMass.
If no external variable name name is supplied in the declaration, the variable is referenced only with a colon, for example, :10.0.
The code suffix for Objective-C source is .m. A skeleton implementation of the widget class from the last section might look like Example 2, which is named MyWidget.m.
|
Code View:
#import "MyWidget.h"
@implementation MyWidget
+ (id)alloc {
}
+ (BOOL)needsBatteries {
return YES;
}
- (BOOL)powerOn {
isPoweredOn = YES;
return YES;
}
- (void)setSpeed:(float)_speed {
speed = _speed;
}
- (void)setSpeed:(float)_speed withMass:(float)_mass {
speed = _speed;
mass = _mass;
}
- (void)setSpeed:(float)_speed withGyroscope:(float)_gyroscope {
speed = _speed;
gyroscope = _gyroscope;
}
@end
|
Just as the interface was contained within its own code block, the implementation begins with an @implementation statement and ends with @end. In C++, it is common practice to prefix member variables with m_ so that public methods can accept the name of the variable. This makes it easy to reuse someone else’s code because they can deduce a variable’s purpose by its name. Since Objective-C allows for an external variable name to be used, the method is able to provide a sensible name for the developer to use while internally using some proprietary name. The true name can then be used inside the object, while the method’s local variable name is prefixed with an underscore, e.g., _speed.
Objective-C adds a new element to object-oriented programming called categories. Categories were designed to solve the problem where base classes are treated as fragile to prevent seemingly innocuous changes from breaking the more complex derived classes. When a program grows to a certain size, the developer can often become afraid to touch the smaller base classes because it’s too difficult by then to determine what changes are safe without auditing the entire application. Categories provide a mechanism to add functionality to smaller classes without affecting other objects.
A category class can be placed “on top” of a smaller class, adding to or replacing methods within the base class. This can be done without recompiling or even having access to the base classes’ source code. Categories allows for base classes to be expanded within a limited scope, so that any objects using the base class (and not the category) will continue to see the original version. From a development perspective, this makes it much easier to improve on a class written by a different developer. At runtime, portions of code using the category will see the new version of the class, and code using the base class directly will see only the original version.
The difference between categories and inheritance is the difference between tricking out your car versus dressing it up as a parade float. When you soup up your sports car, new components are added to the internals of the vehicle that cause it to perform differently. Sometimes components are even pulled out and replaced with new ones. The act of adding a new component to the engine, such as a turbo, affects the function of the entire vehicle. This is how inheritance works.
Categories, on the other hand, are more like a parade float in that the vehicle remains completely intact, but cardboard cutouts and papier-mâché are affixed to the outside of the vehicle so that it appears different. In the context of a parade, the vehicle is a completely different animal, but when you take it to the mechanic, it’s the same old stock car you’ve been driving around.
The widget factory is coming out with a new type of widget that can fly through space, but is concerned that making changes to their base class might break existing applications. By building a category, applications using the MyWidget base class will continue to see the original class, while the newer space applications will use a category instead. The following example builds a new category named MySpaceWidget on top of the existing MyWidget base class. Because we need the ability to blow things up in space, a method named selfDestruct is added. This category also replaces the existing powerOn method with its own. Contrast the use of parentheses here to hold the MySpaceWidget contained class with the use of a colon in Example 1 to carry out inheritance.
#import "MyWidget.h" @interface MyWidget (MySpaceWidget) - (void)selfDestruct; - (BOOL)powerOn; @end
Example 3 shows a complete source file implementing the category.
#import "MySpaceWidget.h"
@implementation MyWidget (MySpaceWidget)
- (void)selfDestruct {
isPoweredOn = 0;
speed = 1000.0;
mass = 0;
}
- (BOOL)powerOn {
if (speed == 0) {
isPoweredOn = YES;
return YES;
}
/* Don't power on if the spaceship is moving */
return NO;
}
@end
|
In Objective-C, a subclass class can pose as one of its superclasses, virtually replacing it as the recipient of all messages. This is similar to overriding, only an entire class is being overridden instead of a single method. A posing class is not permitted to declare any new variables, although it may override or replace existing methods. Posing is similar to categories in that it allows a developer to augment an existing class at runtime.
In past examples, mechanical widget classes were created. Well, at some point after designing all of these widgets, perpetual energy was discovered. This allowed many of the newer widgets to be autonomous, while some legacy widgets still required batteries. Because autonomous widgets have such a significant amount of different code, a new object called MyAutonomousWidget was derived to override all of the functionality that has changed, such as the static needsBatteries method. See Example 4 and Example 5.
#import <Foundation/Foundation.h>
#import "MyWidget.h"
@interface MyAutonomousWidget : MyWidget
{
}
+ (BOOL)needsBatteries;
@end
|
#import "MyAutonomousWidget.h"
@implementation MyAutonomousWidget
+ (BOOL)needsBatteries {
return NO;
}
@end
|
Instead of changing all of the existing code to use this class, the autonomous class can simply pose as the widget class. The class_poseAs method is called from the main program or another high-level method to invoke this behavior:
MyAutonomousWidget *myAutoWidget = [ MyAutonomousWidget alloc ];
MyWidget *myWidget = [ MyWidget alloc ];
class_poseAs(myAutoWidget, myWidget);
At this point, any other methods we’ve replaced in the posing class (to change how we talk to autonomous devices) would pose as the original base class.
No comments yet.
RSS feed for comments on this post. TrackBack URL
You must be logged in to post a comment.
---