⬆️ ⬇️

Objective-C from scratch

Anyone who wants to write programs for Apple products comes to a point when he has to learn a new programming language - Objective-C. Once this happy moment befell me. And in order to better remember the main features of this language, I decided to inspect my thoughts while thinking about its documentation, which I share with you.



Banal theory of OOP



The problem of reuse of written code and its portability constantly forces programmers to look for new ways to organize, structure and abstract it. To solve these problems, new programming paradigms, design patterns, new languages, compilers and standard libraries for them, software platforms and frameworks are created. This formed the paradigm of subroutines (procedures) implemented using the CALL \ RET processor commands and the stack (in fact, transferring the flow of execution to an arbitrary address rather than following the current command, followed by a return). Then, the module paradigm (each file is a separate translation unit), which gave rise to a two-stage translation: compilation of modules, and then their layout (static or dynamic) into an executable module.



As a result of the increase in the amount of code in projects and the complexity of its support, from the 1960s a new, object-oriented programming paradigm began to form, breaking up programs into even smaller components - data types. Its essence lies in the interaction of entities (objects) by sending messages to each other. Each object is a variable of a data type defined by the programmer (the so-called class). The definition of such a special user data type (class) consists of two things: the definition of a data set (invariants, members) and a set of subroutines (methods) that will serve them.





A class is usually designed as a programmer-defined type based on built-in (language) data types and / or other classes. For a C language that does not support an object-oriented paradigm, this may be a structure (struct). A set of subroutines is implemented as normal functions, necessarily taking at least one parameter - a pointer to a set of data to be processed.

')

The main advantage of the object-oriented approach was the ability to create new classes based on those already written (add invariants and methods, redefine methods, use methods defined in the base class as your own), called inheritance.





A set of methods is an interface for interacting with invariants. The impossibility of directly modifying class data (without using its interface) reflects the principle of encapsulation. The figure shows the class and its objects. There is an x ​​invariant of type float and an interface (method) doubleX to it, which returns the value of the invariant.



It happens that it is necessary to send a message to an object that definitely responds to it (that is, to call such an object for the class object that it has implemented), but, in a situation, the specific class of this object is unknown. For example, each element of the list of pointers to objects of class Auto needs to send a message Move, and it is known that the list contains pointers to objects not only of class Auto, but also pointers to derived (inherited) classes Ford and Subaru. This can be done only due to the principle of polymorphism, which means that when a certain message is sent to an object from a certain class hierarchy, in which all objects are able to receive such a message, this object responds to it according to its own class rather than the base one.



The first language with the support of the object-oriented approach was Simula67. Then came the Smalltalk. And in the 80s C ++ began to take shape - the main language of modern system programming. Its expansion and improvement in the 90s gave rise to a number of paradigms and design patterns, and had an irreversible influence on the modern vision of the object-oriented approach, including the Objective-C language.



A little bit of history



Objective-C appeared in the 80s as a modification of C in the direction of Smalltalk. Moreover, this modification consisted in the addition of new syntactic constructions and a special preprocessor for them (which, passing through the code, converted them into ordinary calls of C functions), as well as a runtime library (these calls are processing). Thus, initially Objective-C was perceived as an add-on over C. In a sense, this is still true: you can write a program in pure C, and then add to it some constructs from Objective-C (if necessary), or on the contrary, it is free to use C in programs on Objective-C. In addition, this also applies to C ++ programs. In 1988, NeXT (and later Apple) licensed Objective-C and wrote the compiler and the standard library for it (essentially the SDK). In 1992, the developers of the GNU project as part of the OpenStep project joined the improvement of the language and compiler. Since then, GCC has supported Objective-C. After buying NeXT, Apple took their SDK (compiler, libraries, IDE) as the basis for their further development. IDE for code was called Xcode, and for GUI - Interface Builder. Cocoa development framework for GUI development (and not only) today is the most significant development environment for Objective-C programs.



Objective-C Features



Module files in Objective-C have the extension “.m” (if a mixture of C ++ and Objective-C was used, then the extension is “.mm”). Header files are “.h”. All objects created in Objective-C should be placed in dynamic memory. Therefore, the id type, which is a pointer to an object of any class (essentially void *), takes on particular importance. A null pointer is called a constant nil. Thus, a pointer to any class can be cast to type id. The problem arises: how to find out what class the object hiding under id belongs to? This is due to the isa invariant, which is present in any object of the class that inherited the special base class NSObject (the NS prefix denotes NeXT Step). The isa invariant is of the reserved type Class. An object of this type allows you to find out the names of your own and base class, a set of class invariants, as well as prototypes of all the methods that this object has implemented and their addresses (through a local list of selectors). All Objective-C reserved words that differ from C reserved words begin with an at sign (for example, @protocol, selector , interface ). Typically, the names of invariants of classes with limited scope (@private, protected ) begin with an underscore. Cocoa has very convenient NSString class for strings. The string constant of this class is written as @ ”Hello world”, and not as the usual string “Hello world” for C. The type BOOL (essentially unsigned char) can take constant values ​​YES and NO. All special Objective-C reserved words (which are different from C and are found in the objc / objc.h header file) are listed below:



Message exchange



To force an object to perform some method, it is necessary to send a message to it, which is named the same as the required method. This message is called a method selector. The parcel syntax is:



[receiver method]; 




In the message, you can pass parameters for the called method:



 [receiver method: 20.0 : 30.0]; 


Before each parameter it is necessary to put a colon. How many colons - so many parameters. The method name can continue after each such parameter colon:



 [receiver methodWithFirstArgument: 10 andSecondArgument: 20]; 


Methods with unlimited number of arguments are invoked with the following syntax:



 [receiver undefinedNumberParameters: one, two, three, four, five, six, seven]; 


Sending a message, like any C function, returns a specific (maybe void) value:



 BOOL booleanValue; booleanValue = [reveiver method]; 


When sending a nil message, it just disappears. When a message is sent to an object that belongs to a class that does not implement the ordered method, an exception is thrown which, if not intercepted, leads the entire program to an unplanned termination. You can use the following code template to check whether this object responds to any message:



 if ([anObject respondsToSelector: @selector(myMethodWith2Argumets::)]) { //  [anObject myMethodWith2Argumetns: @”first” : @”second”]; } else { //      } 




How messaging works



Sending a message is translated to the C-function with a prototype:



 id objc_msgSend(id receiver, SEL method, ...); 


The SEL type is essentially defined as char const *, but it’s better to take it as an int, because at run time all selectors are indexed by integer values ​​according to the global selector table.





Using the isa invariant of the receiver object (when using the Foundation framework based on Cocoa, all classes must inherit the NSObject class, therefore the presence of isa is unavoidable), this function searches the local list of class selectors to determine if the object of this class responds to the message method. If such a selector is found, then control is transferred to the appropriate class method, to which the object id is passed (a pointer to its invariants) and the parameters of the objc_msgSend () function specified after the selector. The value returned by the method is given as the result of the message being sent. If the selector object does not have this selector, the objc_msgSend () function scans the list of selectors of its base class.





In this scheme, the call, for example:



 [receiver ddObject: otherObject]; 


Broadcast to:



 objc_msgSend(receiver, 12, otherObject); 


As in the global selector table 12 corresponds to the line “addObject:”. Next, the objc_msgSend () function searches the selector list of the receiver object and, finding it (even if it is an object of the NSArray class that implemented the method with the selector 12), makes a call like:



 addObject(receiver, otherObject); 




Method declaration



It is interesting to note that the prototype of the addObject method from the previous section in the class declaration looked like this:



 - (void)addObject: (id)otherObject; 


That is, took only one parameter. But, based on the principle of the object-oriented paradigm that methods are subroutines that process certain data sets, the address of the data to be processed must be passed to the method. Therefore, such a parameter is passed to any class method implicitly. The compiler makes this additional parameter understand the minus ("-"), which is the first in the prototype of the method. Such a method (with a minus in front) is called an object (or instance) method, since can be called only for an object of some class. In the body of the method, this pointer to the data instance (or the address of the object to which the message was sent) is accessible by means of the reserved word self (the same as this in C ++), and the pointer to the base class instance via the reserved word super. In addition, the implicit parameter _cmd is also passed to the object method — a selector of this method from the global selector table. From the point of view of the C ++ programmer, all methods of objects in Objective-C are as if declared with the virtual keyword, and always follow dynamic polymorphism.



If at the beginning of the prototype method, put a plus sign (“+”), then this method will be considered a class method, and, naturally, will not accept the implicit self parameter (this is similar to declaring a static method in C ++). And without the isa invariant of the object pointed to by self, the super pointer, of course, will not work either.

Thus, the prototype of any method is declared as follows:



 -|+ (<  >)  [ : (<  >) [ [] : (<  >)] … ] 


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 an object (type id) or a class (type Class), you can use the nested call syntax:



 [myLabel setText: [[NSString stringWithString: @”Hello”] stringByAppendingString: @” world”]]; 


Here, the UILabel class object from the UIKit framework is set to the text invariant equal to the string @ ”Hello world”. This string, in turn, is formed by the concatenation of the @ ”Hello” and @ ”world” strings. The first is the result of sending the stringWithString message to the NSString class with the constant parameter @ ”Hello”. Such a call returns an object of class NSString, initialized with a string-parameter. Then a stringByAppendingString message with the @ ”world” parameter is sent to this object. The result of sending this message is an object of the NSString class containing the concatenation of the value of the receiver object and a string argument. This object is passed as a parameter to the setText: message of the myLabel object.



Class declaration



Let's declare a simple class of a complex number in the Complex.h file:



 #import <Foundation/Foundation.h> // NSObject   NSString @interface Complex : NSObject { double _re; //    double _im; //    NSString *_format; //    description } - (id)initWithRe: (double)re andIm: (double)im; //  + (Complex *)complexWithRe: (double)re andIm: (double)im; //      - (Complex *)add: (Complex *)other; //   - (Complex *)sub: (Complex *)other; //   - (NSString *)format; //   _format - (void)setFormat: (NSString *)format; //  _format - (double)re; //        - (void)setRe: (double)re; - (double)im; - (void)setIm: (double)im; @end 


As you can see, the entire declaration is enclosed in the interface and end keywords . First, invariants are declared (in curly braces). Outside braces are declared methods. The description method is missing in the class declaration is not accidental. The fact is that it, like the dealloc and init methods, is present in the class definition. When sending a description message to the Complex object of the Complex class, its local list of selectors will be considered, where, after compilation, the selectors of all the methods implemented by the class of this object will fall, not even declared in the interface part. That is, init, description and dealloc will call absolutely correctly.



Creating objects



Due to the fact that all objects are distributed in dynamic memory, the creation of an object has to be carried out in two stages: 1) memory allocation (message alloc) and 2) initialization of invariants (class constructors).



 MyClass *myObject = [[MyClass alloc] init]; //  MyClass alloc          ,   init    myObject 


After creating an object, you can safely use it:



 NSMutableArray *array = [[NSMutableArray alloc] init]; //   MyClass *myObject = [[MyClass alloc] init]; //  [myObject myMethod]; //   [array addObject: myObject]; //    MyClass *otherObject = [array getLastObject:]; //   ,      [otherObject myOtherMethod: YES]; //       BOOL 


Some classes have a method for quickly (in one step) creating their own instances. Such methods are class methods, return a pointer to an object of its class, and their name usually begins with the name of the class itself. For example, the method:



 + (NSString *)stringWithCString: (char const *)string encoding: (NSStringEncoding)encoding; 


Returns a ready-made string initialized by the corresponding line with a terminating zero, without calls to alloc and init:



 NSString *myString = [NSString stringWithCString: “Bla-bla-bla” encoding: NSASCIIStringEncoding]; 




Object lifetime



As soon as a pointer to an object goes beyond its scope, the memory allocated for it is irretrievably lost (unless, of course, it was the last pointer to that object) and a leak occurs. In order to avoid such undesirable consequences in Objective-C, the paradigm of resource reference counting is supported. Thus, each object has an integer counter, which shows the number of pointers referring to it. When this counter reaches zero, the memory allocated for this object is returned to the system. After calling the class's alloc method, this counter is one. To increase its value, you need to send a retain message to the object, and to decrease - release. All these methods are implemented by NSObject, which any of our classes inherit by all means. It is interesting to note that the counter value for static objects of the NSString class (for example, @ ”I am a string”) is -1, that is, the maximum possible. Here is an example of working with a meter:



 id anObject = [SomeClass alloc]; //  == 1 [anObject init]; //    [anObject reatin]; //   (  == 2) [anObject release]; // (  == 1     ) [anObject release]; // ,   1          


The init implementation is very important. This is a class constructor. Constructors differ in that they return id and their names always begin with the word init, and the default constructor is just init. The scheme of any constructor is approximately as follows:



 - (id)init { self = [super init]; //     //   if (self) //        //    ,      nil { //       } return self; //    } 


Here is a typical specialized (not default) constructor for a class with two members of the type of a certain class and one integer invariant:



 - (id)initWithInt: (int)number { if (self = [super init]) { _myMember1 = [[SomeClass alloc] init]; //  :  ,    _myMember2 = [[SomeClass alloc] init]; _myIntMember = number; //     //   } return self; } 


The implementation of release and retain for NSObject is ideologically approximately the following, and it does not need to be redefined in derived classes, due to the lack of access to the reference counter invariant:



 - (void)retain { [_internalLock lock]; //   _referenceCounter++; //  _referenceCounter –    [_internalLock unlock]; } - (void)release { [_internalLock lock]; _referenceCounter--; //  if (!_referenceCounter) //    { [_internalLock unlock]; [self dealloc]; // ,    (  ) } [_internalLock unlock]; } 


That is, the object itself is sent a message dealloc, in the implementation of the method of which it can, if necessary, reduce the counters of its invariants and transmit a similar message to the base class object so that it does the same. Obviously, the implementation of the dealloc method for NSObject will free up memory allocated to the object. Usually dealloc for any class looks like this:



 - (void)dealloc { [_myMember1 release]; //    [_myMember2 release]; //     //[_myIntMember release];   , ..           [super dealloc]; //c   ,     } 




Access methods



Proper work with reference counting is very important when returning an object address from a method or invariant initialization with a formal parameter. Usually, the so-called access methods, which return and set object invariants, deal with such things. It is customary to call the method that returns the value of the invariant, as well as the invariant, and the name of the method that sets its value to begin with the word set:



 - (void)setRe: (double)re { _re = re; } 


Since the _re invariant refers to the built-in type, there are no difficulties with changing its value. But if an invariant is an object of a certain class, then simple assignment is indispensable, since reference counters must be taken into account. To solve this problem, the following three methods are used:



 //,      [label setText: @”Hello world”]; //  text // label     NSString * //  setText   UILabel ( №1) - (void)setText: (NSString *)text { [text retain]; //      [_text release]; //       _text _text = text; //    } //  setText   UILabel ( №2) - (void)setText: (NSString *)text { if (_text != text) //c    { [_text release]; //     //  _text _text = [text retain]; //   //       } } //  setText   UILabel ( №3 – ) - (void)setText: (NSString *)text { if (_text != text) { [_text autorelease]; // e   // _text    _text = [text retain]; //   //       } } 


Option number 3 is not very good because it clogs the current self-unloading pool, and usually this is not very desirable (see the next section).

The access method for reading the value of an invariant is always very simple:



 - (NSString *)text { return _text; } 




Self-unloading pool in program threads



Now let's try to return from the method an object created inside it:



 -(NSString *)sayHelloToName: (NSString *)name withSurname: (NSString *)surname { NSString *retString = [[NSString alloc] initWithFormat: @”%@ %@!”, name, surname]; //      return retString; } 


The format string conforms to the C standard. But if it is necessary to specify the type id in it, the format specifier% @ is used. How does the method that parses the format understand which characters to substitute with id? It will simply substitute what the method of the description of this object will return. This method is originally declared for the class NSObject. NSString redefined it to print its string content. By redefining it, any object can represent its string content. For example, this is how a class number with two double invariants can make it:



 - (NSString *)description { return [NSString stringWithFormat: @”re: %lf im: %lf”, _re, _im]; //  @“re: 1.0 im: 2.5”  _re == 1.0  _im == 2.5 } 


After the sayHelloToName: withSurname method is executed, a memory leak will definitely occur, since the calling code most likely does not realize that the returned object needs to be sent a release message after processing. Even if he guesses to do this, it is possible that the pointer to the object invariant was returned, which means its destruction is fraught with serious consequences. I would like to have a mechanism for the self-release of objects ever in the future, so that the user code does not even think about their release. This problem is solved by using an object of the NSAutoreleasePool class - a self-unloading pool of objects.



After creating an object of this class, all objects created after it can be sent autorelease messages. In this case, this object is placed in the current (last created) self-unloading pool. When a pool receives a release message, it will send the same message to all its objects, reducing their reference count (in effect, destroying). In this way. An object placed in a self-unloading pool continues to live and occupy memory during the entire lifetime of the pool. This is convenient for small temporary objects, but may over time take up a significant amount of memory. Therefore, it is recommended that cycles capable of generating a large number of temporary objects that are sent to a self-unloading pool be framed with local (nested) pools.



Any thread in a program that uses Cocoa should create an object of the NSAutoreleasePool class at the very beginning (before creating other objects), and at the very end destroy it (after destroying all other objects). The main () function, which is the main thread of any program on Objective-C, when using the Cocoa framework, should always look like this:



 int main(int argc, char *argv[]) //    main() { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // ,     int retVal; //    [pool drain]; //    ,     autorelease return retVal; } 


And the correct method sayHelloToName: withSurname: will now look like this:



 -(NSString *)sayHelloToName: (NSString *)name withSurname: (NSString *)surname { NSString *retString = [[NSString alloc] initWithFormat: @”%@ %@!”, name, surname]; //      [retString autorelease]; //  ,  retString     return retString; } 


By the way, the drain method of the self-unloading pool is similar to release with the only difference that, in addition to freeing itself and all the contained objects, it also gives a hint to the garbage collector to enter the game. However, this is only relevant for Mac OS 10.4 and higher, since there is no garbage collection on iOS.



Class definition



Now consider the Complex.m file with the definition of the 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”; //    } } + (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 {//    - [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 {//    return [NSString stringWithFormat: _format, _re, _im]; } - (void)dealloc { [_format release]; //    dealloc [super dealloc]; } @end 


The default constructor invokes a specialized constructor with certain initial parameters. The complexWithRe: andIm: method returns an initialized object of the Complex class placed in the current self-unloading pool. The description method does the same thing, returning an object of class NSString. Here is an example program where the Complex class is used:



 #import “Complex.h” #import <stdio.h> // 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]); //> re: 1.5 im: -2.0 printf(“%s\n”, [[[num2 add: num3] description] cStringUsingEncoding: NSASCIIStringEncoding]); //> re: 6.5 im: 5.0 [num1 setRe: [num2 re]]; // _re  num1   num2 [num1 setIm: [num3 im]]; // _im  num1   num3 [num1 setFormat: @”%.2lf+%.2lf*i”]; //    num1 printf(“%s\n”, [[num1 description] cStringUsingEncoding: NSASCIIStringEncoding]); //> 1.50+7.00*i [num1 release]; [num2 release]; //[num3 release];  , ..      [pool drain]; return 0; } 




Categories and Extensions



If you need to add / redefine some methods without inheritance to an already written (and, possibly, compiled) class, categories allow you to do it with little effort:



 // “CategorizedComplex.h” #import “Complex.h” @interfce Complex (CategorizedComplex) - (Complex *)mul: (Complex *)other; - (Complex *)div: (Complex *)other; @end // “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 you can use it like this:



 CategorizdComplex *num1 = [[CategorizedComplex alloc] initWithRe: 1 andIm: 999]; Complex *num2 = [Complex complexWithRe: 0 andIm: 0]; CategorizedComplex *num3 = [num1 div: num2]; //num3 == nil 


Extensions do good service as unnamed categories:



 // “CategorizedComplex.m” #import “CategorizedComplex.h” @interface Complex () - (void)zeroComplex; //     @end @implementation Complex - (void)zeroComplex //       { _re = 0; _im = 0; } @end 




Protocols



The Objective-C protocol is a formalized declaration of a group of methods that, if desired, can be implemented by any class (analogous to a class in C ++, where all methods are declared with the virtual… = 0 specifier). In the 2.0 version of the language, protocol methods can be required (@required specifier, it is considered silent) and selective (@optional specifier). If any class has implemented the required protocol methods, then it is called a class that supports this protocol. The protocol, and the class that supports it, are declared like this:



 @protocol MyPrinterProtocol @required - (void)print; - (BOOL)switchedOn; @optional - (void)loadPapaer: (int)numberOfPages; @end @interface MyPrinter : NSObject <MyPrinterProtocol> // MyPrinter   MyPrinterProtocol { BOOL _state; int _numberOfPages; } - (id)initWithState: (BOOL)state andPagesCount: (int)pages; - (BOOL)state; @end 


The MyPrinter class object is guaranteed to send print and switchedOn messages, and, after checking the respondsToSelector: message, you can send a loadPaper message, as its implementations must contain definitions of the same methods. The declaration of an object of a class that supports any protocol is as follows:



 MyPrinter *printer; id anotherPrinter = [[MyPrinter alloc] init]; [anotherPrinter print]; //        


In addition, one class can satisfy several protocols. To do this, you can list them separated by commas in angle brackets in the class declaration.



 @interface MyPrinter : NSObject <MyPrinterProtocol, OtherProtocol> 


And in order to declare an object of an unknown class (id) that corresponds to a certain protocol, write this:



 id <MyPrinterProtocol> somePrinter; 


Exceptions



There are two main approaches to error handling: a global status variable, the value of which indicates the success of the previous operation, and the generation of exceptions. The essence of both is that the code in which the error occurred hopes that the code that caused it will be able to solve it, so it returns control to it, reporting the situation as much as possible. Objective-C supports both of these approaches.



An exception is an object of some class. He (even his type) carries some information about the situation. For convenience, Cocoa has an NSException class, which can be initialized with two NSString objects and one object of an arbitrary class (type id):



 - (id)initWitnName: (NSString *)name reason: (NSString *)reason userInfo: (id)userInfo; 


You can generate an exception and, thus, launch the call stack promotion mechanism using the @throw operator. To grab the generated exception, the code section where it can be generated must be enclosed in a special block with the name try (such blocks can be nested). And then, after this block, put a block with the name catch (), where in parentheses indicate the type of the proposed exception. There can be several catch () blocks after a try block . After the exception is thrown, the control spins the stack out of the try block and, checking all the catch () blocks in turn , falls into that catch block(), in curly brackets of which there is a type to which the type of the exception is implicitly given (exact match, pointer to base class or id). If the exception type does not coincide with any catch () block , management continues to promote the stack. If after the block with the try name there is a block with the name finally , then control will be transferred to it regardless of whether an exception occurred in the try block (and some catch () block was processed ), or its last instruction was executed. The following is an example of working with an object of the class Cup in the fill method of which an exception occurs:



 Cup *cup = [[Cup alloc] init]; @try { [cup fill]; // fill    NSException } @catch (NSException *exception) {//     NSLog NSLog(@"main: Caught %@: %@", [exception name], [exception reason]); } @finally //  @try    { [cup release]; } 


In the finally block it is convenient to free the resources allocated in the try block , but not freed because of the generated exception.



Properties



For Objective-C 2.0, our implementation of the Complex class is clearly redundant: there are too many access methods and their definition is a complete routine. Rewrite it using properties:



 // “Complex.h” #import <Foundation/Foundation.h> // NSObject   NSString @interface Complex : NSObject { double _re; //    double _im; //    NSString *_format; //    description } - (id)initWithRe: (double)re andIm: (double)im; + (Complex *)complexWithRe: (double)re andIm: (double)im; - (Complex *)add: (Complex *)other; //   - (Complex *)sub: (Complex *)other; //   @property (nonatomic, retain) NSString *format; //   @property (nonatomic, assign) double re; //   @property (nonatomic, assign) double im; @end // “Complex.m” #import “Complex.h” @implementation Complex @synthesize format = _format; //   @synthesize re = _re; //    @synthesize im = _im; //      - (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”; //    } } + (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]; //  re  im } - (Complex *)sub: (Complex *)other { return [[Complex alloc] initWithRe: _re – other.re andIm: _im – other.im]; //  re  im } @end 


A property is a certain name accessible through a pointer to an object by means of the dot operator “.”. Properties are used instead of access methods to get or set an object invariant. When a property is declared, a number of parameters are specified that describe the features of the access methods generated by the property.

Now in the definition of the class Complex, we do not need to manually write access methods. They are generated by the compiler and will be identical to those that were before.



Good luck!

Source: https://habr.com/ru/post/107126/



All Articles