When you created the BNRPerson class, you declared it to be a subclass of NSObject. This means that every instance of BNRPerson will have the instance variables and methods defined in NSObject as well as the instance variables and methods defined in BNRPerson. We say that BNRPerson inherits the instance variables and methods from NSObject.
In this chapter, you are going to create a new class named BNREmployee. BNREmployee will be a subclass of BNRPerson.
Makes sense, right? Employees are people. They have heights and weights. But not all people are employees; employees can have characteristics specific to being an employee. Your BNREmployee class adds two BNREmployee-specific characteristics – an employee ID and a hire date.
Open up the BMITime project and create a new file: an Objective-C class. Name the class BNREmployee and leave its superclass as NSObject for now.
Open BNREmployee.h. Import BNRPerson.h and change the superclass from NSObject to BNRPerson:
#import "BNRPerson.h" @interface BNREmployee : BNRPerson @end
BNREmployee is now a subclass of BNRPerson.
The main function of BMITime will need to access the employee ID and the hire date. Each employee also has an alarm code to get into the office. Thus, you need three new properties, and you need to add them in BNREmployee.h. Also declare a method that will calculate the years of employment based on the employee’s hire date.
#import "BNRPerson.h" @interface BNREmployee : BNRPerson @property (nonatomic) unsigned int employeeID; @property (nonatomic) unsigned int officeAlarmCode; @property (nonatomic) NSDate *hireDate; - (double)yearsOfEmployment; @end
The hireDate property is the first property you have declared that points to another object. When a property points to an object, there are memory management implications that you will learn about in later chapters.
For now, recognize that you have declared a property named hireDate that is a pointer to an NSDate. You tell the compiler that this property is to be nonatomic, like all the other properties.
And, like with your primitive type properties, by default you get an instance variable. This variable is named _hireDate and is a pointer to an NSDate. The compiler also synthesizes two accessor methods:
- (void)setHireDate:(NSDate *)d; - (NSDate *)hireDate;
In BNREmployee.m, implement the yearsOfEmployment method:
@implementation BNREmployee - (double)yearsOfEmployment { // Do I have a non-nil hireDate? if (self.hireDate) { // NSTimeInterval is the same as double NSDate *now = [NSDate date]; NSTimeInterval secs = [now timeIntervalSinceDate:self.hireDate]; return secs / 31557600.0; // Seconds per year } else { return 0; } } @end
To try out the BNREmployee class, open main.m and make two changes: import BNREmployee.h and create an instance of BNREmployee instead of a BNRPerson. Leave the person variable declared as a pointer to a BNRPerson for now:
#import <Foundation/Foundation.h>#import "BNRPerson.h" #import "BNREmployee.h" int main(int argc, const char * argv[]) { @autoreleasepool { // Create an instance of BNREmployee BNRPerson *mikey = [[BNREmployee alloc] init]; // Give properties interesting values using setter methods mikey.weightInKilos = 96; mikey.heightInMeters = 1.8; // Log some properties using getter methods NSLog(@"mikey has a weight of %d", mikey.weightInKilos); NSLog(@"mikey has a height of %f", mikey.heightInMeters); // Log the body mass index float bmi = [mikey bodyMassIndex]; NSLog(@"mikey has a BMI of %f", bmi); } return 0; }
Think this will cause a problem? Build and run the program to see.
The program works fine, and nothing in the output has changed. An employee is a person – it can do anything a person can. An instance of BNREmployee can respond to methods from BNRPerson (like setWeightInKilos:). You can use an instance of BNREmployee anywhere that the program expects an instance of BNRPerson. A instance of a subclass can stand in for an instance of the superclass without problems because it inherits everything in the superclass.
Also note that you do not need to import BNRPerson.h.
The compiler will find the #import "BNRPerson.h"
statement in the BNREmployee.h file, so including it in here would be redundant.
Now make the following changes in main.m to make fuller use of the BNREmployee class. Give mikey an employee ID and set the hire date to the current date.
#import <Foundation/Foundation.h> #import "BNREmployee.h" int main(int argc, const char * argv[]) { @autoreleasepool { // Create an instance of BNREmployee BNREmployee *mikey = [[BNREmployee alloc] init]; // Give the instance variables interesting values using setter methods mikey.weightInKilos = 96; mikey.heightInMeters = 1.8; mikey.employeeID = 12; mikey.hireDate = [NSDate dateWithNaturalLanguageString:@"Aug 2nd, 2010"]; // Log the instance variables using the getters float height = mikey.heightInMeters; int weight = mikey.weightInKilos; NSLog(@"mikey is %.2f meters tall and weighs %d kilos", height, weight); NSLog(@"Employee %u hired on %@", mikey.employeeID, mikey.hireDate); // Log the body mass index using the bodyMassIndex method float bmi = [mikey bodyMassIndex]; double years = [mikey yearsOfEmployment]; NSLog(@"BMI of %.2f, has worked with us for %.2f years", bmi, years); } return 0; }
Build and run the program and see your new output.
Usually, a subclass needs to do something differently than its superclass. Let’s say, for example, that, unlike people in general, employees always have a BMI of 19. In this case, you would override the bodyMassIndex method in BNREmployee.
You override an inherited method by writing a new implementation.
In BNREmployee.m, override bodyMassIndex:
#import "BNREmployee.h" @implementation BNREmployee - (double)yearsOfEmployment { // Do I have a non-nil hireDate? if (self.hireDate) { // NSTimeInterval is the same as double NSTimeInterval secs = [self.hireDate timeIntervalSinceNow]; return secs / 31557600.0; // Seconds per year } else { return 0; } } - (float)bodyMassIndex { return 19.0; } @end
Because BNREmployee inherits from BNRPerson, everyone already knows that instances of BNREmployee will respond to a bodyMassIndex message. There is no need to advertise it again, so you do not declare it in BNREmployee.h.
This also means that, when you override a method, you can only change its implementation. You cannot change how it is declared; the method’s name, return type, and argument types must stay the same.
Build and run the program. Confirm that BNREmployee’s implementation of bodyMassIndex is the one that gets executed – not the implementation from BNRPerson.