This blog is sponsored by FuelCollective, LLC.
The first lesson might have been a little boring as I needed to explain some absolute basics of the language, which, perhaps, many of you already have known before.
This lesson will be more exciting as you'll be able to actually try something out.
I have given you a homework last time - how many have finished it? None? Alright, no worries, I'm sure you'll catch up. I wanted you to install Xcode tools (if not already installed) and create a new project - from a template Command Line Tool, add a new class called LTCow and declare & implement an instance method 'moo'.
Generally now your project should look something like this:
Don't worry if your window looks a bit different. There are several layouts you can switch between in preferences, I'm using the 'Condensed' one, where the editor for every file has its own window as well as the project itself is in a separate window.
A little side-note about project folder organization. By default, when you create a new file, it gets created in the project directory, so you end up with a folder of zillion files and it's really tough to find something, unless you open the Xcode project, where you manage the files into groups. I've found useful to keep the project folder hierarchical. Here's an example from Fuel Collective's project Swatch:
It's a little more work when creating files, but I've found it quite helpful at times. Also note that the source files don't have to be inside the project's directory, so for example, I have a folder where I keep files that I use in multiple projects (rather than keeping up several versions of the particular class).
Right now, when you launch your new application (in menu Build > Build and Run), nothing really happens. The source codes get compiled and a console window should automatically open. If it doesn't, you can either open it via the menu (Run > Console), or set it up to open automatically in Preferences > Debugging.
Note that in Xcode 4 it's all quite different, but until it's released in the wild, you should get quite familiar with the basics so that you don't get lost even when the UI changes.
Your app should write a line similar to this one:
2010-08-10 12:07:08.481 AnimalFarm[14238:80f] Hello, World!
Where did this come from? In Command Line Tools, the template automatically creates a source code file <name-of-the-project>.m - that's where the application "comes in" when it's launched (as we'll see later, in GUI applications, the file is always main.m and you don't really have a reason to modify it).
So let's open the AnimalFarm.m file by double-clicking it in Xcode.
On the first line, you're importing Foundation (the UI-less part of Cocoa) and implementation of the main function, just like in C.
Inside the main function, you're creating a NSAutoreleasePool that you're draining in the end. We'll talk about it more later on, however, I'll mention some basics now:
When you create an object, you allocate some memory (malloc in C). Unless you're using garbage collection (basically the runtime is tracking what pointers are no longer used and releases the memory automatically), you need to release the memory (in C it's the free(void*) function). I'm not a big fan of garbage collection as it can create its own problems and causes people not to understand memory management, which I believe is one of the programming fundaments, even if you decide to only write UI apps.
In ObjC, you usually create an object this way:
MyClass *myObject = [[MyClass alloc] init];
The alloc class method returns a newly allocated place in memory with enough space for the class's variables. The init method is called on the object to set up initial values and it's supposed to return either a consistent object or nil (something like NULL in C).
Note that this doesn't cause the object to be immediately deallocated and removed from the memory. Each object has a retain counter. When you create an object the retain count is +1. Each time the -release method is called, the retain counter gets decremented by one and when it reaches 0, the -dealloc method gets called, which returns the memory used by the object to the system's resource pool. To increment the retain count (e.g. when setting an object as a variable, to make sure it's kept alive), use the -retain method.
To make your life easier, there's NSAutoreleasePool, which is periodically drained - each time it's drained, it calls -release on all autoreleased objects and removes them from the pool.
To add an object to the pool, use the -autorelease method:
MyClass *myObject = [[[MyClass alloc] init] autorelease];
You then don't have to worry about releasing it further on. For convenience, many classes have class methods that return autoreleased instances. For example to get an empty string, you can use either [[NSString alloc] init] (the retain count is +1, it's *not* autoreleased, you're responsible for releasing it) or [NSString string] (the retain count it +1, but it *is* autoreleased, so don't worry about releasing it later on).
To sum it up: whenever you create an object using a method that starts with "init" or "copy", you need to either release it or autorelease it. Whenever you create an object with another (usually class) method, don't worry about it.
Just a quick test. What's wrong in the next example?
MyClass *myObject = [[MyClass alloc] init];
[myObject doSomethingCool];
Yes. The app will crash. Why? On the first line, the object gets created, it has a retain count +1. On the second line, you release it, thus it has a retain count 0 and as I've mentioned, once it reaches 0, it gets deallocated. So on the third line you're doing something naughty.
MyClass *myObject = [[[MyClass alloc] init] autorelease];
Again, the app will crash. First line, retain counter is +1, second line, it becomes 0 → deallocated. When the NSAutoreleasePool tries to send the -release message to the object, the app crashes as the object no longer exists.
MyClass *myObject = [[MyClass alloc] init]; //+1
[myObject release]; //0 → gets dellocated
[myObject retain]; //Crash, object no longer exists
MyClass *myObject = [[MyClass alloc] init]; //+1
//Hey, who's gonna release it?!
Now you're leaking memory. You lose all references to the object and it has retain count +1 - it never gets released, it's in the memory all the time the app's running.
Back to our app - the only unexamined line in the generated code is the
line. So this is where the "Hello, World!" came from! NSLog is a function just like printf in C. Get familiar with it, you'll be using it a lot as a debugging tool and much more.
Generally the only difference between NSLog and printf is that NSLog adds a %@ token support which is used for printing objects. Let's try it.
Remove the Hello world line and add the following code:
LTCow *blackCow = [[LTCow alloc] init];
NSLog(@"My new black cow: %@", blackCow);
[blackCow release]; //We don't need the cow anymore
Try to build and run it. Got an error? I'm sure you'll be able to fix it. If not (e.g. you're not too familiar with C), I'm giving you a clue: #import. If still nothing, leave a comment here and I'll get back to you ASAP.
2010-08-10 13:44:47.071 AnimalFarm[14934:80f] My new black cow: <LTCow: 0x103fe0>
Uh? <LTCow: 0x103fe0>? What's that? When the NSLog function is supposed to log an object, it calls a -(NSString*)description; method on it, which should sum up the description of the cow. By default (NSObject defines it this way) it prints it like this: <CLASS_NAME: 0xADDRESS_OF_THE_OBJECT_IN_MEMORY>. In the next lesson, we'll modify it a bit.
To do so, you'll need to do some more homework.
1) Create another class called LTAnimal.
2) Make the LTCow class a subclass of LTAnimal instead of NSObject (remember to #import the LTAnimal header file).