Explaining this chapter


#1

Before even attempting the challenge, I need to know I truly understand the contents of this chapter, and it’s not that clear yet:

  1. Why do person.h and person.m have the particular related codes that they do?

  2. I’m not clear about the difference between setter, getter and accessor methods.

  3. I noticed at least three versions of the BMI program, each new one more terse and efficient than the previous. I’m not sure what the newer codes are doing more efficiently, and why.

  4. I’m especially intrigued by “@property” and “@synthesize”, and again to echo my first question, why one is in .h and the other in .m?

              I really need to conceptualize how to make your own classes.

#2

I will try to answer the last part of your question first.

Things that are designed to be interacted with consists of two parts: an interface part and a details part.

Consider your Mac as an example. At the physical level, its interfaces are a keyboard, a mouse, maybe a trackpad, the speakers, the microphone, and the display (there are more), which you use to interact with your Mac. Its details are the parts, both SW & HW, that take input from the interfaces, make the magic happen and then send the computed output to interfaces such as the display and the speakers.

Even though they are not physical, programs are no exception: they too have an interface part and an implementation part. You should be familiar with this by now: Xcode is a good example, for instance.

Here is a simple but monolithic Objective-C++ program (monolithic? will explain in a minute) that computes the factorial of a given number and that also speaks to you. (Try concentrating on the forest, not the trees).

(The code is a mix of Objective-C & C++; if you want to compile in Xcode, make sure that you retain the .mm suffix, which indicates to the compiler that the source is Objective-C++: mixture of Objective-C and C++.)

Paste into Xcode to view.

//
//  main.mm
//

#import <AppKit/AppKit.h>
#import <sstream>

namespace my
{
    typedef unsigned long long value_type;
    value_type Factorial (value_type);
}

namespace my {
{
    void SpeakText (const char*);
    void SpeakNumber (value_type);
}

static const char *enterNumber        = "Dear user: please enter a number greater than minus one!";
static const char *enterSmallerNumber = "Dear user: please enter a smaller number!";

int main (int argc, const char * argv[])
{
    if (argc != 2)
    {
        my::SpeakText (enterNumber);
        return 1;
    }
    
    std::istringstream is (argv [1]);
    my::value_type number;
    is >> number;
    if (!is)
    {
        my::SpeakText (enterNumber);
        return 1;        
    }
    
    if (number > 10)
    {
        my::SpeakText (enterSmallerNumber);
        return 1;                
    }
    
    my::value_type result = my::Factorial (number);
    std::ostringstream os;
    os << "Dear user: the factorilal of " << number << "is :";
    my::SpeakText (os.str().c_str());
    my::SpeakNumber (result);
    
    return 0;
}

namespace my
{
    value_type Factorial (value_type v)
    {
        if (v == 0)
            return 1;
        else {
            return v * Factorial (v-1);
        }
    }

    void SpeakText (const char*string)
    {
        @autoreleasepool {
            NSSpeechSynthesizer *SS = [[NSSpeechSynthesizer alloc] initWithVoice:[NSSpeechSynthesizer defaultVoice]];
            [SS startSpeakingString:[NSString stringWithUTF8String:string]];
            while ([SS isSpeaking])
            {
                [NSThread sleepForTimeInterval:0.1];
            }
        }
    }

    void SpeakNumber (value_type number)
    {
        SpeakText ([[NSString stringWithFormat:@"%ld", number] UTF8String]);
    }
}

The above program has two components: a component for calculating the factorial of a number, and a component for speaking numbers and text. Although they are useful internally, they can’t be accessed from outside and thus they are not reusable, which makes the above program monolithic (" formed of a single large block of stone or very large and characterless");

So that they can be reusable, let’s separate the internal components into two parts: an interface part and a details part. Details part go into *.mm file, and the interface part into *.h file. (We name the two files Factorial.h and Factorial.mm.)

Take 1:
main.mm (the driver):

//
//  main.mm
//

#import "Factorial.h"
#import <sstream>

static const char *enterNumber        = "Dear user: please enter a number greater than minus one!";
static const char *enterSmallerNumber = "Dear user: please enter a smaller number!";

int main (int argc, const char * argv[])
{
    if (argc != 2)
    {
        my::SpeakText (enterNumber);
        return 1;
    }
    
    std::istringstream is (argv [1]);
    my::value_type number;
    is >> number;
    if (!is)
    {
        my::SpeakText (enterNumber);
        return 1;        
    }
    
    if (number > 10)
    {
        my::SpeakText (enterSmallerNumber);
        return 1;                
    }
    
    my::value_type result = my::Factorial (number);
    std::ostringstream os;
    os << "Dear user: the factorilal of " << number << "is :";
    my::SpeakText (os.str().c_str());
    my::SpeakNumber (result);
    
    return 0;
}

Factorial.h (interface):

//  Factorial.h

#ifndef MY_Factorial_h
#define MY_Factorial_h

namespace my
{
    typedef unsigned long long value_type;
    value_type Factorial (value_type);
}

namespace my
{
    void SpeakText (const char*);
    void SpeakNumber (value_type);
}

#endif

Factorial.mm (details):

//  Factorial.mm

#import <AppKit/AppKit.h>
#import "Factorial.h"

namespace my
{
    value_type Factorial (value_type v)
    {
        if (v == 0)
            return 1;
        else {
            return v * Factorial (v-1);
        }
    }
    
    void SpeakText (const char*string)
    {
        @autoreleasepool {
            NSSpeechSynthesizer *SS = [[NSSpeechSynthesizer alloc] initWithVoice:[NSSpeechSynthesizer defaultVoice]];
            [SS startSpeakingString:[NSString stringWithUTF8String:string]];
            while ([SS isSpeaking])
            {
                [NSThread sleepForTimeInterval:0.1];
            }
            [SS release];
        }
    }
    
    void SpeakNumber (value_type number)
    {
        SpeakText ([[NSString stringWithFormat:@"%ld", number] UTF8String]);
    }
}

Take-1 is a big improvement over the monolithic version. But what if I only want the component that speaks numbers and text?

Let’s break it up further into two distinct components: a Factorial component and a SpeechEngine component.
We name the new files Factorial.h & Factorial.mm, and SpeechEngine.h & SpeechEngine.mm.

Take-2
main.mm (the driver):

//  main.mm

#import "Factorial.h"
#import "SpeechEngine.h"
#import <sstream>

static const char *enterNumber        = "Dear user: please enter a number greater than minus one!";
static const char *enterSmallerNumber = "Dear user: please enter a smaller number!";

int main (int argc, const char * argv[])
{
    if (argc != 2)
    {
        my::SpeechEngine::SpeakText (enterNumber);
        return 1;
    }
    
    std::istringstream is (argv [1]);
    my::value_type number;
    is >> number;
    if (!is)
    {
        my::SpeechEngine::SpeakText (enterNumber);
        return 1;        
    }
    
    if (number > 10)
    {
        my::SpeechEngine::SpeakText (enterSmallerNumber);
        return 1;                
    }
    
    my::value_type result = my::Factorial (number);
    std::ostringstream os;
    os << "Dear user: the factorilal of " << number << "is :";
    my::SpeechEngine::SpeakText (os.str().c_str());
    my::SpeechEngine::SpeakNumber (result);
    
    return 0;
}

Factorial.h (interface):

//  Factorial.h

#ifndef MY_Factorial_h
#define MY_Factorial_h

namespace my
{
    typedef unsigned long long value_type;
    value_type Factorial (value_type);
}

#endif

Factorial.mm (details):

//  Factorial.mm

#import "Factorial.h"

namespace my
{
    value_type Factorial (value_type v)
    {
        if (v == 0)
            return 1;
        else {
            return v * Factorial (v-1);
        }
    }
}

SpeechEngine.h:

//  SpeechEngine.h

#ifndef MY_SpeechEngine_h
#define MY_SpeechEngine_h

namespace my {
namespace SpeechEngine {
    typedef unsigned long long value_type;

    void SpeakText (const char*);
    void SpeakNumber (value_type);
}
}

#endif

SpeechEngine.mm:

//  SpeechEngine.mm

#import <AppKit/AppKit.h>
#import "SpeechEngine.h"

namespace my {
namespace SpeechEngine {   
    
    void SpeakText (const char*string)
    {
        @autoreleasepool {
            NSSpeechSynthesizer *SS = [[NSSpeechSynthesizer alloc] initWithVoice:[NSSpeechSynthesizer defaultVoice]];
            [SS startSpeakingString:[NSString stringWithUTF8String:string]];
            while ([SS isSpeaking])
            {
                [NSThread sleepForTimeInterval:0.1];
            }
            [SS release];
        }
    }
    
    void SpeakNumber (value_type number)
    {
        SpeakText ([[NSString stringWithFormat:@"%ld", number] UTF8String]);
    }
}
}

Now we have two reusable components: Factorial and SpeechEngine.

Factorial: Factorial.h (interface), Factorial.mm (implementation details)
SpeechEngine: SpeechEngine.h (interface), SpeechEngine.mm (implementation details)

We also have a third component: main.mm. This is the driver (the component that glues the components together); it is not reusable.

Now I can pick and choose: Factorial, SpeechEngine, or both.

The interface part of a component provides us with information about how to use that component; the details part takes care of the implementation details of the component. Unless we want to modify it, we don’t really care about the details part.

So, after all, writing classes are not different at all. You put the interfaces in *.h files, and details in *.m (or *.mm) files.


#3

Thanks for trying to help. You told me to see the forest and ignore the trees, but those trees were really thick. I did get a better sense of interface vs implementation, but I still need to understand why, of Aaron’s particular functions, some were interface mode, and others implementation mode. For instance, in Aaron’s version 3 of BMI Time, what is actually happening inside the compiler when it sees
"synthesize weightInKilos heightInMeters" while in Person.h and “property int weightInKilos;
property float heightInMeters;” while in Person.m?

Nathan