Recently I started thinking about writing something simple
in Objective C – you know, that weird Apple language used for Mac programming…
Not that I’m planning to switch from Ruby to ObjC – I rather treat this as
a kind of a challenge (“what, I can’t learn this thing? of course
I can!” :), besides, there is no language good for everything, and
there are some low-level things that you really shouldn’t write in Ruby, like
plugins for Finder, Quicklook, etc. Also, I remember reading somewhere that
it’s good for a programmer to learn a new language each year – so, two years
ago Python, last year Ruby, this year Objective C? :)
I googled for some quick tutorials, read a couple of them,
and I realised that this language is really very simple, it just looks
complicated because of that syntax and symbols; but once you start translating
everything in your head, it all starts looking very familiar. Of course, it’s a
different thing to understand what’s going on in someone’s code and to be able
to write your own (for that, you also need to know some libraries…); but in
this case, it’s pretty easy to go from “OMG WTF dude what’s this??” to a state
when you can look at ObjC code and understand it as if it was written in Java
or something else.
I made some notes while I was reading the tutorials,
and I’m publishing them here so that maybe someone can learn this faster than
I did – it’s just a “diff”, so I didn’t describe in detail anything
that is obvious for someone who knows C, Java, Ruby, etc. You can treat this as
a kind of poor man’s tutorial… Although I’m warning you that I spent a
total of about one day looking at ObjC code, so some of what I’ve written here
may not be completely correct, or may even be completely wrong. This is all
based on what I read, not on my own experiences (which I have almost
none). This product is provided without any warranty :)
The language
·
ObjC is a superset
of C. This means that all properly written C programs should
theoretically compile as ObjC programs without making any changes. Everything
that works in C, works in ObjC. ObjC adds some new features to C, including OOP
and Smalltalk-like message
passing.
·
Objective C is a language that is compiled to native machine code, just like C/C++, and
that should probably make it faster than Java, Python or Ruby, which run in
virtual machines.
·
On the other hand, ObjC has a lot of features that make it much more dynamic than
C++, and more similar to Ruby or Python. ObjC supports both static typing anddynamic typing, depending on how variables are declared
(more about this later). Unfortunately, all that makes it slower than C++.
I don’t know how much slower though, I haven’t been able to find a
single benchmark… I guess I need to write one myself.
·
The newest version of the language is 2.0, which was released
recently (with Leopard); it adds some cool new features, like garbage
collection, properties, or for-each loops.
Source code
·
Files are organized in a way similar to C/C++: there are header
files (*.h), which contain interface declarations for classes, and source files
(*.m), which provide their implementations. I have no idea why they
selected the extension .m – even Google doesn’t seem to know that…
o
Update: Thanks to Stack Overflow, I’ve learned that the .m extension means
either “messages” or “methods” :)
·
In ObjC there are no namespaces like in other languages;
instead, to be able to distinguish classes coming from different frameworks, name prefixes are
used. For example, a lot of core ObjC classes have the prefix “NS” (NSString,
NSObject, etc.), which is short for “NextStep” (it’s a Unix environment created
in the 90s, which was used as a base for MacOSX when it was developed).
·
Program execution starts in the int main() function,
as expected.
·
Headers can be included using #include, but it’s better to use #import, which works just like include, except it never includes the
same header twice (so you don’t have to do any stupid #ifndef / #define stuff
like in C++).
·
Keywords added by ObjC start with “@” to distinguish them from those from C.
·
Programs can be compiled using gcc, like C/C++, but you need to
add-framework Cocoa to use the ObjC standard library (or, as it is called in ObjC
world,“framework”).
Methods
·
That’s the single weirdest thing in ObjC. You don’t call methods
with foo.bar, orfoo->bar, or foo-whatever-bar. You call them with [foo bar]… and obj.foo.barbecomes [[obj foo] bar]. A bit
inconvenient IMHO, because when you write a sequence “object method method
method …” then at the end you have to go back to the beginning and add an
appropriate number of opening brackets to match the closing brackets… But once
you install a proper regular expression in your head that changes bracket notation
to dots, it at least becomes readable.
·
Another unusual thing about methods is that parameters in most
cases are given labels, which
are used in the method call. So method calls with more than one parameter look
like this:
|
[appleStore buyMacs:
2 withRAM: 4 withCPU: 2.66]
|
·
This is one of the ideas that were taken from Smalltalk. It does
make the code longer, but on the other hand, it becomes much more readable.
Besides, it’s quite similar to Python’sfoo=bar parameters and Ruby’s :foo => bar, so it’s nothing really new.
·
The labels actually form the full name of the method. So the
method above is referred to as buyMacs:withRAM:withCPU:.
·
Methods can’t be overloaded by parameter type, so there can’t be
two methodsfoo(int) and foo(char*). But they can be overloaded by labels, so you can have methods buyMacs:, buyMacs:withRAM:, buyMacs:withMonitors: and so on.
·
The above is often used as a replacement for default parameter values in
functions, which don’t exist in ObjC. So if you want the second parameter to
have a default value, you must write two methods, one with one parameter, and
the other with two…
·
Like in Python or Ruby, calling a method that doesn’t exist
doesn’t throw a compiler error, but a runtime exception, which can be caught
and handled.
·
What’s more, calls to not existing methods can be intercepted
and handled before an exception is thrown. Does that remind you of something?
Ruby’s method_missing, of
course! Here, the method that can be used for this is forwardInvocation:.
·
Tutorial authors generally insist that in ObjC you don’t call
methods, you “send
messages”. I think it’s just a matter of naming, I can’t see
how the method calls in ObjC are different that, say, in Python… except the
brackets, of course. But I may be wrong. (Methods are also called “selectors” sometimes.)
Data types
·
ObjC has both
primitive and object types, like C++ or Java. Unlike C++, it doesn’t
have value types for objects, only pointers, so there’s no Foo x, only Foo *x.
·
Normal C data types (int, float, etc.) can be used, although
that’s probably not always the recommended way. For example, there are types
like NSInteger, NSUInteger, orCGFloat, which are architecture-safe versions of int, unsigned int,
float and so on. There is also a class named NSNumber used
for boxing/unboxing primitive types in cases when we need to pass an object
somewhere.
·
Apart from C strings (char *),
there are also ObjC strings (NSString). To
create the latter on the fly, use the at sign: @"some text". NSStrings can be
converted to char*using cString (deprecated)
or UTF8String methods of NSString (for example, when you need to use them as
parameters to printf()).
·
Boolean is called BOOL, and its values are… you’ll never guess: YES and NO :)
·
There is a generic type called id, which basically means “any object”. This is something like Object*, but
there’s a “tiny” difference. These two letters are a door to the world of
dynamic typing. If you declare a variable as Foo* x, it works like in C++ – try to call something that isn’t
available in class Foo, and the compiler will notice that (although in ObjC it
doesn’t exit with an error, it just prints a warning – because sometimes the
code may be correct, even if this isn’t obvious for the compiler). But if you
declare a variable as id x, it works like in Ruby – the compiler lets you call anything
you want on it, and only at runtime it will be determined if the object can
handle that method or not.
·
Null is called nil. And, as in Ruby, apparently you can call methods on it and the
universe won’t implode. Even more, you can call any method on it, and it will
always return nil – this means that often you don’t even have to check if an
object is nil. You just call whatever you want on it, and if it’s nil, it just
sits there and ignores you. It may make coding a bit simpler, but on the other
hand it probably makes debugging much harder – you don’t immediately get an
exception if something which shouldn’t be nil is nil…
·
For pointers to classes, nil is written in uppercase (Nil). Don’t ask me why…
·
Just like NSNumber for
ints, there is a wrapper for the nil value – the NSNull class.
You can’t put a nil into an NSArray, but you can put a NSNull there (usage: [NSNull null]).
·
SEL (short for “selector”) is a type for pointers to methods. To get a SEL pointer,
use the keyword @selector with
the method signature, e.g.@selector(methodName:withArg:).
·
All objects have a method named description, which is the same as toString in Java.
·
A lot of the built-in object types have mutable and non-mutable versions: there’sNSString and NSMutableString, NSArray and NSMutableArray, and so forth.
Classes
·
class interfaces (*.h) are declared using the keyword @interface:
|
@interface MyClass:
NSObject { // <-- NSObject is the superclass
// now, a list of instance
variables:
int
foo;
...
}
// and now - a list
of methods:
- (void) print;
- (void) setValue:
(int) v;
- (void) setValue:
(int) v andAnother: (int) a;
- (int) value;
- foo;
+ (int)
numberOfInstances;
+ (void)
initialize; // class initializer
@end
|
·
Method definition syntax is quite different from C/C++, but is
rather easy to understand. Methods with a minus sign are instance methods, and
those with a plus sign are class (static) methods.
·
ObjC uses single
inheritance, like Java or Ruby.
·
If you don’t mention the returned type explicitly (like in
method foo above), the default type is id.
·
If a class has a class method named initialize, it is
called once at the moment when the class is used for the first time (like static constructors in C#).
·
Another keyword used in interface declarations, @class, is
used for forward
declarations, like in C++. If you need to mention one class in the interface
of another, to reduce coupling between classes you can declare it with @class Klass instead of#import <klass.h>.
·
Class implementation (*.m) is enclosed in @implementation … @end keywords:
|
@implementation MyClass
- (void) setValue:
(int) v {
...
}
@end
|
·
Variable declarations don’t have plus/minus signs in front of
them, so if you want to have a static variable in a class, declare it using the
C keyword static in the
header file, outside the @interface block (e.g. static int instances;).
·
Instance variables are accessed using the arrow operator ->, e.g. person->name.
·
Variable access is set using the keywords @public, @private, and @protected;
they’re used like in C++ or Ruby, not like in Java – you put a keyword before a
group of variables, not before each variable. Default access is @protected.
Methods, on the other hand, are always public; you can simulate private methods
by not adding their signatures to the @interface, writing only implementations
in @implementation.
·
ObjC 2.0 introduced “properties”,
which are similar to object properties in other languages:
o
Inside @interface, in the method block you declare e.g. @property int age;. This
works the same as if you manually declared a getter (int) age (in ObjC getters don’t have a prefix “get”) and a setter (void) setAge: (int) age. You
still have to mention a matching instance variable in the variable block.
o
Inside @implementation, you write @synthesize age;, which generates the implementations of the
getter and setter. You can also provide your own custom implementations.
o
To increase confusion, properties aren’t accessed with [obj age] or withobj->age, but
with a third syntax: obj.age…
o
Properties can have additional options inserted in parentheses
after the @property keyword, like: @property (readonly) int age;. The readonly option
means that only a getter will be generated.
Categories and posing
·
In ObjC, apart from inheriting from classes, you can also… add
code to existing classes. Even built-in classes. Sounds terribly Ruby-ish,
doesn’t it? :) It’s not as powerful as Ruby’s “open classes” mechanism
(e.g. you can’t add new instance variables), but it seems nice anyway. Here
it’s called “categories” (because
if you have a long class, you can divide its methods into several categories
and put them in different files), and here’s how it’s done:
|
@interface ExistingClass (SomeNewName)
...methods...
@end
|
·
Then you include both the old header and the new header in the
implementation file, and voila – you have a class with new methods added.
·
There’s also a second, similar mechanism called “posing”. With posing, instead of
adding methods to a chosen class, you create a subclass of that class, and then
you declare that the subclass should “pose” as the superclass, which means that
the old class will just be replaced with the new one. You do that using poseAsClass method:
[SubClass poseAsClass: [SuperClass class]];.
[SubClass poseAsClass: [SuperClass class]];.
·
Apparently posing is now deprecated, so it shouldn’t really be
used anymore; I’ve just included it here so that you know what this means when
you see it.
Loops, conditions and operators
·
Those things generally work just like in C – for, while, if…
everything looks as you can expect.
·
For ObjC collections, you can use Java-like enumerators:
|
NSEnumerator *enum =
[array objectEnumerator];
while (i
= [enum nextObject]) {
...
}
|
·
In ObjC 2.0, there are also for-each
loops, called “fast
enumeration” (because it’s apparently more efficient than the old way):
|
for (Foo *foo
in list) {
...
}
|
·
Equality checks work similar to Java: the == operator compares object
identity, and to compare values you need to call a function: [x isEqual: y].
Objects
·
Inside object methods, the reference to the object is called self, like
in Python or Ruby.
·
To call the superclass’s implementations of functions, use the super reference. For example, [super foo] calls
super’s version of the method foo on self.
·
Object creation is two-phase: first you call alloc on the class to allocate memory, theninit (or
sometimes a different, specialized initializer) on the object to initialize it;
so it looks like this:
|
Mac *box
= [[Mac alloc] init];
|
·
If I remember correctly, it’s the same in Ruby, but there
it happens behind the scenes –Foo#allocate and Foo#initialize are
called when you write Foo.new.
·
If you want to have several constructor variants, you can write
them like this:
|
- (ClassName*)
initWithParam: (int) one andAnother: (int) two {
// first, call any initializations
defined in superclass
self
= [super init];
if
(self) { //
self could be nil if something went wrong
// ... set some instance variables
and stuff
}
return self;
// won't work without this
}
|
·
And then you use it like this:
|
ClassName* var =
[[ClassName alloc] initWithParam: 5
andAnother: 6];
|
Protocols
·
Protocols are basically Java’s interfaces – lists of methods
without implementation, which can be implemented by classes. Protocol
declaration looks similar to a class declaration:
|
@protocol FooInterface
- (int) foo;
- (void) bar;
@end
|
·
To mark a class as implementing an interface, add the interface
name in triangle brackets:
|
@interface MyClass:
NSObject <Printable, FooInterface>
|
·
The methods declared in the implemented protocol don’t need to
be listed inside the@interface declaration.
·
Like in Java, you can declare variables that point to anything
that implements an interface:
|
id foo
= [[MyClass alloc] init];
|
·
You can assign any object to variable foo, as long as it
implements FooInterface.
·
There are also so-called “informal
protocols”, which are just lists of methods published somewhere in the
documentation that your class has to or can define, but the fact of
implementing the protocol isn’t explicitly mentioned in the code. This is often
done by adding a category to a class, named the same as the protocol, which
contains the required methods.
Memory management
·
Old version: like in C, on which ObjC is based, you have to
remember about releasing all memory that you allocate, and if you forget about
something, you get memory leaks (and if you release too much, you get
segfaults).
·
New version (since 2.0): ObjC now includes a garbage collector (!), so
you just turn on one option in Xcode project settings and forget about memory
management :) It’s quite unusual for a fully compiled language to have a
garbage collector, and that makes ObjC rather unique.
·
But the introduction of garbage collector in 2.0 doesn’t mean
that everybody will start using it right now (e.g. it isn’t available on the
iPhone yet), so here are a few details about memory management:
o
Even without garbage collector, memory management is a bit
simpler than in C/C++. The difference is that you don’t have to think about who
exactly should be responsible for destroying an object. Every time you store a
reference to it, you call[obj retain], which
increases its “retain count” (number
of references); and when you no longer need it, you call [obj release]. If
this makes the retain count reach 0 (it starts at 1 after alloc/init), the
object is released, if not – nothing happens. So every object can have several
“owners” and they all dispose of it in the same way.
o
When an object is destroyed, its destructor – a method named dealloc – is called. In this method, you can call release on all
references to other objects that you store in that object. You should also call
super’s dealloc.
o
When garbage collector is turned on, all methods like retain,
release, etc. are just ignored, and dealloc is not called. Instead, the method finalize gets called when an object is garbage collected (like in Java).
o
Another way to manage memory is something called “autorelease pools”.
They’re kind of multi object managers, instances of NSAutoreleasePool class, and you can assign objects to them using [obj autorelease]. Then,
when an autorelease pool is released (using [pool drain] – nice
terminology :), it automatically releases all objects assigned to it.
o
You have to be careful about which objects are already
autoreleased when you get them, and which should be (auto)released by you. For
example, objects produced not with alloc, but using static methods in basic classes like NSString, e.g.[NSString stringWithFormat: ...] (which
is something like sprintf()), are usually already autoreleased. You can’t call release on
them, because this will cause a segfault when the pool tries to release them.
The general rule is: when you create an object using alloc or copy methods,
you need to release it, and if you get it through some other means, usually
someone else will release it.
o
With release/retain, you also have to be careful about how you
write property setters (if you don’t use @synthesize). You have to both release the old object and
retain the new one; but you can’t release before you retain, because if they
point to the same object, it would be deallocated at the moment of release. So
you have to either call retain before release, or check the pointers if they’re
not the same at the beginning of the setter, or use autorelease instead of
release.
Reflection
·
ObjC is more dynamic than C or C++, despite the fact that it’s
really a strongly typed, compiled language. Here are some examples of functions
related to reflection:
o
isKindOfClass, isMemberOfClass – like kind_of?, instance_of? in Ruby
o
respondsToSelector, instancesRespondToSelector – tells if an object has a specific method (like respond_to? in
Ruby)
o
performSelector – calls a specified method on the object (like
send(:method_name))
o
conformsToProtocol(@protocol(FooInterface)) – tells if a class implements a protocol
Exceptions and debugging
·
Exceptions work like in every other modern language: they are
represented by classes inheriting (usually) from NSException, and you use the
keywords @try, @catch and@finally to
intercept exceptions thrown inside a block and handle them. Use@throw e to
throw exception e, and @throw to
re-throw an exception in the catch block (like in Python/Ruby).
·
Two methods that may be useful in debugging:
o
NSAssert(x != 0, @"X
must not be zero") –
throws exception if x == 0
o
NSLog(@"foo bar
%@", x) –
outputs the string (+ some info) on stderr, and also adds the same line to
/var/log/system.log (which can be viewed with “Console” application).
And that’s all… if you didn’t know anything about Objective C
before, and you did have the patience to read all of this, then I hope you
have now some idea what this is all about. Anyway, now you shouldn’t run away
screaming when you see some ObjC code somewhere… At least I don’t do that
anymore ;)
0 nhận xét :
Đăng nhận xét