Many object oriented programming languages, have a class, from which all other classes extends.
In general, a class has a set of methods, and variables, which it defines. The variables being the data, and the methods, being the operations, that can be performed over the data.
Data and methods, can belong to the class itself, so they are applied on the class level, or they can belong, to what is called, an instance of the class.
A class can have multiple instances created from it, each having its own data values, and each capable of using the methods, that the class has defined for it. Additionally an instance can call, by referring to the class, the class methods, and access its data, if the class allows that.
This being said, if a class extends another class, the data and methods, which are defined on the class that is being extended, become part, of the extending class. So the extending class, will have the other class, class methods and data, and the methods and data, which the extended class, defines for its instances, will also be defined for the instances of the extending class.
Java has for every top level class, a class, that does not have a parent, the Object
class as its parent, whereas for C++, this is not the
case, but you can explicitly create a set of classes, and their descendants, having a given
class, that you define, as their ancestor class.
For objective-C, not each class, is made to be implicitly descendant, of a base class, as in java , as an example, a class, which does not have any parent:
// NoRoot.h #import <Foundation/Foundation.h> @interface NoRoot + (void ) initialize; + (void ) print; @end // NoRoot.m #import "NoRoot.h" static int x = 1 ; static int y = 2 ; @implementation NoRoot + (void )initialize {} /* The initialize method, is called by the runtime, before any method of the class, or of one of its instances is called . This is done only once, for each class .*/ + (void ) print{ printf ("NoRoot x is : %d\n" , x ); printf ("NoRoot y is : %d\n" , y );} @end // main.m #import "NoRoot.h" int main (int argc, const char* argv[ ] ){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc ] init ]; [NoRoot print ]; [pool release ]; return 0; } /*Output: NoRoot x is : 1 NoRoot y is : 2 .*/
This being said, in objective-C, there are some classes, which do not have any parents, and
from which many other classes derive, these classes are called root classes. An example of a root class, is the
NSObject
class, of which all the classes of cocoa, and
cocoa touch frameworks, inherits.
Having a root class, allow certain operations, to be performed, over all the objects, as in checking for their equality, or allocating memory.
This being said, one must ask, what kind of
operations, the NSObject
class allows, or opens
up for grab?
Well first of all, NSObject
, defines methods related to
memory management. So you have the alloc
method, which allows memory to be allocated for an
object, the init
method, which allows an object memory, to
be initialized to certain values, and the new
method,
which is basically a call to alloc
, followed by a call to
init
, so it is as if, doing both operations, using one
function.
@interface NSObject ... + (id) alloc; /* Class method to allocate memory .*/ - (id) init; /* instance method to initialize memory .*/ + (id) new; /* Basically a combination of a call to alloc, and a call to init .*/ - (id) copy; /* Copy method, allows copying an object .*/ ... @end
Some methods implemented by NSObject
, are actually just
stubs, so they provide a mock implementation, of an
operation, as in, the copy
method, implemented by NSObject
, just returns self
, so it does not do any copying, and hence the idea,
that it is up to the developer, to provide the actual implementation of the method,
depending on the kind of data.
Having allocated memory, memory must be freed. In
C
, this can be done, by calling the free
method, so in other words, it is up to you, to do it.
In java there is garbage collection, so you do not need to worry, about freeing memory .
In objective-C, for a short period, there was also garbage collection, as in java, but this was deprecated, and removed starting macOS sierra.
Still, in objective-C, you are not the one , who is responsible for directly making the call, to free up memory, but what is used instead, is reference counting.
Reference counting, is basically counting the number of references, to an object, and if the number of references reaches zero , the object reserved memory is freed.
This was originally done, using a manual reference counting scheme, or algorithm, so it was you, as a developer, who was responsible, for incrementing and decrementing, the count of references to an object.
How is this going to happen, you might ask!? well
simply, the NSObject
class, has some methods , which
allowed to increment, and decrement, the number of references to an object.
@interface NSObject ... - (id) retain; // increment an object reference count. - (oneway void) release; // decrement an object reference count. - (id) autorelease; /* In simple terms, you are asking the count decrement, to be managed, and count decrement will happen, when a call to the manager of the count, is made .*/ - (NSUInteger) retainCount; /* Returns the number of reference count for an object. This method should not be used .*/ - (void) dealloc; /* Once count reaches 0, the dealloc method is called. As with some other methods of NSObject, this method is called automatically by the runtime. In other words, the runtime will call some methods on the class, and on its instances automatically, so it is like certain aspects of the code, is being managed by the runtime .*/ ... @end
Once the number of references reaches 0, an object dealloc
method is called, hence this gives a chance to the
object, to decrement the count of other objects which it has retained, in other words to
which it has incremented, their reference count.
After that dealloc
is called, the object reserved memory is
freed, so the system can use it for other purposes.
Automatic reference counting, came up later on, and
when it is being used, incrementing and decrementing the count of references, to an object,
is done by the compiler, so you should not be calling, retain
or release
.
The NSObject
class, is
declared in the NSObject.h
header file, and is
part of the foundation framework. Additionally the NSObject.h
header file, defines the NSObject
protocol.
A protocol in objective-C, if you come from the java world, is more or less, a java interface. Basically a protocol, is just a set of functions, that must be defined, by the implementing class.
This being said, the NSObject
class, implements the NSObject
protocol, and additional methods, so it defines all
the methods, declared in the NSObject
protocol, in order
to have all objects, perform certain kind of operations.
All the methods described earlier, besides copy
, are part
of the NSObject
protocol. This protocol, additionally
defines method, for object introspection.
- (Class ) class; /* returns the Object class .*/ - (Class ) superclass; /* returns the Object superClass .*/ - (BOOL ) isMemberOfClass :(Class )aClass; /* returns true, if this object, is a direct child of aClass .*/ - (BOOL ) isKindOfClass :(Class )aClass; /* returns true, if this object, is a descendant of aClass .*/ - (BOOL ) isProxy; /* returns true, if the class is not a descendant, of NSObject .*/ - (BOOL ) respondsToSelector :(SEL )aSelector; /* returns true, if the object implements the selected, or specified method .*/ - (BOOL ) conformsToProtocol :(Protocol* )aProtocol; /* returns true, if the object implements the specified protocol .*/ - (id ) self; /* Returns a reference to the object */
An example of the previous explained methods, is :
#import <Foundation/Foundation.h> int main (int argc, const char* argv [ ] ){ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc ] init ]; //Example class method NSObject* nsobject = [[NSObject alloc ] init]; NSLog ( @"nsobect class is : %@", [nsobject class ]); // .. nsobect class is : NSObject //Example superclass method NSString* ns_string = @"hey!"; NSLog ( @"ns_string super class is : %@", [ns_string superclass ]); // .. ns_string super class is : NSMutableString //Example isMemberOfClass if ([ns_string isMemberOfClass :[nsobject class ]] == NO ) NSLog ( @"ns_string member of NSObject? N" ); // .. ns_string member of NSObject? N //Example isKindOfClass if ([ns_string isKindOfClass :[nsobject class ]] == YES ) NSLog ( @"ns_string kind of NSObject? Y" ); // .. ns_string kind of NSObject? Y //Example isProxy if ([ns_string isProxy ] == NO ) NSLog ( @"ns_string is proxy ? N" ); // .. ns_string is proxy ? N // respondsToSelector example if ([ns_string respondsToSelector :@selector(isMemberOfClass: )]) NSLog ( @"ns_string has the method isMemberOfClass? Y" ); // .. ns_string has the method isMemberOfClass? Y // conformsToProtocol example if ([nsobject conformsToProtocol :@protocol(NSObject )]) NSLog ( @"nsobject conforms to the NSObject protocol? Y" ); // .. nsobject conforms to the NSObject protocol? Y // self method example NSLog ( @"%@" , [nsobject self ] ); // .. <NSObject: 0x10010c5c0> [pool release ]; return 0; }
Additionally, the NSObject protocol , defines methods, which allow instances to perform, equality, hashing and description.
- (BOOL ) isEqual :(id )anObject; /* True, if two objects are equal. The default implementation, checks if two objects, have the same reference .*/ - (NSUInteger ) hash; /* If two objects are equal, the hash method, must return the same integral value .*/ - (NSString *) description; /* A String describing an instance .*/
As an example,
#import <Foundation/Foundation.h> int main (int argc, const char* argv[ ] ){ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc ] init ]; NSObject* object = [NSObject new ]; NSLog (@"Object description : %@", object ); // .. Object description <NSObject: 0x10010c5c0> // when using @ , object is printed, // as the string returned by descriptionWithLocale // if defined, or else by the description // method . NSLog (@"Object hash : %lu", [object hash ]); // .. Object hash : 1099200 if ([object isEqual :object ]) NSLog (@"object == object ? Y" ); // .. object == object ? Y [pool release ]; return 0; }
Finally, imagine that a class instance, acts as a service provider, like for example, one of its method, do downloading in the background. This method is passed an object, and is asked to call a method, that this object has defined, once it has done its tasks, how should this be possible, you might ask?
Well simply, the NSObject
protocol, defines the following methods:
- (id ) performSelector :(SEL )aSelector; /* call the selected method on the object .*/ - (id ) performSelector :(SEL )aSelector withObject:(id )anObject; /* Call the selected method on the object, passing one argument .*/ - (id ) performSelector :(SEL )aSelector withObject:(id )object1 withObject:(id )object2; /* Call the selected method on the object, passing two arguments .*/
So once your service has finished performing its tasks, having the passed object, and the
selected method which is to be called, it can perform the selected method, on the passed
object, using the performSelector
method. As an example :
#import <Foundation/Foundation.h> int main (int argc, const char* argv[ ] ){ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc ] init ]; NSObject* object_1 = [NSObject new ]; SEL eq_selector = @selector (isEqual: ); if ([object_1 respondsToSelector :eq_selector ]) { if ([object_1 performSelector :eq_selector withObject:object_1 ] ) NSLog(@"object_1 == object_2 ? Y" );} // .. bject_1 == object_2 ? Y [pool release ]; return 0;}
For a root class, so a class, which does not have
any parent, the objective-C runtime, additionally to having methods defined for an instance,
be defined on that instance, has these methods, also defined for the class itself, and for
its sub classes. So for the NSObject
class, methods which
are defined for its instances, can also be accessed, or called from the NSObject
class itself, or from the classes that extends it.
#import <Foundation/Foundation.h> @interface Test + (void)initialize; - (void ) print_test; @end @implementation Test + (void)initialize{} - (void ) print_test{ printf("print_test, Instance method\n");} @end @interface Test_sub :Test - (void ) print_test_sub; @end @implementation Test_sub - (void ) print_test_sub{ printf("print_test_sub, Instance method\n");} @end @interface Test_sub_sub :Test_sub @end @implementation Test_sub_sub @end int main (int argc, const char* argv[ ]){ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc ] init ]; [Test print_test ]; //print_test, Instance method [Test_sub print_test ]; //print_test, Instance method //[Test_sub print_test_sub ]; /* The commented code above, will cause an error, since Test_sub is not a root class, it does not have its instance methods, defined for it. */ //[Test_sub_sub print_test_sub ]; /* Test_sub_sub is not an instance of Test_sub, hence the commented call above will also cause an error .*/ [pool release ]; return 0; }
Finally, as explained earlier on, the NSObject
class,
defines additional methods ,
to those required by the NSObject
protocol, so for example
it defines methods for archiving objects, which is like encoding an object, and storing it,
and recreating it afterwards.