Creating Programs for Mac OS X. Part 1: Introduction and Objective-C
Introduction
I think everyone has heard about Mac OS X as an operating system for designers and housewives. But I want to talk about development tools for OS X, otherwise good programs are written, and nobody knows what they do.
I must say right away that I will not talk about cross-platform frameworks and toolkits (such as Qt) or about creating console applications, I will tell you about what distinguishes Mac OS X from other operating systems in terms of creating applications, namely, the Cocoa framework. I will make a reservation right away that I will try to avoid comparisons with other frameworks, I just want to talk about Cocoa.
Let's look a little into the story. Mac OS X is a further development of NextSTEP OS. NextSTEP was the first OS in which the Objective-C language was very widely used, a large library of ready-made objects was written on it, moreover, as well as ordinary data types - strings, arrays, dictionaries, and objects used to build GUI applications. Therefore, most of the applications under NextSTEP were written in Objective-C using ready-made objects. This very library has grown into the Cocoa framework.
But to include an almost unfamiliar API in the new OS would be an extremely bad decision, so 2 more were added: Classic and Carbon.
Classic is designed to run Mac OS 9 applications, at the moment it is pointless to consider it, because after switching to Intel processors, for obvious reasons, Classic was thrown out of the system.
Carbon was created to easily transfer applications from Mac OS 9 to OS X, with the ability to add to the finished program code new functionality, available only in a dozen. Oddly enough, but many applications are still written in Carbon (for example, MS Office for Mac and some Adobe products).
At the moment, the Carbon and Cocoa frameworks are developing in parallel, but only Cocoa will be developed from the next release of Mac OS X.
The main development language for Cocoa is Objective-C, and since in the future all the examples will go in this language, for the first part I will tell you about it. But if you already own Python or Ruby, then you do not need to learn Objective-C, in XCode 3.0 (development hint, about it in the next part) bindings for these languages are "sparkling".
Objective-C Programming Language
In addition to the widely known and widespread object extension of the C language - the C ++ language - there is another extension of it - the Objective-C language, which is extremely simple, fully compatible with the C language and a very powerful and expressive object model borrowed from Smalltalk.
The language was coined by Brad Cox in the early 80s of the last century. Cox's goal was to create a language that supports the concept of software IC. This concept refers to the ability to assemble programs from off-the-shelf components (objects), just as complex electronic devices can be easily assembled from a set of off-the-shelf integrated circuits (ICs, integrated curcuits). Moreover, such a language should be simple enough and based on the C language to facilitate the transition of developers to it.
One of the goals was also to create a model in which the classes themselves are also full-fledged objects, and introspection and dynamic message processing would be supported.
The resulting Objective-C language turned out to be extremely simple - it will only take a few days for a C programmer to master it. It is precisely an extension of the C language - the C language simply added new features for object-oriented programming. Moreover, any C program is also a program on Objective-C (for C ++, this is not true).
Another of the features of the language is that it is message-oriented while C ++ is function-oriented. This means that in it method calls are interpreted not as a function call (although this usually comes down to this), but rather as sending a message (with a name and arguments) to an object, similar to how it happens in Smalltalk. This approach provides a number of advantages - so any message can be sent to any object. Instead of processing a message, an object can simply send it to another object for processing (the so-called delegation), in particular, distributed objects can be easily implemented in this way (i.e. objects located in different address spaces and even on different computers). The binding of the message to the corresponding function occurs directly at the execution stage.
Objective-C language supports working with meta-information - so you can ask the object directly at runtime for its class, a list of methods (with the types of arguments passed) and instance variables, to check whether the class is a descendant of the given and supports the specified protocol, etc. P.
The language has normal protocol support (i.e., the concept of an object interface and a protocol are clearly separated). For objects, inheritance is supported (not multiple), for protocols, multiple inheritance is supported. An object can be inherited from another object and several protocols at once (although this is more likely not a protocol inheritance, but its support).
Objective-C is currently supported by the gcc compiler. Quite a lot of the language has been ported to the runtime library and is heavily dependent on it. Together with the gcc compiler, a minimal version of such a library is supplied. You can also freely download the runtime library from Apple: Apple's Objective-C runtime. These two runtime libraries are quite similar (the main difference is in the names of the methods), although in the future I will focus on the runtime library from Apple.
Language syntax
Objective-C uses a special type id to denote objects . A variable of type id is actually a pointer to an arbitrary object. To indicate a null pointer to an object, the constant nil is used . Speaking of id: the Doom game engine was developed on Next workstations, so there may be a connection between the id type and the idSoftware name.
Moreover, instead of idyou can use a more familiar designation with an explicit indication of the class. In particular, the latter allows the compiler to perform some check on the support of the message by objects - if the compiler from a variable type cannot conclude that the object supports this message, then it will give a warning (not an error!). Thus, the language supports type checking, but in a non-strict form (i.e., found inconsistencies are returned as warnings, not errors).
The following syntax is used to send messages:
[receiver message];
The message may also contain parameters:
[myRect setOrigin: 30.0: 50.0];
In this example, the name of the method (message) is setOrigin ::. Note that exactly one colon corresponds to each argument passed. In this example, the first argument has a label (the text before the colon), and the second does not.
Objective-C language allows you to label each argument, which significantly increases the readability of the code and reduces the likelihood of passing an incorrect parameter.
[myRect setWidth: 10.0 height: 20.0];
In this example, setWidth: height: is the message name.
It also supports the ability to pass an arbitrary number of arguments in a message:
[myObject makeGroup: obj1, obj2, obj3, obj4, nil];
Like functions, messages can return values, and in contrast to C, the type returned by default is id.
float area = [myRect area];
The result of one message can be immediately used in another message:
[myRect setColor: [otherRect color]];
As already mentioned, in Objective-C, classes themselves are objects. The main objective of such objects (called class objects) is to create instances of this class. In this case, the class name itself plays a double role - on the one hand, it acts as a data type (i.e., it can be used to describe pointers to objects of a given class). On the other hand, the class name can act as the object to which the message is sent (in messages, the class name can participate only as a receiver). Objective-C does not have a built-in type for Boolean values, so this type is usually introduced artificially. Next, I will use the BOOL type for logical values with possible values of YES and NO (IMHO is more clear, but not as “politically correct” as true / false).
Creating New Classes
All new directives to the compiler in Objective-C begin with the @ symbol. As in C ++, the description of the class and its implementation are separated (usually the description is placed in header files with the extension h, and implementations in files with the extension m).
The following is the general structure for describing the new class:
@interface ClassName: SuperClass
{
instance variable declarations
}
method declarations
end
In the runtime version from Apple, all classes share a common ancestor - the NSObject class, which contains a number of important methods. The description of variables is no different from the description of variables in structures in the C language:
@interface Rect: NSObject
{
float width;
float height;
BOOL isFilled;
NSColor * color;
}
end
Each description begins with a plus or minus sign. A plus sign indicates that this method is a class method (that is, it can only be sent to a class object, and not to instances of this class). In fact, class methods are analogues of static methods in classes in C ++. The minus sign is used to denote methods of objects - instances of this class. Note that in Objective-C, all methods are virtual, i.e. may be overridden.
The following are descriptions of the possible methods for the Rect class.
@interface Rect: NSObject
{
float x, y;
float width;
float height;
BOOL isFilled;
NSColor * color;
}
+ newRect;
- (void) display;
- (float) width;
- (float) height;
- (float) area;
- (void) setWidth: (float) theWidth;
- (void) setHeight: (float) theHeight;
- (void) setX: (float) theX y: (float) theY;
end
Note that the method name may match the name of the instance variable of this class (for example, width and heigh).
The type of the value returned by the method is indicated in parentheses immediately after the plus or minus sign (but before the method name). If the type is not specified, it is considered that the value of type id is returned. Next is the name of the method, where after each colon the type of the argument (in parentheses) and the argument itself are specified. Objective-C language also allows one of the following descriptors to be specified for method arguments - oneway, in, out, inout, bycopy and byref. These descriptors are used to specify the direction of data transfer and the transmission method.
A method that takes an arbitrary number of parameters can be described as follows:
- makeGroup: (id) object, ...;
To include the header file in Objective-C, instead of the #include directive, use the #import directive, completely similar to #include , but guaranteeing that the data file will be connected only once.
The implementation of the class methods is as follows:
#import "ClassName.h"
@implmentation ClassName
method implementations
end
The following is an example implementation of the methods of the Rect class described earlier.
#import "Rect.h"
@implmentation Rect
+ newRect {
Rect * rect = [[Rect alloc] init];
[rect setWidth: 1.0f];
[rect setHeight: 1.0f];
[rect setX: 0.0fy: 0.0f];
}
- (float) width {return width; }
- (float) height {return height; }
- (float) area {return [self width] * [self height]; }
- (void) setWidth: (float) theWidth {width = theWidth; }
- (void) setHeight: (float) theHeight {height = theHeight; }
- (void) setX: (float) theX y: (float) theY {
x = theX;
y = theY;
}
end
As you can see from the example above, all instance variables are available in the methods. However, as in C ++, it is possible to control the visibility of variables (the visibility of methods cannot be controlled) using the directives private , protected, and public (acting completely similar to the C ++ language).
How the messaging engine works
The compiler translates each message sent, i.e. a construction of the form [object msg] in the function call objc_msgSend.
This function, as its first parameter, takes a pointer to the recipient of the message, the so-called second parameter a selector used to identify the message being sent. If the message contains arguments, then they are also passed to objc_msgSend as the third, fourth, etc. parameters. Next, a search is made for a suitable function among the functions of this class, if it is not found, then among the functions of the parent class, if not found there, then among the functions of the parent class of the parent class (:-)), etc. If the method (i.e. the function corresponding to it) is found, then it is called with the transfer of all the necessary arguments.
Otherwise, the object is given the last chance to process the message before the exception is thrown - the message selector along with the parameters is “wrapped” in a special object of type NSInvocation and the message forwardInvocation: is sent to the object, where the object of the NSInvocation class acts as a parameter.
If the object supports forwardInvocation:, then it can either process the sent message itself or send it to another object for processing:
- (void) forwardInvocation: (NSInvocation *) anInvocation
{
if ([someOtherObject respondsToSelector: [anInvocation selector]])
[anInvocation invokeWithTarget: someOtherObject];
else
...
}
Creation and destruction of objects
In the Objective-C language itself, there are no special commands for creating and destroying objects (like new and delete). This task lies with the runtime library and is implemented using the message sending mechanism.
Creating a new object is divided into two steps - memory allocation and object initialization. The first step is implemented by the method of the alloc class (implemented in the NSObject class), which allocates the required amount of memory (this method is used to allocate memory not only for objects of the NSObject class, but also any class inherited from it). In this case, the allocated memory is zeroed and a pointer to the class object of the corresponding class is written to the isa attribute.
Please note that the alloc message is sent to the class object of the required class and this message returns a pointer to the memory allocated for the object.
Actually, the initialization of the object itself (i.e., setting the values of its instance-variables, allocation of additional resources, etc.) is carried out by other methods, according to tradition, the names of these methods begin with init. Typically, such a message is sent immediately after the alloc message, to the address returned by this message.
id anObject = [[Rectangle alloc] init];
When creating a new class, there is usually no need to redefine the alloc method, but the need to redefine the init method arises quite often (although in many cases you can rely on resetting the allocated memory to zero).
Please note that the init method (s) is a normal method that does not stand out from the rest (unlike C ++, where the constructor is a special method from which for example it is impossible to take the address). Therefore, when creating a new class and init method, the call to the overridden init method (using [super init]) must be done explicitly at the very beginning of the method.
Mac OS X (like NextStep) uses reference counting to control the lifetime of objects - each object contains a certain counter inside it, which is set to one during creation.
Sending a retain message to an object increases the value of this counter by one (so all container classes of the Foundation library, when an object is placed in them, send a retain message to it). The established practice is to send the message to the retain object by all parties (objects) interested in it, i.e. if you remember a reference to an object, you should send a message to it with retain.
When the object ceases to be needed, then a release message is simply sent to it. This message reduces the counter value by one and, if this value is less than one, destroys the given object.
Before destroying the object, a dealloc message is sent to it, allowing the object to perform its de-initialization. At the same time, this is also a normal message, and in it you should explicitly call the inherited implementation at the end via [super dealloc].
- (void) dealloc
{
...
[super dealloc];
}
Objective-C 2.0
At WDC2006, Apple introduced a new version of the language - 2.0. Among the innovations were garbage collection, fast enumeration, class properties, 64-bit support, and much more. It should be noted that these innovations are available only for Leopard.
Garbage collection
Objective-C 2.0 allows automatic garbage collection, although this is optional.
The properties
Previously, to change and read instance variables, it was necessary to write return and set methods (the so-called getters and setters), now you can write like this:
@interface Person: NSObject {
}
@property (readonly) NSString * name;
@property (readonly) int age;
- (id) initWithName: (NSString) name age: (int) age;
end
You can get the name like this:
NSString * name = aPerson.name;
Fast enumeration
Now an analogue of the foreach statement has been added:
for (Person * p in thePeople) NSLog (@ "% @ is% i years old.", [P getName], [p getAge]);
Enough for the first part. When compiling the article, we used materials from the sites developer.apple.com and steps3d.narod.ru (by the way, the only site on which there is information about programming in Mac OS X in Russian).
In the next part I’ll talk about the Xcode development environment and Interface Builder, as well as show how to create a very simple application.