Experimenting With Cocoa
George Wright
gwright@labyrinth.net.au

Back to the List of Tutorials


Introduction
Cocoa is composed of frameworks containing libraries of objects and related resources, data types, functions, header files and documentation. The two major Cocoa frameworks are the Foundation Framework and Application Kit framework. Foundation contains non-graphical objects useful in all Cocoa applications. Application Kit provides all the graphical user interface elements you are familiar with in Mac OS X Aqua applications.
My earlier articles provided tutorial experience in using Project Builder and Interface Builder to work with Application Kit objects - windows, buttons, text views and progress indicators. Now I want to do some experimenting with non-graphical Foundation framework elements.

What's in Frameworks ?
There are lots of classes in Frameworks to handle such objects as strings, arrays, dates, dictionaries, enumerators, formatters, filemanagers, ports and many others. The documentation gives basic information about these and, in a few cases some sample code. I find that to become familiar with these objects the most effective strategy is to write some code and experiment with it - get something working and then modify it bit by bit while observing resulting changes. Fortunately you don't have to build a user interface every time you want to experiment because Project Builder provides a template - called Foundation Tool - for just this purpose.

Creating a New Foundation Tool Project
Start the Project Builder application (you can find it in the /Developer/Applications folder if you have installed the Developer Disk supplied with Mac OS X).
Choose New Project and then in the Assistant window choose Foundation Tool (second from the bottom in my version). Click Next. In the Project Name field write CocoaExperiment. A folder called CocoaExperiment will be created in your Home directory. In the Project Builder Groups & Files panel select main.m which can be revealed when you click the disclosure triangle near the Source folder. The code in main.m is as listed below.
Hello World.tiff


Build and Run
The template code that comes up will build and run without any further additions and you should do that right now.
Click the Build & Run tool (third tool from the left in the Project Builder tool bar). When it runs you will see a print-out like that illustrated here.
Run Window.tiff

It will have the date and time, the name of the application and the words "Hello, World!" which were part of the NSLog(@"Hello, World!"); line of code in the main.m file. Here's where you can do a simple experiment. Change the words between the double quotes, save then build & run again. Actually I prefer not to replace the line but rather duplicate it and change the duplicate. For example change the duplicate line to
NSLog(@"Any string declared between quotes following @ is created at compile time as a constant instance of NSString");
and then save, build & run. The output should be something like this:
2003-07-12 19:29:00.370 CocoaExperiment[1247] Hello, World!
2003-07-12 19:29:00.371 CocoaExperiment[1247] Any string declared between quotes following @ is created at compile time as a constant instance of NSString

CocoaExperiment has exited with status 0.

NSAutoreleasePool
A Cocoa class to help in memory management is NSAutoreleasePool. When you use Interface Builder to build applications incorporating the Application Kit framework objects, a release pool is created automatically. But here we dealing only with Foundation framework so have to create our own. Fortunately the Foundation Tool template we have already opened in Project Builder includes it for us. All the implementation code we will write for our experiments must be between the lines
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
and
[pool release];
That should be enough to take care of memory management!.

Experimenting with NSString
Normally to initialize and create an instance of some class you will code it like this
[[SomeClass alloc] init];
But in many cases Cocoa provides convenient shortcuts to creating new objects.
An easy way to create a fixed, constant, immutable string is to include the text between the quotes of a @"" expression as follows

NSString *thisString = @"Whatever is written here becomes the NSString instance called thisString";

Now if we send the 'description' message to an object we will get back - in most cases - a sensible description of the object. It's done like this
[anObject description];
or in this case
[thisString description];

To print it out to the Project Builder Run window we use NSLog
NSLog(@"The Description of thisString is %@", [thisString description]);

Notice the format specifier, %@, is similar to those used for a printf and scanf operations in ANSII C. So now you can add the following two lines to main.m (anywhere between the init and release of the autorelease pool)

NSString *thisString = @"Whatever is written here becomes the NSString instance called thisString";
NSLog(@"The Description of thisString is %@", [thisString description]);

description1.tiff



After you save, build & run, the print-out should look like this.

descriptionRun1.tiff

NSString Documentation
There are several ways to access Cocoa documentation - here's one. Refer to the picture below showing how to use the disclosure triangles in the Groups & Files panel to expose the table of contents of Foundation documentation, FoundationTOC.html.
FoundationTOC.tiff


In the main window you should scroll down to locate and click on the blue link to NSString. If you scroll down again in the NSString window you will find a blue highlighted link to 'length' which seems a good place to start - it is described as one of the primitive methods of NSString. Sending a 'length' message to the string, thisString, already created should return an integer - the number of Unicode characters in thisString. It's done like this
[thisString length];
Now add this line to main.m somewhere after the initialisation of thisString

NSLog(@"The length of thisString is %d", [thisString length]);

Note the %d format specifier to handle printing an integer whereas earlier we had %@ to handle printing of a string.
Before building this new code you could comment out some of the earlier code experiments, keeping them for future reference while avoiding clutter of the output window. Just select the relevant lines, and under the User Scripts menu choose Comment Selection. The code should now look like this
Commented.tiff


Finding Characters Within an NSString

The other method described as primitive is
- (unichar)characterAtIndex:(unsigned)index
which returns the character in Unicode. A different NSLog format specifier is required to handle printing of Unicode. In this case it is %C. To locate a table of format specifiers for NSLog I suggest that it is a little easier to use Finder rather than the Project Builder Find. Type Command F in Finder and search for the documentation file 'FormatStrings.html'. You can open it in Safari or another web browser and maybe print out the table for reference. Incidentally if you have the incorrect NSLog format specifier without any attempt to trap errors, the program will crash and Console will display lots of information which at this stage I have not learnt how to use.

Add the following line to main.m

NSLog(@"The 6 th character in thisString is %C", [thisString characterAtIndex:5]);

In my example, after save, build & run, the letter 'v' is displayed. Note that indexing begins with 0.

Once you have the idea you can locate interesting methods from the documentation and try out your implementations. One more simple example I will give here is the method
- (NSString *)uppercaseString
which will convert the string to upper case. To avoid typo errors I prefer to copy and paste the method from the documentation and remove unwanted parts. The first line creates a new string called 'thatString' by case converting the original string. Then the second line gives NSLog the task of displaying the description of 'thatString'.
NSString *thatString = [thisString uppercaseString];
NSLog(@"thisString after receiving uppercaseString message is %@", [thatString description]);

An alternative layout is to nest the two sets of square brackets holding messages into a single line of code without naming the new uppercase string at all:
NSLog(@"thisString after receiving uppercaseString message is %@", [[thisString uppercaseString] description]);
You could try both styles if you like.

Specialised NSString methods relating to File Path
In the NSString documentation you will find several methods that help handling file paths. I have picked out stringByExpandingTildeInPath to play with.
First define a string named 'theShortPath' to represent the file 'dataTest' in my home directory. At this stage no such file exists although it doesn't hurt to run the code because we are not yet attempting to read or write to the non-existent file.
NSString *theShortPath = @"~/dataTest";
Then using the method stringByExpandingTildeInPath I can create a new string that has the full path to the same file.
NSString *thePath = [theShortPath stringByExpandingTildeInPath];
Then use NSLog to inspect the result
NSLog(@"The Description of theShortPath is %@", [theShortPath description]);
NSLog(@"The Description of thePath is %@", [thePath description]);
When you insert the above four lines, build & run to see the display of paths to the file dataTest. At this stage you can comment out earlier lines but keep this group as we intend to use them later when we create the file.

Experimenting with NSArray
The documentation has copious information about the collections classes - NSArray, NSDictionary and NSSet and their mutable subclasses.
To create an interesting array of different objects first set up some objects from Foundation Framework. I have chosen examples of NSDate, NSValue and NSDecimalNumber after looking at Foundation documentation.
NSDate *startOfMillennium = [NSDate dateWithTimeIntervalSinceReferenceDate:nil];
NSValue *aValue = [NSNumber numberWithInt:5];
NSDecimalNumber *aDecimalNumber = [NSDecimalNumber decimalNumberWithString:@"6.024E23"];

The array to hold these objects (and the NSString thePath created earlier), will be
NSArray *myArray;
myArray = [NSArray arrayWithObjects: startOfMillennium, aValue,
aDecimalNumber, thePath, nil];

Then display a description of the array and the number of elements.
NSLog(@"myArray has description %@", [myArray description]);
NSLog(@"myArray has count %d", [myArray count]);

Path & Array.tiff

Path&Array Out.tiff


Experimenting with NSDecimalNumber
Notice from the pictures above that I have included a couple of extra lines of code to create a second decimal number called anotherDecimalNumber and added the two NSDecimalNumber objects using the method decimalNumberByAdding:. This creates a third NSDecimalNumber called aNewDecimalNumber which I have included in the array. The printout demonstrates the high level of precision of NSDecimalNumber objects - there is no rounding off of the 26 significant figures in this example. The documentation suggests there are 38 significant figures available so you might like to add the additional code and experiment further.

Experimenting with NSEnumerator
NSEnumerator objects provide a convenient method for looping through arrays, dictionaries and sets. The objectEnumerator method of NSArray returns an enumerator object that lets you access each object in the receiver, in order, starting with the element at index 0.
The following code creates a new NSEnumerator for the existing myArray and proceeds to print out a description of each object in turn.
NSEnumerator *myEnumerator = [myArray objectEnumerator];
id object;
while (nil != (object = [myEnumerator nextObject])) {
NSLog(@"This OBJECT IN myArray has description %@", [object description]);
}

enumerator.tiff

enumeratorOut.tiff

Experimenting with NSData

NSData is another Foundation Framework class to learn about. It stores bytes of data that could be text, images, sounds or any other type.
First create a NSString called 'myTestString' to give us something to experiment with
NSString *myTestString = @"This is a test string that I want to turn into an NSData and then write to file atomically. Then I will make a subdata read from this file and write it to another file";

Next create a NSData instance using the dataWithBytes:length: method. Information about the cString and cStringLength messages used here can be found in the NSString documentation.
NSData *myTestData = [NSData dataWithBytes:[myTestString cString] length:[myTestString cStringLength]];

Now the data can be written to file. The name and location of the file has previously been defined as the NSString object called thePath. (Make sure you haven't commented out the relevant lines of code)
[myTestData writeToFile:thePath atomically:YES];

After you enter the three lines above, save, build and run then check your home directory where you should find a new file called dataTest. Open that file in TextEdit to check that it contains the same text you wrote in myTestString.
Now create another NSData object called anotherData by reading only a restricted range from the original myTestData object. First we need to create a NSRange object
NSRange myRange = {92, 75}; // Starting at character #92 for the next 75 characters

Then use the subdataWithRange:range method the create the new NSData
NSData *anotherData = [myTestData subdataWithRange:myRange];

To write this to a new file we need another file path.
[anotherData writeToFile:[@"~/anotherDataTest" stringByExpandingTildeInPath] atomically:YES];

When you run this successfully you will find the new file 'anotherDataTest' in your home directory with only the second sentence of myTestString saved. If the program crashes it may be that the NSRange you have defined runs over the end of file. Try reducing the second parameter of NSRange myRange = {92, 75}; to something less than 75 in this example.

Experimenting with NSFileManager
NSFileHandle and NSFileManager classes provide tools for saving, retrieving and organising files - not only traditional Mac but other platform files too. The classes generally figure out for themselves what kind of files you are dealing with. In this final example rather than just adding new bits of code I will create a new method to handle a certain task and then call on that method and test the results.
This little method called getPathsInCurrentDirectory uses the default NSFileManager that every application has to read the file contents of the current directory and store the list in a mutable array. I have called this method getPathsInCurrentDirectory(). It requires the name of an already defined NSMutableArray so it has a place to store the contents. Incidently this bit of code defining the new method but not executing any of it can be placed - if you like - before the lines creating the NSAutoreleasePool.
void getPathsInCurrentDirectory(NSMutableArray *resultArray)
{
NSString *fileName;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:
[fileManager currentDirectoryPath]];

while (nil != (fileName = [enumerator nextObject]))
{
[resultArray addObject:fileName];
}
}

Now allocate and initialise a new NSMutableArray called myResultArray. According to the documentation you must use the -initWithCapacity or +arrayWithCapacity to create it. These executing lines must be within the NSAutoreleasePool block.
NSMutableArray *myResultArray = [[NSMutableArray alloc] initWithCapacity:1];

Call the getPathsInCurrentDirectory method we have just written to fill myResultArray with the file names.
getPathsInCurrentDirectory(myResultArray);

Finally print a description of myResultArray to display the contents of the current directory.
NSLog(@"The Description of myResultArray is %@", [myResultArray description]);

You could check in Finder that the contents of the 'CocoaExperiment.build' directory and all its subdirectories are correct.

Conclusion
In this article I have described how the Project Builder Foundation Tool provides a convenient platform on which to experiment with Foundation Frameworks objects. I have discussed how NSLog can be used to print out information about the state of your objects. I have noted the appropriate format specifiers to be used with a variety of data types printed out by NSLog. To illustrate the use of the Foundation Tool I have chosen examples from a wide variety of classes and indicated how relevant documentation is accessed. There are of course very many Foundation classes I have omitted or left incomplete. I hope the technique presented here will allow the beginning Cocoa programmer to explore further.
This article and earlier articles on Cocoa programming for beginners I have written for Ausom News can be accessed on my website at
http://www.labyrinth.net.au~/gwright
I look forward to hearing from you and seeing you at AUSOM Programming SIG meetings. See the yellow pages of AUSOM News for details.

Acknowledgements
I have relied heavily on Cocoa documentation included with the Developer Disk installation. I used Mac OS X 10.2.5 and Project Builder 2.1 Dec 2002. I have referred to the Sams book 'Cocoa Programming' Anguish, Buck, Yacktman (Chapter 3 for the introduction to Project Builder Foundation Tool and Chapter 7 for the general approach to Foundation Frameworks). Thanks to Tim Mclaren and Bernd Wachs for reading and commenting on the draft.

Back to the List of Tutorials