Anyone who wants to develop programs for the products of Apple Company, i.e. for MacOS and iOS platforms, meets such a moment in his life when he needs to learn new programming language – Objective-C. I met such moment once too and asked myself how to learn Objective-C fast. I decided to take notes of my thoughts when understanding the documentation to remember the main specifics of this language – this is how this beginner’s guide to Objective-C appeared. This is probably not the best Objective-C tutorial for beginners, but I hope it will allow you to save time and efforts starting your work with this language.
Contents:
- Banal Theory of Origin of OOP
- A Little of History
- Specifics of Objective-C
- Message Exchnage
- The Work of Message Transfer
- Objective-C Method Declaration
- Objective-C Class Declaration
- Creation of Objects in Objective-C
- Object Lifetime
- Access Methods
- Autorelease Pool in Program Threads
- Objective-C Class Definition
- Categories and Extensions
- Protocols
- Exceptions in Objective-C
- Properties
Banal Theory of Origin of OOP
The problem of reuse of the written code and its portability always makes the developers search for new ways of its sorting, organization, and abstracting. New programming paradigms, design patterns, languages, compilers and standard libraries for them, program platforms and frameworks are created to solve these problems. In such a way, a paradigm of subprograms (procedures) appeared. It is implemented with the help of CALL\RET
processor commands and stack (in fact, it is redirection of the execution thread by the address of an arbitrary command but not the one following the current command, with the subsequent return). Then, there is a paradigm of modules (each file is a separate translation unit) that created a two-stage translation: compilation of modules and then their linking (static and dynamic) to an executable module. As a result of increasing of the code size in projects and difficulties of its support, a new object โ oriented paradigm of programming appeared in early 1960s. This paradigm splits programs into even smallest components โ types of data. Its main point consists in interaction of entities (objects) by means of sending messages to each other. Each object is a variable of a data type defined by the developer (the so-called class). The definition of such special user data type (class) consists in the following two things: definition of data set (invariants, members) and a set of subprograms (methods) that will serve them.
Class is usually designed as a type defined by the developer. This type is based on embedded (language) data types and/or other classes. This can be a structure (struct
) for the C language that does not support an object-oriented paradigm. A set of subprograms is implemented as usual functions that have at least one parameter โ pointer to a data set that is to be handled. The main advantage of the object-oriented method is the possibility to create new classes on the basis of the written ones (to add invariants and methods, redefine methods, use methods defined in the base class as their own). It is called inheritance.
A set of methods represents an interface for interaction with invariants. Impossibility of direct modification of class data (without using its interface) reflects the principle of encapsulation. On the figure above, class and its oblects are represented. There is an x invariant of float type and doubleX
interface (method) for it that returns the invariant value. Sometimes it is necessary to send message to an object that responds to it definitely (i.e., to call the method implemented for this class object), but the certain class of this object is unknown according to cicumstances. For example, we need to send a Move
message to each element of the list of pointers to objects of Auto
class. And we know that there are pointers to objects of not only Auto
class in the list but there are also pointers to derivative (inherited) Ford
and Subaru
classes. We can do it only due to the polymorphism principle. It consists in the following: when a certain message is sent to an object from a certain class hierarchy, where all objects can receive such message, this object responds to it correspondingly to its own class but not the basic class for the current hierarchy. Simula 67 became the first language with the support of the object-oriented approach. Then, Smalltalk appeared. And in 1980s, C++ appeared, the main language of the modern system programming. Its expansion and improvement in 1990s generated a number of design paradigms and patterns. It also influenced the modern vision of object-oriented approach, including Objective-C language.
A Little of History
Objective-C appeared in 1980s as a modification of C in Smalltalk โstyleโ. This modification consisted in adding the new syntactic constructions and in a special preprocessor for them (which transformed them into the simple calls of C functions in the code), and also in the runtime library (to process those calls). Thus, Objective-C was initially interpreted as an add-on over C. In some way, it is still so: we can write a program in pure C and then add some constructions from Objective-C (if necessary). Or vice versa: we can freely use C in programs in Objective-C. Besides, all of this also concerns programs in C++.We can say that the easiest way to learn Objective-C from scratch is already have knowledge of C/C++ :).
In 1988, NeXT (later, Apple) licensed Objective-C and wrote a compiler and a standard library (SDK in fact) for it. In 1992, the developers of the GNU project within the OpenStep project improved of the language and compiler. Since then, GCC has supported Objective-C. After buying NeXT, Apple took their SDK (compiler, libraries, IDE) as a basis for their further implementations. IDE for the code was named Xcode and IDE for GUI was named Interface Builder. Cocoa framework for GUI implementations (and not only for them) is the most significant development environment for programs in Objective-C nowadays, so learning Cocoa is a must.
Specifics of Objective-C
Module files in Objective-C have the โ.mโ extension (if the mix of C++ and Objective-C was used, then the extension is โ.mmโ). Header files have the โ.hโ extension. All class objects created in Objective-C must be allocated in the dynamic memory. That is why, id type takes special significance. This type is a pointer to an object of any class (void *
in fact). The zero pointer is named by the nil
constant. Thus, we can convert the pointer to any class to the id
type. A problem appears: how can we know to which class the object that is hidden behind id
refers? It is made using the isa
invariant that is present in any class object that inherited the special NSObject
basic class (NS
prefix means NeXT Step). The isa
invariant refers to the reserved Class
type. The object of such type allows to find out names of its own and basic class, set of class invariants, and also prototypes of all methods that this object implements and their addresses (by means of the local list of selectors). All reserved Objective-C words that differ from the reserved C language words start with the @ symbol (for example, @protocol
, @selector
, @interface
). Usually, names of class invaiants with the limited scope (@private
, @protected
) start with the underscore character. There is a convenient NSString
class for strings in Cocoa. String constant of such class is written as @โHello worldโ and not as a simple C string constant โHello worldโ. The BOOL
type (in fact, unsigned char) can accept YES
and NO
constant values. All reserved words specific for Objective-C (that differ from C language and are located in the objc/objc.h header file) are listed below:
@interface
โ Starts the declaration of the class or category (category is an extension of the class by additional methods without inheritance)@implementation
โ Starts the definition of the class or category@protocol
โ Starts the declaration of a protocol (analog of C++ class that consists of only virtual functions)@end
โ Ends the declaration\definition of any class, category, or protocol@private
โ Limits the scope of visibility of class invariants for the class methods (similarly to C++)@protected
โ is set by default. Limits the scope of visibility of class invariants for the class methods and methods of derivative classes (similarly to C++)@public
โ Deletes the limitations for the scope of visibility (similarly to C++)@try
โ Defines the block with a possible generation of exceptions (similarly to C++)@throw
โ Generates an object-exception (similarly to C++)@catch
() โ Handles the exception generated in the previous @try block (similarly to C++)@finally
โ Defines the block after the@try
block, to which the control is passed, regardless of the fact if the exeption was generated or not@class
โ Shortened form of class declaration (only the name; similarly to C++)@selector(method_name)
โ Returns the compiled selector for the name of themethod_name method
@protocol(protocol_name)
โ Returns the copy of the class-protocol with theprotocol_name name
@encode(type_spec)
โ Initializes the string of symbols that will be used for encryption of data oftype_spec type
@synchronized()
โ Defines the code block that is executed only by one thread at any certain moment
Message Exchnage
To make the object execute a certain method, we need to send it a message with the same name as the required method. Such message is called method selector. Sending syntax is as follows:
[receiver method];
We can send parameters for the called method in the message:
[receiver method: 20.0ย : 30.0];
We need to put a colon before each parameter. There are as many colons as parameters. The name of the method can continue after each such colon-parameter:
[receiver methodWithFirstArgument: 10 andSecondArgument: 20];
Methods with unlimited amount of arguments are called by the following syntax:
[receiver undefinedNumberParameters: one, two, three, four, five, six, seven];
Message sending (as any C function) returns a certain value (perhaps, void
):
BOOL booleanValue; booleanValue = [reveiver method];
It disappears when sending the nil
message. An exception appears when sending a message to an object that belongs to a class that did not implement the required method. This exception, being not intercepted, leads the whole program to an unexpected shutdown. We can use the following code pattern to check if the current object responds to any message:
if ([anObject respondsToSelector: @selector(myMethodWith2Argumets::)])
{ //we can call
[anObject myMethodWith2Argumetns: @โfirstโย : @โsecondโ];
}
else
{
//do not call in any case
}
The Work of Message Transfer
Sending a message is translated into the C-function with a prototype:
id objc_msgSend(id receiver, SEL method, โฆ);
The SEL
type is defined as char const *
in fact. But it is better to interpret it as int because all selectors are indexed by integer values according to the global table of selectors during the execution.
Using the isa
invariant of the receiver object (the presence of isa
is inevitable because all classes must inherit the NSObject
class when using Foundation framework, basic for Cocoa), this function browses the local list of class selectors to define if an object of the current class responds to the method message. If there is such selector, the control is passed to the corresponding class method. The object id
(pointer to its invariants) and parameters of the objc_msgSend()
function defined after the selector are passed to this class method. The value returned by the method is returned as a result of message sending. If the object-receiver does not have such selector, the objc_msgSend()
function browses the list of selectors of its basic class.
For example, using such scheme, the call:
[receiver addObject: otherObject];
Is translated into:
objc_msgSend(receiver, 12, otherObject);
In the global table of selectors, 12 corresponds to the โaddObject:โ string. Next, the objc_msgSend()
function performs the search in the list of selectors of the receiver object. When the function finds it (let it be an object of the NSArray
class, which implemented the method with 12 selector), it performs the call of the type: addObject(receiver, otherObject);
Objective-C Method Declaration
It is interesting to note that the prototype of the addObject method from the previous part looked as follows in the Objective-C class declaration:
(void)addObject: (id)otherObject;
It accepted only one parameter. Proceeding from the principle of object-oriented paradigm that methods are subprograms that handle certain data sets we need to pass address of data to be processed to the method. That is why such parameter is passed to any class method indirectly. The minus sign (โ-โ) that stands first in the method prototype informs the compiler about the additional parameter. Such method (with the minus sign before) is called the object method (or instance method). It can be called only for the object of any class. In the method body, this pointer to the copy of data (or the object address to which the message was sent) is available by means of the reserved self
word (similarly to this in C++). The pointer to the instance of the basic class is available through the reserved super
word. Besides, an indirect _cmd
parameter โ selector of this method from the global table of selectors โ is passed to the object method. From the C++ developer point of view, all object methods in Objective-C are like those declared with the virtual key word and always follow the dynamic polymorphism. If we put the plus sign (โ+โ) in the beginning of the method prototype, such method will be interpreted as class method and will not accept the indirect self parameter, correspondingly (it is similarly to the declaration of static-method in C++). The super
pointer will not also work without the isa
object invariant to which self
points. Thus, the prototype of any method is declared in the following way:
-|+ (<type of the returned value>) main PartoftheMethodName [ย : (<type of the first parameter>)name of the FirstFormalParameter [ [additional PartoftheMethodName]ย : (<type of the second parameter>)name of the SecondFormalParameter] โฆ ]
For example:
+ (Class)class;
+ (id)alloc;
- (id)init;
- (void)addObject: (id)anObject;
+ (NSString *)stringWithCString: (const char*)aCString usingUncoding: (enum NSStringEncoding)encoding;
- (NSString *)initStringWithFormat: (NSString *)format, โฆ;
If the method returns a certain object (id
type) or class (Class
type), we can use the embedded syntax of the call:
[myLabel setText: [[NSString stringWithString: @โHelloโ] stringByAppendingString: @โ worldโ]];
Here, value of the text invariant (that is equal to a @โHello worldโ string) is set for the object of the UILabel
class from the UIKit
framework. This string in its turn is created by the concatenat ion of @โHelloโ and @โ worldโ strings. The first is a result of sending of stringWithString
message to a NSString
class with a @โHelloโ parameter-constant. Such call returns the object of the NSString
class that is initialized by a string-parameter. Then, the stringByAppendingString
message with @โ worldโ parameter is sent to this method. The result of sending this message is the object of the NSString
class that contains concatenation of the value of the object-receiver and string argument. This object gets as a parameter to the setText:
message of the myLabel
object.
Objective-C Class Declaration
Let’s proceed to a small Objective-C classes tutoral. Letโs declare a simple class for the complex number in the Complex.h file:
#import <Foundation/Foundation.h> //for NSObject and NSString strings
@interface Complex : NSObject
{
double _re; //invariant for a real part
double _im; //invariant for an imaginary part
NSString *_format; //format string for the description method
}
- (id)initWithRe: (double)re andIm: (double)im; //specialized constructor
+ (Complex *)complexWithRe: (double)re andIm: (double)im; //class method for a one-stage creation of an object
- (Complex *)add: (Complex *)other; //method for addition
- (Complex *)sub: (Complex *)other; //method for substraction
- (NSString *)format; //method of access to _format
- (void)setFormat: (NSString *)format; //method of _format setting
- (double)re; //other methods of access to real and imaginary parts
- (void)setRe: (double)re;
- (double)im;
- (void)setIm: (double)im;
@end
As we can see, the whole declaration is put into key words @interface
and @end
. First, invariants are declared (in curly brackets). Methods are placed out of curly brackets. The description method is absent in the class declaration not accidentally. It is present in the class definition as well as the dealloc
and init
method. When sending a description
message to an object of the Complex
class, its local list of selectors will be examined. After compilation, selectors of all methods implemented by the class of this object and even not declared in the interface part will get into this list. It means that init
, description
, and dealloc
will be called correctly.
Creation of Objects in Objective-C
All objects are allocated in the dynamic memory, that is why we need to create an object in two steps:
- memory allocation (alloc message) and
- initialization of invariants (class constructors).
MyClass *myObject = [[MyClass alloc] init]; //method of the MyClass alloc class allocates a chunk of required size and returns the pointer to it, method of the init object initializes invariants of the myObject object
After the creation of an object, we can use it:
NSMutableArray *array = [[NSMutableArray alloc] init]; //we create a variable array
MyClass *myObject = [[MyClass alloc] init]; //our object [myObject myMethod]; //sending of a certain message
[array addObject: myObject]; //we place the object to the array
MyClass *otherObject = [array getLastObject:]; //we take it from the array, point to it using another pointer
[otherObject myOtherMethod: YES]; //we send it another message with an argument of the BOOL type
Some classes possess a method for quick (one-stage) creation of their own instances. Such methods are class methods, they return a pointer to an object of their own class and their name usually starts with the name of the class itself. For example, the following method:
+ (NSString *)stringWithCString: (char const *)string encoding: (NSStringEncoding)encoding;
It returns the ready string that is initialized by the corresponding string with a final zero without calls of alloc
and init
:
NSString *myString = [NSString stringWithCString: โBla-bla-blaโ encoding: NSASCIIStringEncoding];
Object Lifetime
As soon as the pointer to an object goes beyond the scope, memory that is allocated for it is lost (if that is the last pointer to that object) and leaks. The paradigm of calculation of references to resources is supported in Objective-C to avoid such unwanted effects. Thus, each object has an integer counter that displays the number of pointers that refer to it. When this counter reaches zero, memory allocated for this object returns to the system. This counter is equal to 1 after the call of the method of the alloc class. We need to send a retain message to an object to increase its value and release message to decrease the value. NSObject
implements all these methods. Any our class inherits it. It is interesting to note that value of the counter for static objects of the NSString
class (for example, @โI am a stringโ) is equal to -1, i.e., the maximum possible. Here is an example of work with the counter:
id anObject = [SomeClass alloc]; //first, the counter == 1
anObject = [anObject init]; //here, object invariants are created
[anObject reatin]; //we increase its value (now, it is equal to 2)
[anObject release]; //then, we decrease it (the counter is equal to 1 again and the object is still viable)
[anObject release]; //the counter is zeroed; invariant counters are decresed by 1 and memory allocated for the object is returned to the OS
Implementation of init
is very important. It is the class constructor. Constructors differ in that they return id
and their names always start with the word init
, and the constructor by default is init
itself. The scheme of any constructor is like the one below:
- (id)init
{
self = [super init]; //we call the constructor of a basic class for initialization of its invariants
if (self) //if everything succeeded in the constructor of a basic class and it returned a correct object and did not return nil having released memory
{
//we can initialize our invariants
}
return self; //and return itself
}
Here is a typical specialized (not default) constructor for a class with two members of a type of a certain class and with one integer invariant:
- (id)initWithInt: (int)number
{
if (self = [super init])
{
_myMember1 = [[SomeClass alloc] init]; //we allocated memory and then initialized it
_myMember2 = [[SomeClass alloc] init];
_myIntMember = number; //here, we initialize a constructor by a passed parameter โ just for example
}
return self;
}
Implementation of release and retain for NSObject
is ideologically as the shown below and we do not need to redefine it in derivative classes due to absence of access to the invariant of references counter:
- (void)retain
{
[_internalLock lock]; //locking for synchronization
_referenceCounter++; // supposing that _referenceCounter is a hidden invariant of a counter
[_internalLock unlock];
}
- (void)release
{
[_internalLock lock];
_referenceCounter--; //we decrease the counter
if (!_referenceCounter) //if it is equal to zero
{
[_internalLock unlock];
[self dealloc]; // (here, locking will release)
}
[_internalLock unlock];
}
It means that dealloc
message is sent to the object itself. In the implementation of its method it can decrease counters of its invariants if necessary and pass the similar message to an object of a basic class so that it makes the same. It is obvious that implementation of a dealloc
method for NSObject
will release memory allocated for the object. Usually, dealloc
for a certain class looks as follows:
- (void)dealloc
{
[_myMember1 release]; //we decrease the counter of our invariant
[_myMember2 release]; //we decrease the counter of another our invariant
//[_myIntMember release]; it does not have any sense; embedded types do not receive messages at all and do not have counters
[super dealloc]; //letโs tell an object of a basic class that itโs time to release memory
}
Access Methods
A correct work with the calculation of references is very important when returning the object address from the method or when initializing invariant using a formal parameter. Usually, the so-called access methods are used for such things. They return and set invariants of objects. It is good practice to name the method that returns the invariant value in the same way as the invariant and to start the name of the method that sets its value with the set
word:
- (void)setRe: (double)re
{
_re = re;
}
The _re
invariant refers to the embedded type so there will be no difficulties with change of its values. But if the invariant is an object of a certain class, a simple assignment is not enough because we need to take into account reference counters. The following three methods are applied to solve this problem:
//for example, we need to change the label text
[label setText: @"Hello world"]; //we set the text invariant
//of a label object equal to text constant of NSString * type
//approximate implementation of setText in UILabel class (variant โ1)
- (void)setText: (NSString *)text
{
[text retain]; //we increase the reference counter to a formal parameter
[_text release]; //we decrease the reference counter of the current value of our _text invariant
_text = text; //we initialize the invariant with a new value
}
//approximate implementation of setText in UILabel class (variant โ2)
- (void)setText: (NSString *)text
{
if (_text != text) //we compare pointers to objects
{
[_text release]; //we decrease the reference counter of the current value of our _text invariant
_text = [text retain]; //we increase the reference counter to a formal parameter and initialize our invariant
}
}
//approximate implementation of setText in UILabel class (variant โ3 โ undesirable)
- (void)setText: (NSString *)text
{
if (_text != text)
{
[_text autorelease]; //we throw the current value of our _text invariant to a autorelease pool
_text = [text retain]; //we increase the reference counter to a formal parameter and initialize our invariant
}
}
Variant โ3 is not very good because it obstructs the current autorelease pool and it is usually undesirable (see the next part). Method of access for reading the value of invariant is always very simple:
- (NSString *)text
{
return _text;
}
Autorelease Pool in Program Threads
Now, letโs try to return an object created inside the method:
- (NSString *)sayHelloToName: (NSString *)name withSurname: (NSString *)surname
{
NSString *retString = [[NSString alloc] initWithFormat: @โ%@ %@!โ, name, surname]; //we initialize the created object by means of the return retString format string;
}
The format string corresponds to C language standard. But if we need to define the id type in it, we use the format specificator – %@. How does the method that parses the format understand, which symbols to place instead of id
? It will just place what the description
method of the current object will return. This method is initially declared for the NSObject
class. NSString
redefines it to the output of its string contents. Having redefined it, any object can represent its string contents. For example, the class of a complex number with two invariants of the double type can perform this as follows:
- (NSString *)description
{
return [NSString stringWithFormat: @โre: %lf im: %lfโ, _re, _im]; //it returns the @โre: 1.0 im: 2.5โ string for _re == 1.0 and _im == 2.5
}
There will be a memory leak after performing the sayHelloToName:withSurname:
method. The calling code will not guess that the release
message must be sent to the returned object after processing. Even if it performs that, it can happen that the pointer to object invariant is returned and it means that its deletion can have serious consequences. It would be great to have a mechanism of self-release of objects in future so that the user code did not need to release them. This problem can be solved with the help of an object of the NSAutoreleasePool
class โ autorelease pool of objects. We can send the autorelease
message to all objects created after the creation of an object of such class. Such object is placed to the current (the last created) autorelease pool. When a certain pool receives the release
message, it sends the same message to all its objects. It also decreases their reference counter (in fact, deleting it). Thus, an object placed into the autorelease pool continues living and occupies memory during the whole pool life. It is convenient for small temporary objects but it can occupy a significant part of the memory with the course of time. That is why it is recommended to frame cycles that can create a lot of temporary objects, which move to the autorelease pool, with the local (embedded) pools. Any thread in the program that uses Cocoa must create an object of the NSAutoreleasePool
class in the beginning (before creating other objects) and delete it at the end (after deleting all other objects). The main()
function that is the main thread of any program in Objective-C must look like this when using the Cocoa framework:
int main(int argc, char *argv[]) // or just main()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //we create a pool and it becomes the current one automatically
int retVal; //now we can develop
[pool release]; //we release the pool and all objects that are placed to it using the autorelease calls
return retVal;
}
And the correct sayHelloToName:withSurname:
method will look like this:
-(NSString *)sayHelloToName: (NSString *)name withSurname: (NSString *)surname
{
NSString *retString = [[NSString alloc] initWithFormat: @โ%@ %@!โ, name, surname]; //we initialize the created object by means of the [retString autorelease] format string; then we place it to the pool and retString will be released together with the pool
return retString;
}
Besides, the drain
method of the autorelease pool is similar to release
. The only difference is that except the release of itself and other objects contained in it, it prompts the garbage collector to go in. But it is actual only for Mac OS 10.4 and higher because iOS does not have the garbage collector.
Objective-C Class Definition
Now, letโs examine the Complex.m file with the definition of methods of the Complex
class:
#import โComplex.hโ
@implementation Complex
- (id)init
{
return [self initWithRe: 0.0 andIm: 0.0];
}
- (id)initWithRe: (double)re andIm: (double)im
{
if (self = [super init])
{
_re = re;
_im = im;
_format = @โre: %.1lf im: %.1lfโ; //output format by default
}
}
+ (Complex *)complexWithRe: (double)re andIm: (double)im
{
return [[[Complex alloc] initWithRe: re andIm: im] autorelease];
}
- (Complex *)add: (Complex *)other
{
return [[Complex alloc] initWithRe: _re + other->_re andIm: _im + other->_im];
}
- (Complex *)sub: (Complex *)other
{
return [[Complex alloc] initWithRe: _re โ other->_re andIm: _im โ other->_im];
}
- (NSString *)format
{
return _format;
}
- (void)setFormat: (NSString *)format
{//standard order of actions for invariant-object
[format retain];
[_format release];
_format = format;
}
- (double)re
{
return _re;
}
- (void)setRe: (double)re
{
_re = re;
}
- (double)im
{
return _im;
}
- (void)setIm: (double)im
{
_im = im;
}
- (NSString *)description
{//we use the set output format
return [NSString stringWithFormat: _format, _re, _im];
}
- (void)dealloc
{
[_format release]; //dealloc was redefined for this
[super dealloc];
}
@end
By default, the constructor calls the specialized constructor with the defined initial parameters. The complexWithRe:andIm:
method returns the initialized object of the Complex
class that is located in the current autorelease pool. The description method performs the same and returns the object of the NSString
class. Here is an example of a program where the Complex
class is used:
#import โComplex.hโ
#import <stdio.h> //for printf()
int main()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Complex *num1 = [[Complex alloc] init]; //0.0+0.0*i
Complex *num2 = [[Complex alloc] initWithRe: 1.5 andIm: -2]; //1.5-2.0*i
Complex *num3 = [Complex complexWithRe: 5 andIm: 7]; //5.0+7.0*i
printf(โ%s\nโ, [[num2 description] cStringUsingEncoding: NSASCIIStringEncoding]); //output> re: 1.5 im: -2.0
printf(โ%s\nโ, [[[num2 add: num3] description] cStringUsingEncoding: NSASCIIStringEncoding]); //output> re: 6.5 im: 5.0
[num1 setRe: [num2 re]]; //set _re for num1 as in num2
[num1 setIm: [num3 im]]; //set _im for num1 as in num3
[num1 setFormat: @โ%.2lf+%.2lf*iโ]; //change the output format for num1
printf(โ%s\nโ, [[num1 description] cStringUsingEncoding: NSASCIIStringEncoding]); //output> 1.50+7.00*i
[num1 release];
[num2 release];
//[num3 release]; it is not required as it is already in the autorelease pool
[pool drain];
return 0;
}
Categories and Extensions
Categories help to add/redefine some methods without inheritance from an already written (or, maybe, compiled one) class:
//file โCategorizedComplex.hโ
#import โComplex.hโ
@interfce Complex (CategorizedComplex)
- (Complex *)mul: (Complex *)other;
- (Complex *)div: (Complex *)other;
@end
//file โCategorizedComplex.mโ
#import โCategorizedComplex.hโ
@implementation Complex (CategorizedComplex)
- (Complex *)mul: (Complex *)other
{
return [Complex complexWithRe: _re * other->_re - _im * other->_im andIm: _re * other->_im + _im * other->_re];
}
- (Complex *)div: (Complex *)other
{
double retRe, retIm, denominator;
denominator = other->_re * other->_re + other->_im * other->_im;
if (!denominator)
return nil;
retRe = (_re * other->_re + _im * other->_im) / denominator;
retIm = (_im * other->_re - _re * other->_im) / denominator;
return [Complex complexWithRe: retRe andIm: retIm];
}
@end
And we can use it as follows:
Categoriz?dComplex *num1 = [[CategorizedComplex alloc] initWithRe: 1 andIm: 999];
Complex *num2 = [Complex complexWithRe: 0 andIm: 0];
CategorizedComplex *num3 = [num1 div: num2]; //num3 == nil
Extensions serve as anonymous categories:
//file โCategorizedComplex.mโ
#import โCategorizedComplex.hโ
@interface Complex ()
- (void)zeroComplex; //secret method for zeroing of a number
@end
@implementation Complex
- (void)zeroComplex //only methods of the class itself can use it
{
_re = 0;
_im = 0;
}
@end
Protocols
The Objective-C protocol is a formalized declaration of a group of methods that can implement any class if necessary (similar to class in C++ where all methods are declared with the virtual โฆ = 0
specificator). There can be required (@required
specificator that is considered as a default one) and selective (@optional
specificator) protocol methods in the language version 2.0. If any class implemented the required protocol methods, it is called a class that supports this protocol. Protocol and class that supports it are declared as follows:
@protocol MyPrinterProtocol
@required
- (void)print;
- (BOOL)switchedOn;
@optional
- (void)loadPapaer: (int)numberOfPages;
@end
@interface MyPrinter : NSObject <MyPrinterProtocol>
//now, MyPrinter implements MyPrinterProtocol methods
{
BOOL _state;
int _numberOfPages;
}
- (id)initWithState: (BOOL)state andPagesCount: (int)pages;
- (BOOL)state;
@end
We can send the print
and switchedOn
messages to an object of the MyPrinter
class. After checking for respondsToSelector:
, we can send the loadPaper:
message as there must be definitions of the similar methods in its implementation. The declaration of an object of a class that supports a certain protocol is performed in as follows:
MyPrinter *printer; id anotherPrinter = [[MyPrinter alloc] init]; [anotherPrinter print]; //anonymous object responds to the message without compiler warning
Besides, one class can support several protocols. For this, we can enumerate them using commas in brockets in the class declaration.
@interface MyPrinterย : NSObject <MyPrinterProtocol, OtherProtocol>
We should write such line to declare an object of an unknown class (id) that corresponds to a certain protocol:
id <MyPrinterProtocol> somePrinter;
Exceptions in Objective-C
There are two main approaches for errors processing: global status variable, whose value informs about the success of execution of a previous operation, and generation of exceptions. Their main point is: the code where an error occured expects that this error can be solved by the code that called it. That is why it returns the control to this code and informs about the situation that happened in detail. Objective-C supports both these approaches. Exception is an object of a certain class. It carries certain information about the occured situation (even by its type). There is an NSException
class in Cocoa that can be initialized using two NSString
objects and one object of any class (id type):
- (id)initWitnName: (NSString *)name reason: (NSString *)reason userInfo: (id)userInfo;
We can generate exceptions and start the mechanism of unwinding of call stack with the help of the @throw
operator. To intercept the generated exception, we need to enclose the part of the code where generation is possible in a special block with a @try
heading (such blocks can be embedded). Then, after this block, we need to place a block with a @catch()
heading where we need to define the type of the supposed exception in round brackets. There can be several @catch()
blocks after the @try
block. After the exception generation, control unwinds stack and leaves the @try
block. Then it checks all @catch()
blocks one by one and gets into the @catch()
block in whose curly brackets the type, which the exception type is converted to indirectly (exact match, pointer to basic class or id), stands. If an exception by type did not match any @catch()
block, control continues the stack unwinding. If there is a block with @finally
heading after the block with a @try
heading, control is passed to it independently if exception occurred in the @try
block (and some @catch()
block is processed), or its last instruction is executed. Here is an example of work with an object of Cup
class (exception occurs in its fill
method):
Cup *cup = [[Cup alloc] init];
@try
{
[cup fill]; //an exeption of the NSException type is generated to fill
}
@catch (NSException *exception)
{//we log the occurred exception with the help of NSLog
NSLog(@"main: Caught %@: %@", [exception name], [exception reason]);
}
@finally //we get here after @try
{
[cup release];
}
In the @finally
block, it is good to release resources that are allocated in the @try
block but that are not released due to generated exception.
Properties
Our implementation of the Complex
class is redundant for Objective-C 2.0. There are a lot of access methods in it and their definition is a rut. Letโs rewrite it using the properties:
//โComplex.hโ file
#import <Foundation/Foundation.h> //for NSObject and NSString strings
@interface Complex : NSObject
{
double _re; //invariant for a real part
double _im; //invariant for an imaginary part
NSString *_format; //format string for the description method
}
- (id)initWithRe: (double)re andIm: (double)im;
+ (Complex *)complexWithRe: (double)re andIm: (double)im;
- (Complex *)add: (Complex *)other; //method for addition
- (Complex *)sub: (Complex *)other; //method for substraction
@property (nonatomic, retain) NSString *format; //we declare access methods
@property (nonatomic, assign) double re; //by means of declaration of properties
@property (nonatomic, assign) double im;
@end
//file โComplex.mโ
#import โComplex.hโ
@implementation Complex
@synthesize format = _format; //we generate access methods
@synthesize re = _re; //and rename them at the same time
@synthesize im = _im; //so that there is no underlining in the name
- (id)init
{
return [self initWithRe: 0.0 andIm: 0.0];
}
- (id)initWithRe: (double)re andIm: (double)im
{
if (self = [super init])
{
_re = re;
_im = im;
_format = @โre: %.1lf im: %.1lfโ; //output format by default
}
}
+ (Complex *)complexWithRe: (double)re andIm: (double)im
{
return [[[Complex alloc] initWithRe: re andIm: im] autorelease];
}
- (Complex *)add: (Complex *)other
{
return [[Complex alloc] initWithRe: _re + other.re andIm: _im + other.im]; //we use re and im properties
}
- (Complex *)sub: (Complex *)other
{
return [[Complex alloc] initWithRe: _re โ other.re andIm: _im โ other.im]; //we use re and im properties
}
@end
Property is a certain name that is available via the pointer to an object by means of point operator โ.โ. Properties are used instead of access methods to get or set the object invariant. A row of parameters that describe specifics of access methods generated by a property are defined during the declaration of a property.
getter=getterName
,setter=setterName
– Defines that the access method for reading will be calledgetterName
and for changing –setterName
readonly
โ Not to generate the access method for changingreadwrite
โ To generate both access methodsassign
โ To generate the access method for changing by means of simple assignmentretain
– To send the retain message to the accepting value; to send the release message to the previous invariant value and to assign the accepted value to itcopy
– To use a simple assignment operator but to assign the copy of the accepted value (before assignment, the copy message is sent to it)nonatomic
โ Not to use internal lockings for synchronization of several threads in generated access methods (synchronization is used by default)
Now, we do not need to write access methods manually in the definition of the Complex
class. They will be generated by a compiler and will be identical to those that were before.
And of course,this theoretical Objective-C quick tutorial is good, but still the best way to learn Onbjective-C as any other programming language is practice. So, install Xcode quicker and proceed to the xCode tutorial ๐