Bookmark and Share

Objective-C Programming Tutorial


SoBackSanA


Chapter 4. Categories and Protocols


4.1 Categories

A category collects method implementations into separate files. The programmer can place groups of related methods into a category to make them more readable. Furthermore, the methods within a category are added to a class at runtime. Thus, categories permit the programmer to add methods to an existing class without the need to recompile that class or even have access to its source code.
If a category declares a method with the same method signature as an existing method in a class, the category's method is adopted. Thus categories can not only add methods to a class, but also overrides existing methods. This feature can be used to fix bugs in other classes by rewriting their methods, or to cause a global change to a class behavior within a program. If two categories have methods with the same method signature, it is undefined which category's method is adopted.

So, Objective-C categories provide a means to extend a class by adding methods to a class as an alternative to subclassing while there are some side effects that the programmer should know about.



Here, I slightly modified the files( "Vector.h" and "Vector.m" ) we used in Chapter 3. Polymorphism and Dynamic Binding. I removed the "add:" method from the interface and implementation.

@Interface: <Vector.h>

#import <Foundation/Foundation.h>

@interface Vector: NSObject 
{ 
      double vec1;
      double vec2;
} 

@property double vec1, vec2;
-(void) print;
-(void) setVec1: (double) v1 andVec2: (double) v2;
@end 

@Implementation: <Vector.m>

#import "Vector.h"

@implementation Vector 

@synthesize vec1, vec2;

-(void) setVec1: (double) v1 andVec2: (double) v2
{ 
      vec1 = v1;
      vec2 = v2;
} 

-(void) print
{
      NSLog(@"(%g,%g)",vec1,vec2);
}

@end

Now, it's time to make our "Category".
Here, we are adding "sub:" method as well as the "add:" method which we removed from the interface.

The basic format for category is as below.

@interface ClassToAddMethodsTo (CategoryName)
  methods we want to add .....
@end

@Interface: <VectorMathOperations.h>

#import <Vector.h>

@interface Vector (MathOperations)
-(Vector *) add: (Vector *) v;
-(Vector *) sub: (Vector *) v; 
@end

@Implementation: <VectorMathOperations.m>

#import "VectorMathOperations.h" 

@implementation Vector (MathOperations) 

-(Vector *) add: (Vector *) v 
{ 
      Vector *result = [[Vector alloc] init];
      [result setVec1: vec1 + [v vec1] andVec2: vec2 + [v vec2]]; 
      return result;
}

-(Vector *) sub: (Vector *) v 
{ 
      Vector *result = [[Vector alloc] init];
      [result setVec1: vec1 - [v vec1] andVec2: vec2 - [v vec2]]; 
      return result;
} 
@end

The main program: <mainCategories.m>

#import "Vector.h"
#import "VectorMathOperations.h"
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) 
{	
      NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
            	
      Vector *vecA =[[Vector alloc] init];	
      Vector *vecB =[[Vector alloc] init];
      Vector *result;
	
      //set the values
      [vecA setVec1: 3.2 andVec2: 4.7];
      [vecB setVec1: 32.2 andVec2: 47.7];
	
      // print it
      [vecA print];
      NSLog(@" + ");
      [vecB print];
      NSLog(@" = ");
      result = [vecA add: vecB];
      [result print];

      [vecA print];
      NSLog(@" - ");
      [vecB print];
      NSLog(@" = ");
      result = [vecA sub: vecB];
      [result print];
	
      // free memory
      [vecA release];
      [vecB release];
      [result release];
	
      [pool drain];
      return 0;		
}

The output is:

(3.2,4.7)
 +
(32.2,47.7)
 =
(35.4,52.4)
(3.2,4.7)
 -
(32.2,47.7)
 =
(-29,-43)


4.2 Protocols

Through the introduction of protocols Objective-C was extended to introduce the concept of multiple inheritance. This is identical in functionality to an interface as in Java and C#, or a pure virtual class in C++.

A protocol, in Objective-C, is a list of method declarations that any class that wishes to adopt the protocol must implement.

Objective-C makes use of two types of protocols: one called informal protocols and the other one is compiler enforced protocols called formal protocols.

An informal protocol is a list of methods which a class can opt to implement. It is specified in the documentation, since it has no presence in the language. Informal protocols often include optional methods, where implementing the method can change the behavior of a class.

A formal protocol is a list of methods which any class can declare itself to implement. Versions of Objective-C before 2.0 required that a class must implement all methods in a protocol it declares itself as adopting; the compiler will emit an error if the class does not implement every method of its declared protocols. Objective-C 2.0 added support for marking certain methods in a protocol optional, and the compiler will not enforce implementation of optional methods.

Still having a hard time to get it?
Let's look into it with an example of copying an object using using the <NSCopying> Protocol .
As you already know, most classes are derived from the NSObject base class. The advantage of deriving new classes from NSObject is that those classes inherit a number of useful methods designed specifically for creating, managing and manipulating objects. Two such methods are the copy and mutableCopy methods. These methods use something called the <NSCopying> Protocol. This protocol defines what must be implemented in an object in order for it to be copyable using the "copy" or "mutableCopy" method.

We are going to use the same codes listed in the Section 4.1 Categories above.

Let's add some lines of code to "mainCategories.m" to copy vecB into vecC:

      Vector *vecC=[[Vector alloc] init];
      vecC = [vecA copy];
So, the file looks like this:

Main file: <mainProtocols.m>

#import "Vector.h"
#import "VectorMath.h"
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) 
{	
      NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
      Vector *vecA =[[Vector alloc] init];	
      Vector *vecB =[[Vector alloc] init];
      Vector *vecC=[[Vector alloc] init];	
      Vector *result;
	
      //set the values
      [vecA setVec1: 3.2 andVec2: 4.7];
      [vecB setVec1: 32.2 andVec2: 47.7];
	
      // print it
      [vecA print];
      NSLog(@" + ");
      [vecB print];
      NSLog(@" = ");
      result = [vecA add: vecB];
      [result print];
	
      [vecA print];
      NSLog(@" - ");
      [vecB print];
      NSLog(@" = ");
      result = [vecA sub: vecB];
      [result print];
	
      vecC = [vecA copy];
      [vecC print];	
	
      // free memory
      [vecA release];
      [vecB release];
      [result release];
	
      [pool drain];
      return 0;		
}

If we attempted to use either of these copying methods("copy"/"mutableCopy") on our class without implementing the <NSCopying> protocol the code will fail to run. If we were to create an instance of the class and then try to call the copy methods we get with a runtime similar to the following:

-[Vector copyWithZone:]: unrecognized selector sent to instance 0x1040b0
*** Terminating app due to uncaught exception'NSInvalidArgumentException',
reason: '-[Vector copyWithZone:]: unrecognized selector sent to instance 0x1040b0'

The reason for this error is that the copy and mutableCopy methods inherited from the NSObject class are trying to call "copyWithZone" method. Unfortunately we have not yet implmented this object in our Vector class. The next step, therefore, is to write such a class.

The first step in implementing the <NSCopying> protocol is to declare that the class conforms to the protocol. This is achieved in the @interface section of the class. For example:

@interface Vector: NSObject <NSCopying> 

Also in the implementation we need to declare that the class includes a method named "copyWithZone" that returns a new object and accepts the zone of the source object as an argument. The entire @interface section of our class will now read as follows:

@interface file: <Vector.h>
#import <Foundation/Foundation.h>

@interface Vector: NSObject 
{ 
	double vec1;
	double vec2;
} 

@property double vec1, vec2;
-(void) print;
-(void) setVec1: (double) v1 andVec2: (double) v2;
-(id) copyWithZone: (NSZone *) zone;
@end 

In our @implementation section we now need to write the code for our copyWithZone method. This method creates a new Vector object, copies the values of the instance variables (in this case vec1 and vec2) and returns a pointer to the new object:

@implementation file: <Vector.m>
#import "Vector.h"

@implementation Vector 

@synthesize vec1, vec2;

-(void) setVec1: (double) v1 andVec2: (double) v2
{ 
	vec1 = v1;
	vec2 = v2;
} 

-(void) print
{
	NSLog(@"(%g,%g)",vec1,vec2);
}

-(id) copyWithZone: (NSZone *) zone
{
	Vector *vectorCopy = [[Vector allocWithZone: zone] init];
	[vectorCopy setVec1: vec1 andVec2: vec2];
	return vectorCopy;
}
@end

The output is:

(3.2,4.7)
 +
(32.2,47.7)
 =
(35.4,52.4)
(3.2,4.7)
 -
(32.2,47.7)
 =
(-29,-43)
(3.2,4.7)

As you see from the last line of the output which is vecC, we copied an object successfully using "copyWithZone" Method.



SoBackSanB