It’s 2008: You’re reading Objective-C tutorials on Cocoa Dev Central, Lucida Grande is the system font and ARC doesn’t exist yet.
You want to write a nice, native Cocoa application. Where do you start?
argv[0]
is the program name, so the first argument starts at argv[1]
.
C is great and all, but what if we want to start interacting with OS X? We can do more interesting stuff with libraries.
Libraries on OS X come in two flavours, static and dynamic.
We’ve successfully compiled our code into an object file, a.k.a. machine code that Mach based kernels can understand. The -c
flag passed to clang
tells it to only compile it: by default it will also try to link it too, but we’re going to do that by hand.
$ objdump -d foo.o
foo.o: file format Mach-O 64-bit x86-64
Disassembly of section __TEXT,__text:
_add:
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 89 7d fc movl %edi, -4(%rbp)
7: 89 75 f8 movl %esi, -8(%rbp)
a: 8b 75 fc movl -4(%rbp), %esi
d: 03 75 f8 addl -8(%rbp), %esi
10: 89 f0 movl %esi, %eax
12: 5d popq %rbp
13: c3 retq
objdump -d
disassembles the object file so we can look inside. Here, foo.o
has one symbol exported: _add
. Now say we want to reuse this function in an executable. Without having the object file, we can compile our executable as long as we tell the compiler what this function looks like via a header file.
// foo.h
int add(int a, int b);
// main.c
#include <stdio.h>
#include "foo.h"
int main() {
printf("%i\n", add(10, 32));
};
$ clang -c main.c
$ objdump -d main.o
main.o: file format Mach-O 64-bit x86-64
Disassembly of section __TEXT,__text:
_main:
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 48 83 ec 10 subq $16, %rsp
8: bf 0a 00 00 00 movl $10, %edi
d: be 20 00 00 00 movl $32, %esi
12: e8 00 00 00 00 callq 0 <_main+0x17>
17: 48 8d 3d 16 00 00 00 leaq 22(%rip), %rdi
1e: 89 c6 movl %eax, %esi
20: b0 00 movb $0, %al
22: e8 00 00 00 00 callq 0 <_main+0x27>
27: 31 f6 xorl %esi, %esi
29: 89 45 fc movl %eax, -4(%rbp)
2c: 89 f0 movl %esi, %eax
2e: 48 83 c4 10 addq $16, %rsp
32: 5d popq %rbp
33: c3
Check out how it’s loading in 10 and 32 into the registers before calling the mysterious <_main+0x17>
. This is where _add
should be, but we haven’t included it into the program yet. We do this by linking it:
The command above links the foo object file, main object file, as well as the C standard library that contains the printf
function that we use. (It’s a dynamic library - we’ll see more of them shortly) Now if we run objdump -d a.out
, we can see that foo.o has been copied into a.out, and our <_main+0x17>
has been replaced with <_add>
.
_add:
1f50: 55 pushq %rbp
1f51: 48 89 e5 movq %rsp, %rbp
1f54: 89 7d fc movl %edi, -4(%rbp)
1f57: 89 75 f8 movl %esi, -8(%rbp)
1f5a: 8b 75 fc movl -4(%rbp), %esi
1f5d: 03 75 f8 addl -8(%rbp), %esi
1f60: 89 f0 movl %esi, %eax
1f62: 5d popq %rbp
1f63: c3 retq
_main:
1f70: 55 pushq %rbp
1f71: 48 89 e5 movq %rsp, %rbp
1f74: 48 83 ec 10 subq $16, %rsp
1f78: bf 0a 00 00 00 movl $10, %edi
1f7d: be 20 00 00 00 movl $32, %esi
1f82: e8 c9 ff ff ff callq -55 <_add>
1f87: 48 8d 3d 38 00 00 00 leaq 56(%rip), %rdi
1f8e: 89 c6 movl %eax, %esi
1f90: b0 00 movb $0, %al
1f92: e8 0d 00 00 00 callq 13
1f97: 31 f6 xorl %esi, %esi
1f99: 89 45 fc movl %eax, -4(%rbp)
1f9c: 89 f0 movl %esi, %eax
1f9e: 48 83 c4 10 addq $16, %rsp
1fa2: 5d popq %rbp
1fa3: c3 retq
What if we want to further expand our rich suite of arithmetic functions in our library, and include a sub
function, compiled into another object file?
// sub.c
int sub(int a, int b) {
return a - b;
}
// foo.h
int add(int a, int b);
int sub(int a, int b);
// main.c
#include <stdio.h>
#include "foo.h"
int main() {
int x = sub(42, 12);
printf("%i\n", add(3, x));
};
We could compile and then link together all the different object files like fools:
But if we had a large library with lots of object files, this would be a pain. Let’s create a library archive instead:
Congratulations, you’ve just created and linked your first static library! Instead of jumping through the whole clang -c
& ld
malarkey, you can just tell clang to link it for you with the library:
-L.
tells clang to look for libraries inside the current directory, .
, and -lfoo
tells it to load the library called libfoo
. You’ll see this lib
prefix all over the place, especially inside /usr/lib/
.
Often we don’t want to copy over the library code into our executable. If every app on OS X did that then you would find yourself with a lot of duplicate functions from Cocoa. Instead we can use a dynamic library, where our executable just points to the library containing the Mach-O code whenever it needs to call a function.
Now if you run objdump -d a.out
, you’ll notice _add
and _sub
are nowhere to be found. Try running a.out
from another directory:
$ cd ../
$ ./dylib-experiments/a.out
dyld: Library not loaded: libfoo.dylib
Referenced from: /Users/luke/Source/./dylib-experiments/a.out
Reason: image not found
Abort trap: 6
dyld
, the tool that manages loading dynamic libraries, can’t find our library since it’s not in the immediate directory. However we can give it a hint by setting an environment variable:
A framework is like a library but can contain additional header files, extra resources and documentation. They are also a type of bundle, a feature carried over from NeXTSTEP. (More info on that later)
Take the humble CoreFoundation for example:
#include <CoreFoundation/CoreFoundation.h>
int main(int argc, char **argv) {
CFStringRef str = CFStringCreateWithCString(NULL, argv[1], kCFStringEncodingUTF8);
printf("Hello %s\n", CFStringGetCStringPtr(str, kCFStringEncodingUTF8));
}
The first parameter of CFStringCreateWithCString
is a CFAllocator
(whatever that is), but thankfully we can leave it as NULL
to use the default one if we don’t want to deal with it. You’ll see this happening with some other CoreFoundation APIs.
Anyway, we’ll need to link the CoreFoundation framework when we compile this time.
OK cool, but why did we need Core Foundation for that? Let’s do something a bit more interesting.
CFLocaleRef locale = CFLocaleCopyCurrent();
CFNumberFormatterRef formatter = CFNumberFormatterCreate(NULL, locale, kCFNumberFormatterSpellOutStyle);
CFNumberRef number = CFNumberFormatterCreateNumberFromString(NULL, formatter, str, NULL, kCFNumberFormatterParseIntegersOnly);
int value;
CFNumberGetValue(number, kCFNumberSInt64Type, &value);
printf("%i\n", value);
}
Try changing up the locale.
CoreFoundation isn’t just for working with strings, it provides a host of powerful APIs for working with locales, formatting, dates/times and more.
To be safe we should analyze our code.
$ clang --analyze cf.c
main.c:4:21: warning: Potential leak of an object stored into 'str'
CFStringRef str = CFStringCreateWithCString(NULL, argv[1], kCFStringEn...
^
main.c:6:24: warning: Potential leak of an object stored into 'locale'
CFLocaleRef locale = CFLocaleCopyCurrent();
...
main.c:11:3: warning: A CFNumber object that represents a 64-bit integer is used
to initialize a 32-bit integer; 32 bits of the CFNumber value will
overwrite adjacent storage
CFNumberGetValue(number, kCFNumberSInt64Type, &value);
Oh right, this is C so we need to keep track of memory ourselves. We’ll need to release anything we create otherwise we will end up leaking memory.
Clang also helpfully pointed out that we were trying to get a 64 bit integer from CFNumberRef
into a 32 bit int
, so let’s fix that.
Perfect. Now let’s try out some more frameworks, like Core Services which lets us interact with OS X[^1]. We can use it to open URLs in the user’s default browser:
[^1] It’s still 2008
#include <CoreFoundation/CFURL.h>
#include <CoreServices/CoreServices.h>
int main(int argc, char **argv) {
CFMutableStringRef str = CFStringCreateMutable(NULL, 0);
CFStringAppend(str, CFSTR("https://duckduckgo.com?q="));
for(int i = 1; i < argc; i++) {
CFStringRef query = CFStringCreateWithCString(NULL, argv[i], kCFStringEncodingUTF8);
CFStringAppend(str, query);
CFStringAppend(str, CFSTR("+"));
CFRelease(query);
}
CFURLRef url = CFURLCreateWithString(NULL, str, NULL);
LSOpenCFURLRef(url, 0);
CFRelease(str);
CFRelease(url);
return 0;
}
$ clang -o search -framework CoreFoundation -framework CoreServices search.c
$ ./search little bites of cocoa
Cool. Repeatedly running these clang commands is starting to get tedious, we should make a makefile
.
This rule says that if we want to make the file search
, we will need the file (dependency) search.c
. The indented line below specifies how we make it, although you can have multiple lines. The analogy here is kind of like a recipe:
search
is the name of the dish:
are ingredients$ make search
clang -o search -framework CoreFoundation -framework CoreServices search.c
$ ./search makefile tutorial
But what if we want to compile something other than search.c? Let’s make this more general.
%
is a wildcard (think *
), and inside the rules $@
and $<
get replaced with the destination and dependency respectively.
Nice and reusable. A nice thing is that if we run make main
again and none of the dependencies have changed (i.e. main.c
), make
won’t bother rebuilding it.
Time to write some Objective-C.
#include <objc/objc-runtime.h>
#include <stdio.h>
char* implementation(id self, SEL _cmd, int x) {
if (x > 5)
return "Hello world!";
else
return "Goodbye world!";
}
int main() {
Class cls = objc_allocateClassPair(objc_getClass("NSObject"), "Foo", 0);
objc_registerClassPair(cls);
class_addMethod(cls, sel_getUid("bar"), (IMP)implementation, "*@:i");
id obj = objc_msgSend((id)cls, sel_getUid("alloc"));
obj = objc_msgSend(obj, sel_getUid("init"));
char* result = (char*)objc_msgSend(obj, sel_getUid("bar"), 42);
printf("%s\n", result);
}
Wait what? This probably isn’t what you expected when you think of Objective-C. However, it shows us how Objective-C’s runtime, libobjc
, works behind the scenes. It highlights some key things about Objective-C:
objc_registerClassPair
and objc_addMethod
objc_msgSend
(you might have seen this guy in a crash log before)sel_getUid
IMP
s (implementations) which can just be function pointersThere’s a lot going on here so let’s take a deeper dive into some of the methods.
objc_allocateClassPair
and objc_registerClassPair
create a new class that subclasses a given class. We give it a name too. The 0
at the end for is how many extra bytes we want for ivars (instance variables), which we don’t have any of. It returns a Class
.
class_addMethod
takes a class and adds a method to it. You need to specify the selector (SEL
) for it, which is kind of like a name for it. sel_getUid
gives us one from a string. The implementation (IMP
) is a function pointer, and in Objective-C all methods need to take in an object and a selector as the first two arguments. The first one is an object (id
), and it’s self
- that self
keyword you’ve been using has actually been a parameter all this time, just like in Python. On the other hand _cmd
is not so common. It contains the selector of the method that is being called, which may seem kind of pointless. However, one of the wierd advantages of Objective-C being super dynamic is that you can “swizzle” methods and swap out their implementations, and the _cmd
parameter lets you know what the original method that was being called before you hijacked it. The last parameter is the type signature, which we specify with this encoded string thing. The first character is the return type, and *
represents char*
. Every character after that is an argument. @
is an object for self
, :
is a selector for _cmd
and i
is our humble int
, x
.
objc_msgSend
takes an object and sends a message to it at a selector, along with any arguments. It’s what happens when you call a method like [obj bar:42];
.
You might have noticed when call a class method like alloc
, we pass in a Class
but just cast it to an object, id
. That’s because in Objective-C, classes are objects.
We could have written something in actual Objective-C like this:
objc.m
#import <objc/NSObject.h>
#import <stdio.h>
@interface Foo: NSObject
- (char*)bar:(int)x;
@end
@implementation Foo
- (char*)bar:(int)x {
if (x > 5)
return "Hello world!";
else
return "Goodbye world!";
}
@end
int main() {
Foo *obj = [Foo alloc];
char* result = [obj bar:42];
printf("%s\n", result);
}
Compile it in the same way you did with C:
The C code that we wrote doesn’t exactly do the same thing as the Objective-C code. If we look at the assembly generated when we compile it, we can see that it still calls objc_msgSend
from the runtime library:
The main difference between this and the C version is that the C version creates the classes and methods on the fly, but here it actually lays out the classes and selectors at compile time:
...
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_: ## @OBJC_METH_VAR_NAME_
.asciz "bar"
.section __TEXT,__objc_methtype,cstring_literals
L_OBJC_METH_VAR_TYPE_: ## @OBJC_METH_VAR_TYPE_
.asciz "*16@0:8"
.section __TEXT,__objc_classname,cstring_literals
L_OBJC_CLASS_NAME_: ## @OBJC_CLASS_NAME_
.asciz "Foo"
.section __DATA,__objc_const
.p2align 3 ## @"\01l_OBJC_METACLASS_RO_$_Foo"
...
You may have spotted that at the top of the Objective-C code we use #import
. It does the same thing as #include
but won’t include files more than once, so we don’t have to worry about recursive includes. We also no longer need to import the runtime headers since we’re no longer directly calling objc_msgSend
, but instead we need to import the headers for NSObject
.
If you’ve written Objective-C before, you’ve probably inherited from NSObject
all over the place without ever having to explicitly import it. That’s usually because importing an Objective-C framework such as Foundation will include it for you. Speaking of which, have you ever wondered why we always inherit from NSObject
?
NSObject
is a root class in Objective-C, meaning that it has no superclass, and most of the time we build classes on top of it by subclassing it. However we can also create our own root classes - we could have skipped NSObject
and just written this instead:
Or when using objc_allocateClassPair
we could have passed in NULL
for the parent class. If you try to compile this, clang
will give you a warning but it will still work:
objc.m:4:12: warning: class 'Foo' defined without specifying a base class [-Wobjc-root-class]
@interface Foo
^
objc.m:4:15: note: add a super class to fix this problem
@interface Foo
^
: NSObject
The shit hits the fan when we try to run it:
$ ./objc
objc[6045]: +[Foo alloc]: unrecognized selector sent to instance 0x106bc30f8 (no message forward handler is installed)
As it turns out NSObject
provides lots of boilerplate methods like alloc
and init
, as well as the methods described in NSObjectProtocol
. NSObjectProtocol
describes a bunch of functions that the runtime needs to query information about your class, stuff like what selectors it responds to, what its subclass is and so on. So if your root class doesn’t implement this, be prepared for snarky error messages from the runtime and a generally bad time.
objc[52822:942545] *** NSForwarding: warning: object 0x10c0f2100 of class 'Foo' does not implement methodSignatureForSelector: -- trouble ahead
objc[52822:942545] *** NSForwarding: warning: object 0x10c0f2100 of class 'Foo' does not implement doesNotRecognizeSelector: -- abort
You can implement this yourself but its not a great idea unless you really know what you’re doing. Hence why we usually let NSObject
take care of this.
Now that we know CoreFoundation and Objective-C, lets try working with the two together:
#include <CoreFoundation/CoreFoundation.h>
int main(int argc, char **argv) {
CFStringRef str = CFStringCreateWithCString(NULL, argv[1], kCFStringEncodingUTF8);
printf("Hello %s\n", CFStringGetCStringPtr(str, kCFStringEncodingUTF8));
return 0;
}
Hold on. This is exactly the same code as the one we did in C. Objective-C is a superset of C, so any valid C code will still compile fine:
But none of this takes advantage of Objective-C. And thats where Foundation comes in.
Foundation, the sequel to CoreFoundation and the prequel to Cocoa, is the Objective-C framework that provides all of those NS classes you know and love. Most of these NS classes build on top of the underlying CoreFoundation structs that we saw earlier, by providing Objective-C classes and methods for them.
#import <Foundation/Foundation.h>
int main(int argc, char **argv) {
NSString *str = [[NSString alloc] initWithUTF8String: argv[1]];
printf("Hello %s\n", [str UTF8String]);
}
We’re going to be working with Foundation for a bit so let’s update our makefile:
$ make foundation
clang -g -o foundation -framework Foundation foundation.m
$ ./foundation Foundation
Hello Foundation
In case you’re still curious here’s how we can do it directly through the Objective-C runtime:
#include <objc/objc-runtime.h>
#include <stdio.h>
int main() {
Class cls = objc_getClass("NSString");
id obj = objc_msgSend((id)cls, sel_getUid("alloc"));
obj = objc_msgSend(obj, sel_getUid("initWithUTF8String:"), "Hello world!");
printf("%s\n", (char*)objc_msgSend(obj, sel_getUid("UTF8String")));
}
The classes in Foundation are interchangable with their corresponding CoreFoundation types - You can cast a CFStringRef
to a NSString
, and vice-versa. You can even swap CFRelease
and [NSObject release]
. This is called toll-free bridging:
#import <Foundation/Foundation.h>
int main() {
CFDateRef date = CFDateCreate(kCFAllocatorDefault, 210678000);
NSDate *newDate = [NSDate dateWithTimeInterval:60 sinceDate:(NSDate*)date];
printf("%s\n", [[newDate description] UTF8String]);
[(id)date release];
}
$ make bridging
clang -g -o bridging -framework Foundation bridging.m
$ ./bridging
2007-09-05 09:41:00 +0000
On the side, if you’ve programmed in Objective-C before, you may have noticed we’ve been avoiding NSLog
. This is because I personally find its purpose is for logging error messages, not for debugging or user facing output. You also get some extra garbage in the output and it ends up being written to the system console which is a bit overkill for our purposes.
Let’s make the Objective-C experience more authentic: Remember when I said ARC didn’t exist yet? If you’ve been compiling the Objective-C code examples from the year 2010 or beyond, its probably been turned on by default. There’s a flag we can set to disable it:
The -g
flag was also added to have the compiler output .dysmb
symbols: these will be helpful when using external tools to debug and investigate our code later on. Now let’s start leaking memory!
#import <Foundation/Foundation.h>
NSMeasurement* measureString(NSString *string) {
double length = [string length];
NSUnit *unit = [NSUnitLength yards];
return [[NSMeasurement alloc] initWithDoubleValue:length unit:unit];
}
int main() {
NSMeasurementFormatter *formatter = [[NSMeasurementFormatter alloc] init];
[formatter setUnitOptions:NSMeasurementFormatterUnitOptionsNaturalScale];
NSString *shortString = @"s";
NSString *longString = @"e x t r a i r o n i c s t r i n g";
printf("%s\n", [[formatter stringFromMeasurement:measureString(shortString)] UTF8String]);
printf("%s\n", [[formatter stringFromMeasurement:measureString(longString)] UTF8String]);
}
Check out those cool new measurement APIs! Alas, we are leaking the NSMeasurement
s we alloc
inside measureString
. We can check for leaks using Instruments, which comes with Xcode.
$ instruments -t Leaks noArc
Instruments Trace Complete: /Users/luke/Repos/cocoa/objc/instrumentscli0.trace
The -t
flag specifies which template we want to use to record diagnostic information. Here we choose the Leaks template, for memory leaks. We then just pass it something we want to run. Instruments will go ahead and run our executable, collecting data and leaving it in the form of a .trace
file. Let’s open it:
There’s not much to see here. Everything seems to look fine, and the program only ran for less than a second. But we’re still leaking memory. It’s just that once the program finished, the operating system deallocated whatever memory it had leaked in the first place. It becomes a problem after a program has been running for a while, when it’s accumulated lots of individual leaks, hence why you see it more in long lived applications, rather than in command line executables. We can change our program to act more like the former:
for (int i = 0; i < 1000; i++) {
printf("%s\n", [[formatter stringFromMeasurement:measureString(shortString)] UTF8String]);
printf("%s\n", [[formatter stringFromMeasurement:measureString(longString)] UTF8String]);
[NSThread sleepForTimeInterval: 0.05];
}
$ instruments -t Leaks noArc
Instruments Trace Complete: /Users/luke/Repos/cocoa/objc/instrumentscli1.trace
$ open /Users/luke/Repos/cocoa/objc/instrumentscli1.trace
Now we’re talking. Around 2000 leaked objects, all NSMeasurement
s.
Thanks to the -g
flag, Instruments is able to give us lots of helpful information about where the leaks might have happened in our code. We can double click on the stack frame on the right to jump right to the line of code that a particular leak happened:
Instruments has lots of wild tools and features for debugging, like a side-by-side dissaembly viewer and even an instruction set reference built right in. But that’s for another day.
So how come we’re leaking memory anyway? With Core Foundation, any “ref” type we created we needed to call CFRelease
on, once we were finished with it. In Objective-C, any object we allocate ourselves with alloc
, we need to release with release
once we’re done.
for (int i = 0; i < 1000; i++) {
NSMeasurement *shortMeasurement = measureString(shortString);
NSMeasurement *longMeasurement = measureString(longString);
printf("%s\n", [[formatter stringFromMeasurement:shortMeasurement] UTF8String]);
printf("%s\n", [[formatter stringFromMeasurement:longMeasurement] UTF8String]);
[shortMeasurement release];
[longMeasurement release];
[NSThread sleepForTimeInterval: 0.05];
}
If we trace this with instruments again:
Leaks are gone! Let’s look at some trickier scenarios
We want to store a variable in Counter
, so we give it an ivar (instance variable) count
. Just like in other object-orientated languages, we can encapsulate it by providing getter and setter methods.
@implementation Counter
- (int)count {
return count;
}
- (void)setCount:(int)value {
count = value;
}
@end
Now we can use it like this:
This is such a common pattern, Objective-C has some syntactic sugar for it: Whenever we have a pair of methods called foo
and setFoo:
, we can also use dot syntax to call them instead:
In traditional Objective-C fashion, let’s superfluously turn our humble int
into an object, NSNumber
:
@interface Counter: NSObject {
NSNumber *count;
}
- (NSNumber*)count;
- (void)setCount:(NSNumber*)value;
@end
Changing the getter is easy:
And the setter too, right?
Congratulations, we’ve leaked memory! count
is now an object, so we have to start thinking about how it’s allocated and deallocated. We’re not releasing the old value so everytime this is called we end up leaving an old NSNumber
lying around. Let’s fix this:
Great! Don’t worry, this will work when count
is nil
since Objective-C lets you call methods on nil
perfectly fine (I don’t know if this is a good thing or not). Now to try it out.
Here we’re being good citizens by making sure to release
whatever we alloc
. But hold on - if we try to access c.count
now it will be deallocated, since we released it. This can cause memory exceptions: it may work when we run it since the memory might still be intact, but we can’t rely on it. We can try to catch it by compiling the program with Address Sanitizer:
$ clang -o counter -framework Foundation -fno-objc-arc -fsanitize=address -g counter.m
$ ./stopwatch
ASAN:DEADLYSIGNAL
=================================================================
==3628==ERROR: AddressSanitizer: SEGV on unknown address 0x7fff1d800018 (pc 0x7fff577f3e9d bp 0x7ffeec445670 sp 0x7ffeec4455f8 T0)
==3628==The signal is caused by a READ memory access.
#0 0x7fff577f3e9c in objc_msgSend (libobjc.A.dylib:x86_64h+0x6e9c)
#1 0x7fff5841b014 in start (libdyld.dylib:x86_64+0x1014)
...
We could simply not call [n release]
, but then this just gets confusing because it breaks the rule that we’re responsible for deallocating any memory we allocate in the first place. What we need to do is transfer ownership: basically, we want to give responsibility of the NSNumber
’s memory to Counter
. We do this with retain
:
What it does is it increments the object’s reference count by one:
Each object has a reference count: - Calling alloc
allocates it and starts its reference count off at 1 - Calling retain
increases the reference count by 1 - Calling release
decreases the recerence count by 1
Whenever its reference count reaches 0, it is deallocated, which is why so far we’ve been deallocating objects by calling release on it just once. By retaining value
we increase the reference count by 1 to 2, so that when we release it from outside it drops back to 1, preventing it from being deallocated.
We have to be careful to call [value retain]
before we call [count release]
, because if count
and value
both point to the same object then we could inadvertently deallocate it by releasing it down to 0. But rather than worrying about this, we can use [value autorelease]
which will defer calling release
until the current autorelease pool is released.
retain
also returns self which can be used to make our setter a bit more succint. But what’s an autorelease pool?
It marks whenever we should “drain” (release) all of the objects that have been marked for release with autorelease:
Checking what happens currently when don’t release the pool:
We can see the reference count keeps on going up because it never gets released. When its wrapped in an NSAutoreleasePool
though:
for (int i = 0; i < 10; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
c.count = c.count;
[pool release];
printf("%lu\n", [c.count retainCount]);
}
It gets released. There’s a nice syntax for this by the way:
for (int i = 0; i < 10; i++) {
@autoreleasepool {
c.count = c.count;
}
printf("%lu\n", [c.count retainCount]);
}
Usually we don’t need to worry about autorelease pools though - In a full Cocoa application NSRunLoop
will automatically call it every frame, but we will get to that later.
For now, you might be thinking this is all a huge pain just to create a single instance variable, and you’re right. Why not let the compiler generate all this stuff for us? The @property
keyword will synthesize the ivar, getter and setter for us all at once:
@interface Counter: NSObject
@property (retain) NSNumber *count;
@end
@implementation Counter
@end
int main() {
@autoreleasepool {
Counter *c = [[[Counter alloc] init] autorelease];
NSNumber *n = [[[NSNumber alloc] initWithFloat: 42.123] autorelease];
c.count = n;
printf("%s\n", [[c.count description] UTF8String]);
}
}
Nice! We’re also specifying that we want the setter to retain whatever’s passed to it with the (retain)
part.
But we’re not just done yet - what happens when Counter
gets deallocated itself?
NSNumber *n;
@autoreleasepool {
Counter *c = [[[Counter alloc] init] autorelease];
n = [[[NSNumber alloc] initWithFloat: 42.123] autorelease];
c.count = n;
}
printf("%lu\n", [n retainCount]);
Since c
now owns n
we’re responsible for releasing the reference we have to it inside Counter
. We do this by overriding dealloc
which gets called whenever its deallocated:
When using @property
, clang
will default to naming the ivar _foo
, but you can also choose what it will be named with @synthesize
. Finally, if you try to access n
afterwards Address Sanitizer will trap, so we know it’s been successfully deallocated!
Now lets throw away everything we just learnt:
@interface Counter: NSObject
@property (retain) NSNumber *count;
@end
@implementation Counter
@end
int main() {
Counter *c = [[Counter alloc] init];
NSNumber *n = [[NSNumber alloc] initWithFloat: 42.123];
c.count = n;
printf("%s\n", [[c.count description] UTF8String]);
}
Remove that cursed flag:
Fast-forward to 2009: Automatic Reference Counting (ARC) is here! It takes care of all of that memory management headache for us. When its enabled, calls to retain
and release
are automatically inserted by the compiler. It’s enabled by default: Turning it off is a very bad idea™. Everything from here on in will be with ARC.
Now we have all that cleared out the way, lets talk about what you came here for. Cocoa is the framework that powers OS X development. But as it turns out, Cocoa is acutally three smaller frameworks stacked on top of each other in a trench coat:
NSObject
and friends that we just saw in the last section.NSApplication
for GUI.If you take a look at the Cocoa framework under /System/Library/Cocoa.framework
, it’s only 39KB. Compare it to Foundation’s hefty 18.6MB, or AppKit which weighs in at a whopping 63.3MB as of 10.14.
Let’s make our first application. An application so basic, it doesn’t even need full Cocoa, just AppKit:
#import <AppKit/AppKit.h>
int main(int argc, char *argv[]) {
[NSApplication sharedApplication];
[NSApp run];
}
We instantiate the application for our process by calling sharedApplication
and then kick it off with [NSApp run]
. NSApp
is just a shortcut for reading [NSApplication sharedApplication]
:
And now we have an app running! But there’s nothing in the dock.
By default applications have an NSApplicationActivationPolicyProhibited
activation policy, which means that they can’t create windows or have menu bars. We need to set this to the regular policy which does allow us. While we’re at it, lets also automatically activate the app so it is front and center when we launch it.
#import <AppKit/AppKit.h>
int main(int argc, char *argv[]) {
[NSApplication sharedApplication];
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps: YES];
[NSApp run];
}
Now how do we get out?
NSMenu *menuBar = [[NSMenu alloc] init];
[NSApp setMainMenu: menuBar];
NSMenuItem *appMenuItem = [[NSMenuItem alloc] init];
[menuBar addItem: appMenuItem];
NSMenu *appMenu = [[NSMenu alloc] initWithTitle: @"Cocoa from Scratch"];
[appMenuItem setSubmenu: appMenu];
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle: @"Quit"
action: @selector(terminate:)
keyEquivalent: @"q"];
[appMenu addItem: quitMenuItem];
We can programatically create menus with the NSMenu
and NSMenuItem
classes. The main menu bar is an NSMenu
itself, as well as all its other submenus.
The signature of -[NSMenuItem initWithTitle:action:keyEquivalent:]
looks like this:
- (instancetype)initWithTitle:(NSString *)string
action:(SEL)selector
keyEquivalent:(NSString *)charCode;
You may notice SEL
from earlier, the type for efficiently referencing Objective-C methods. In the code above we used @selector(terminate:)
to create one for us, which in this case references a method on NSApplication
.
NSWindow *window = [[NSWindow alloc] initWithContentRect: NSMakeRect(0, 0, 200, 200)
styleMask: 0
backing: NSBackingStoreBuffered
defer: YES];
[window setTitle: @"NSWindow from Scratch"];
[window cascadeTopLeftFromPoint: NSMakePoint(20, 20)];
[window makeKeyAndOrderFront: nil];
cascadeTopLeftFromPoint:
positions the window from the top left of the screen with a bit of offset, which is nicer than our default absolute bottom left of (0, 0).
The NSWindow
initializer has some strange looking stuff going on. Let’s break it down:
initWithContentRect
: This is simply the size and position of the window when we first create it.backing
: All but one of the possible enum values are deprecated so we don’t really have a choice for this. It controls how the window is drawn, and has options for back in the day when memory was tight and CPUs had framebuffers.defer
: Waits until the window is moved before doing some work with window devices. Turning it on saves some memory.styleMask
: We can set some NSWindowStyleMask
bitmasks here to control the styling of the window. Let’s try it out with none of them just to see what its like:The result is fairly spartan. What about the just NSWindowStyleMaskTitled
on its own?
And if we throw on some extra flags:
NSWindowStyleMask mask = NSWindowStyleMaskTitled
| NSWindowStyleMaskClosable
| NSWindowStyleMaskMiniaturizable
| NSWindowStyleMaskResizable;
It starts to look pretty good! We get the traffic lights, resizing and even full-screen.
Some basic controls are NSTextField
and NSButton
:
NSTextField *textField = [NSTextField labelWithString: @"The time"];
TimeHandler *timeHandler = [[TimeHandler alloc] initWithTextField:textField];
NSButton *button = [NSButton buttonWithTitle: @"Get the Time"
target: timeHandler
action: @selector(updateTime)];
[[window contentView] addSubview: button];
[[window contentView] addSubview: textField];
Note that unlike UIKit, there is no such class as NSLabel
, NSTextField
can act as both a readonly label and an editable field by setting specific properties. labelWithString:
is a shortcut to create an NSTextField
with these properties.
Our TimeHandler
class looks like this:
@interface TimeHandler: NSObject
@property (retain) NSTextField *textField;
- (void)updateTime;
@end
@implementation TimeHandler
- (id)initWithTextField:(NSTextField*)textField {
self.textField = textField;
[super init];
return self;
}
- (void)updateTime {
NSDate *currentDate = [NSDate date];
[[self textField] setStringValue: [currentDate description]];
}
@end
If we build and run this, we get this:
Yikes, the views overlap. Now we could position them by hand ourselves with, setFrame:
, but this is a pain.
It’s now 2011, and we’re now running shiny OS X Lion. There’s a hip new technology called “Auto Layout” and it allows us to define or UI with constraints:
[button setTranslatesAutoresizingMaskIntoConstraints: NO];
[textField setTranslatesAutoresizingMaskIntoConstraints: NO];
[button.leadingAnchor constraintEqualToAnchor: window.contentView.leadingAnchor constant: 20].active = YES;
[textField.leadingAnchor constraintEqualToAnchor: button.trailingAnchor constant: 20].active = YES;
[window.contentView.trailingAnchor constraintGreaterThanOrEqualToAnchor: textField.trailingAnchor constant: 20].active = YES;
[button.topAnchor constraintEqualToAnchor: window.contentView.topAnchor constant: 20].active = YES;
[textField.topAnchor constraintEqualToAnchor: button.topAnchor constant: 0].active = YES;
[window.contentView.bottomAnchor constraintGreaterThanOrEqualToAnchor: textField.bottomAnchor constant: 20].active = YES;
We can resize the window and it’ll stop before it cuts off the views. There’s also the visual format language:
NSDictionary *views = @{ @"button": button, @"textField": textField };
NSArray<NSLayoutConstraint*>* hConstraints = [NSLayoutConstraint constraintsWithVisualFormat: @"|->=20-[button]-20-[textField]->=20-|"
options: NSLayoutFormatAlignAllCenterY
metrics: @{}
views: views];
[NSLayoutConstraint activateConstraints: hConstraints];
NSArray<NSLayoutConstraint*>* vConstraints = [NSLayoutConstraint constraintsWithVisualFormat: @"V:|-[button]->=20-|"
options: 0
metrics: @{}
views: views];
[NSLayoutConstraint activateConstraints: vConstraints];
If you right click on any OS X application, you’ll see an option to “Show Package Contents”.
$ tree -L 2 Photos.app
Photos.app
└── Contents
├── Info.plist
├── Library
├── MacOS
├── PkgInfo
├── PlugIns
├── Resources
├── XPCServices
├── _CodeSignature
└── version.plist
.app
s are actually directories, but OS X hides the contents as if they were a single file because they are bundles. Frameworks, plugins, screensavers and more are all bundles too. Check out the NSBundle
Foundation class for more information.