LearningObjectiveC

From HerzbubeWiki
Jump to: navigation, search

Contents

Audience

First and foremost, myself. I use this document to keep notes while I go, about stuff that seems interesting or non-obvious, solutions to problems I encountered, and whatever else comes to my mind.

If you are reading this document, please don't expect anything of it. As it reflects my thought processes and information organization preferences, it is probably confusing to other people, and might be incomplete to boot. Nevertheless, questions or critique are welcome, and I will try my best to answer both.


Why should I learn Objective-C

Some years ago I decided to learn Cocoa in order to better be able to torment the Mac world with my mediocre software. I found that I had two choices of programming languages: Java and Objective-C. Thinking for only a second, I immediately decided to go for Java, citing two main reasons:

  • it would be useful for my professional "career" to acquire some skills in a widely spread language (Java)
  • a dynamic typed language (ObjC) seemed a strange beast that I had better not touch; I would only be wasting my time in an obscure corner of software development...

These days, my situation job-wise as well as my interests have changed. I do C++ programming for a living, so I don't have to think hard anymore about turning my free-time engagements into a possible profit. Also, my experience with C++ has changed my point of view, in regard to dynamic typed languages, to one of high curiosity (mixed with a bit of apprehension): is the promise of "unlimited" flexibility that these languages make a reality, or does it all end in chaos? And last but not least, after 2 1/2 years of C++ hacking, to learn a different style of programming simply has an extremely strong appeal for me.


Starting points

The following Internet resources were helpful to me:

Note: in my opinion [3] is a poor teaching document, at least in the version from 2006-05-23 that I have read. Although it covers the important issues, the document's two main problems are:

(1) Things are often explained in the wrong order, i.e. something is explained on the basis of knowledge that is introduced only later. This makes it necessary to read some passages without completely understanding them, only to come back later and re-read everything, this time with the increased knowledge of later chapters. An example for this is the introduction of autorelease pools a long time after the main chapter starts talking about the autorelease message.

(2) Sometimes the text is confusing, either because it is simply poorly written, or because it reveals itself as redundant information about a problem that was already covered. An example for this is the chapter "Implementing Object Copy", with the especially horrible passage about NSCopyObject() - maybe I am too stupid, but I had to read the stuff 3 or 4 times until I thought I had understood everything, not completely but sufficiently.


First impressions

The first important facts that I learned from [1] is that ObjC, as opposed to C++,

  • does not support multiple inheritance (good riddance)
  • does not support operator overloading (sometimes useful, but won't be missed much)
  • does not support argument overloading (uh-oh, this looks bad)
  • polymorphism cannot be "turned off", all methods have it by default (OTOH, this looks good; finally bye-bye to that enervating "virtual" keyword)

Farewell to some comfortable notions... am I really sure I want to go on with this?


Protocols

Protocols are something new, at least from a syntactic point of view. Semantically speaking, they are nothing else than interfaces. ObjC allows the programmer to declare a set of methods, unattached to a class, as a protocol. A class can adopt a protocol and implement the protocol's methods. It then conforms' to the protocol.

See further down for more details.


Outlets and Connections

"Outlet" is just a fancy name for a member variable of an object that stores a reference to another object. The term "outlet" is used because it describes the purpose of such variables: it records the outlet for messages sent by the object that "owns" the outlet.

When an object reference is stored in an outlet, a connection is established between the owner object of the outlet and the object that is being referenced. [1] discusses the connection types "extrinsic" and "intrinsic"; for me these connection types are nothing else than the UML relation types "association", "aggegration" and "composition":

  • an extrinsic connection is an association
  • an intrinsic connection is a composition
  • aggregation is neither clearly extrinsic nor clearly intrinsic; however, [1] makes a strong point of intrinsically related objects having to be destroyed/archived together, which is not a necessity for aggregation related objects; therefore I prefer to say that aggregation tends to be more extrinsic than intrinsic


Objects

  • data type id is defined as a pointer to an object (something along the lines of void*)
  • keyword nil is defined as a null object pointer
  • id and nil are both defined in objc/objc.h
  • instance variable isa references the class object (see below); the variable's type is Class
  • isMemberOfClass: (defined in NSObject) checks if an object is an instance of exactly the given class
  • isKindOfClass: (defined in NSObject) checks if an object is an instance of, or inherits from, the given class


Messages

Regular use

The following example sends the object myRect the message setWidth:height, using two arguments. The return value is placed into bSuccess

BOOL bSuccess = [myRect setWidth:10.0 height:15.0];

Nested messages look like this:

[myRect setPrimaryColor:[otherRect primaryColor]];

Messages to the class object can be sent via the class name (although this is not the only way to do it):

int iVersionNumber = [Rectangle version];

The actual method that is invoked by a message is determined at runtime. The method name in a message serves to select the correct method. Therefore, method names in messages are often referred to as "selectors".

[1] points out that "A method has automatic access to the receiving object's instance variables." My initial reaction to this was "duh!", but on second thought I found it very interesting that this information needs pointing out. I mentally stepped back and realized that what I perceived as a natural thing is actually not so obvious to someone who thinks of objects and messages as two separate things. I guess I still have to learn the OO concept of "sending a message" - in my mind, I still think of "calling a method", where "method" is just another word for "function".


Dot syntax / dot operator

The so-called "dot syntax" uses a dot operator with the intent to invoke accessor methods of a property. See the section about Declared Properties for details about properties.

Example:

float w = anObject.width;
anotherObject.width = w;

The dot syntax is purely "syntactic sugar": When encountering dot syntax, the compiler automatically generates code that sends messages whose names correspond to the properties used. So what happens if the above example is compiled is that the compiler generates the following code:

float w = [anObject getWidth];
[anotherObject setWidth:w];

It is possible to "chain" properties:

anObject.text.font = "foo";


Notes

  • It is not possible to use the dot syntax with untyped objects, i.e. objects whose type is id
  • The compiler is capable of distinguishing between property access and dot syntax used for accessing struct members and the like
  • Accessing properties of self requires the use of self, otherwise the property's instance variable is used. For instance
self.width = 12.7;  // accesses property via setter method
width = 12.7;       // directly accesses the instance variable
  • Although possible, it is not advised to use dot syntax with something else than declared properties


Classes

All classes ultimately inherit from a common base class, the root class NSObject (it is theoretically possible to define a new root class, but there usually is no point to it).

A class cannot define an instance variable that has the same name as an instance variable that it inherits from one of its superclasses. A class may not have the same name as a global variable (they exist in the same scope).

Since ObjC does not support method overloading, only the method name is relevant when a subclass wants to override a method in one of its superclasses.

To create a new instance of a class, send a message alloc to the class object:

id myRect = [Rectangle alloc]

All instance variables (except isA) are initialized to 0. Before anything else can be done with the instance, it must be initialized. To do this, send the message init to the instance:

id myRect = [[Rectangle alloc] init]

Some classes define alternative initialization methods, but the names of such methods should always begin with init (e.g. initWithPosition:size:)


Class objects

The compiler creates a so-called class object for each class. This object stores information about the class such as its name, its instance variables, its methods and their implementations. At runtime, the class object allows introspection through this information, and it also serves as a factory object that creates the actual instances of the class.

A class object does not have instance variables and cannot perform instance methods. It is possible, though, to define class methods specifically for the class object. Class methods are inherited from superclasses as usual. As for class variables, there is no such thing, but the closest thing to them is static variables.

To get a reference to the class object, use the method class and store the result in a variable of type id or Class:

id rectClass = [Rectangle class]
id aClass = [anObject class]
Class rectClass = [Rectangle class]
Class aClass = [anObject class]

The compiler creates a metaclass object for each class that describes the class object in the same way as the class object describes instances of the class. The metaclass object is only available to the runtime system.

To initialize class objects, the runtime system sends the message initialize to each class object

  • before the class object receives any other messages
  • after the superclass object has received the initialize message

If a class does not implement an initialize method, its superclass object will receive the initialize message twice. It must therefore somehow guard against multiple execution, e.g. by using a static BOOL variable, or as demonstrated in the following example (taken from NSObject docs):

+ (void) initialize
{
  if (self == [MyClass class])
  {
       /* put initialization code here */
  }
}

A class object must not send the initialize message to its superclass object, as this is already done by the runtime system.

If a class object receives a message for which it has no class method, the runtime system tries to determine if an instance method of NSObject is suitable to respond to the message.

If the class name is available as a string, a reference to the class object is available through the NSClassFromString function. If the string is not a valid class name, the function returns nil.

NSString* className;
[...]
id aClass = NSClassFromString(className);

The other way round, to get at the name of an object's class, use NSStringFromClass:

NSString* className = NSStringFromClass([self class]);


Implementation

The .h file contains the interface (C++ hackers note the missing semicolon):

#import "SuperClass.h"

@class NSColor;

@interface ClassName : SuperClass
{
   // instance variable declarations; use @private, @protected (the default)
   // and @public
   float width;
   float height;
   NSColor* fillColor;
}
// method declarations; class methods have a '+' prefix, instance
// methods a '-' prefix; class and instance methods can be mixed;
// return and argument types are declared as a C cast; if a type
// declaration is missing, the default type ''id'' is assumed
+ (id) alloc;
- (void) display;
- (float) radius;
- (void) setWidth:(float)width height:(float)height;
- (void) setPrimaryColor:(NSColor*)aColor;
- makeGroup:group, ...;
@end
<pre>


The .m file contains the implementation:
<pre>
#import "ClassName.h"

@implementation ClassName

// method definitions
+ (id) alloc
{
  // ...
}
- (void) setFilled:(BOOL)flag
{
  filled = flag;   // filled is an instance variable
  // somehow obtain a ClassName object and fill it into anotherClass
  // -> direct access to the member of the other object is possible
  //    because the other object has the same class
  ClassName* anotherClass;
  anotherClass->filled = flag;
}
// methods may appear here that were not mentioned in the
// interface; these methods are implementation details
- (void) internalMethod
{
  // ...
}

@end


Selectors

Method names are compiled into unique selectors (two methods with the same name have the same selector). The type SEL refers to a selector and can be used to declare a variable:

SEL setWidthHeight;
// assign a value at compile time (more efficient)
setWidthHeight = @selector(setWidth:height:);
// assign a value from string at runtime
setWidthHeight = NSSelectorFromString(aBuffer);
// convert back to string
NSString* method = NSStringFromSelector(setWidthHeight);


When a message is sent to an object, the runtime system generates a selector and uses that selector to find the proper method that should be called. It starts with the class of the object and - if no suitable method is found - "walks" up the inheritance tree towards the root class, until a suitable method is found. The system then calls the method with the arguments supplied to the message.

Because selectors are bound to method names, two methods with the same name must have the same return type and the same argument types. This is why a method is named "setWidth:height:" and not just "setWidth:". Note that identically named class and instance methods can have different return/argument types. Also note that there is no restriction about types when a message is sent to a statically typed object - in this case the compiler knows what to do.

NSObject defines methods that can be used to send messages with variable selectors:

  • performSelector:
  • performSelector:withObject:
  • performSelector:withObject:withObject:

All performSelector methods return an id, therefore whoever uses performSelector to call a method that returns a different type must cast to the proper type. For instance

id receiver = getTheReceiver();
SEL selector = getTheSelector();
Rectangle* foo = (Rectangle*) [receiver performSelector:selector];


To test whether an object is able to receive a message, NSObject defines the method respondsToSelector:

BOOL canDoIt = [anObject respondsToSelector:@selector(setOrigin::)];

A method has two hidden arguments that are inserted by the compiler:

  • self refers to the object that receives the message
  • _cmd refers to the method's selector

To send a message to the superclass, use the keyword super.


Subclassing

Overriding methods

  • Methods that override superclass methods do not need to be re-declared in the subclass interface
  • It is, however, good practice to do so, because an observer may only look at the interface and will still see which methods are implemented by the class
  • Instead of re-declaring an overriding method in the public interface, it could also be re-declared in a private category (see the section about Categories)
  • In cases with well-known superclass methods (e.g. dealloc) it will also be obvious that overriding is taking place
  • Finally, declaring the method will generate a compiler warning if one forgets to actually implement the method


Designated Initializer

A subclass must guarantee that all init methods inherited from superclasses work. This is done by overriding all inherited init methods.

One way of implementing these init methods would be to call super's version, followed by initialization of instance variables not covered by arguments of the inherited init method. This leads to duplication of code, as every re-implementation of an inherited init method needs to do the same variable initialization over and over again.

A better alternative is to define a "designated initializer" method in each class. Usually this is the init method with the most arguments, but not always. All init methods then follow these two rules:

  • an init method that is not the designated initializer always calls the designated initializer
  • the designated initializer always calls super's designated initializer

Graphically speaking :-) all init method implementations of a class are "funnelled" into the designated initializer, which in turn "tunnels" upwards to the designated initializer of the superclass.


Abstract classes

Objective-C does not support the concept of abstract classes:

  • There is no abstract keyword as in other languages (e.g. Java)
  • A base class cannot declare methods, then leave them unimplemented (e.g. pure virtual methods in C++)
  • In the same vein, a class cannot partially adopt a protocol and leave the rest to be implemented by subclasses


An oblique way to force subclasses to override a method is to implement the method like this:

- (void) doIt
{
   [self doesNotRecognizeSelector:_cmd];
}

Notes about this (taken from the NSObject class reference):

  • doesNotRecognizeSelector: raises an NSInvalidArgumentException
  • The _cmd variable is a hidden argument of type SEL that is passed to every method; its value is the current selector
  • The runtime system invokes this method whenever an object receives a message it can't respond to or forward
  • Invoking the method in a baseclass implementation thus prevents subclasses from inheriting the implementation without overriding it
  • An alternative usage (the one cited in the NSObject class reference) is to make sure in a subclass that a method inherited from a baseclass is never invoked. For instance, an NSObject subclass might want to prevent being copied, so it overrides copy in the way shown above.


Categories

Overview

A category adds methods (not variables) to a class defined elsewhere:

  • A category has a name (except for "Class extensions", see the next section)
  • The number of categories a class can have is not restricted
  • No two categories of the same class may have the same name
  • Category methods are inherited by subclasses
  • Instance variables (even if @private) can be accessed from within category methods
  • Although not recommended, category methods may be used to override methods declared in the class interface
  • A category method may be used to override methods in the superclass
  • A category method cannot override methods in another category


How it's done:

// ---------- the .h file ----------
// This is not required if the .h file does not refer to instance variables
#import "ClassName.h"
@interface ClassName(CategoryName)
// method declarations
@end


// ---------- the .m file ----------
#import "CategoryName.h"
@implementation ClassName(CategoryName)
// method definitions
@end


Class extensions

Objective-C 2.0 introduced a feature called "class extensions", sometimes aka "anonymous categories". In code, this looks just like a category without a name:

@interface ClassName()
// some declarations
@end

From the compiler's perspective, any methods declared in a class extension are part of the original class - not just additions as is the case with regular categories. For this reason, methods declared in a class extension must be implemented in the main @implementation block for the original class!


Categories and private methods

With the introduction of class extensions (or anonymous categories) in Objective-C 2.0, private method declarations have now received official language, and therefore compiler, support. Declaring methods in a class extension really makes those methods part of the class, and the compiler enforces that the method implementations appear in the class' @implementation block!

Historically, in Objective-C 1.0, the usual approach to declare a private class or instance method was to declare the method in a category in the implementation (.m) file. This effectively hid the method declaration from the public interface of a class. The category was usually named "Private". For instance:

// ---------- the .h file ----------
@interface ClassName
{
}
- (void) publicMethod;
@end

// ---------- the .m file ----------
#import "ClassName.h"
@interface ClassName(Private)
- (void) privateMethod;
@end

@implementation ClassName
- (void) publicMethod
{
  // the implementation
}

- (void) privateMethod
{
  // the implementation
}
@end

As can be seen in the example, the method that was declared in the category is also implemented in the class @implementation block. This is merely a convention and is not enforced at all by the compiler. In fact, categories, if used in their proper sense, usually have their own @implementation block somewhere else.


Protocols Revisited

Overview

There are formal and informal protocols. A class that adopts a formal protocol must implement all of the protocol's required methods, whereas with informal protocols the adopting class may implement whatever methods it likes. One typical use case for informal protocols are delegates.

A formal protocol groups methods that are not attached to a class and gives them a name. A class may adopt a protocol by implementing its methods. The class is then said to conform to the protocol. Protocols can be used for typing: Two classes that both implement the same protocol, but are otherwise unrelated, can be used interchangeably by using the protocol name as a type.

Protocols replace the need for multiple inheritance in C++.


Informal protocols

Informal protocols are declared as a category but without an implementation. NSObject is typically used for declaring the category, such as in the following example:

@interface NSObject(RefCounting)
- (int) refCount; 
- (void) incrementCount; 
- (void) decrementCount; 
@end 

"RefCounting" in this example is both a category name, and the name of the informal protocol. All classes can adopt an informal protocol that has been based on/attached to NSObject, because all classes inherit from the root class NSObject. Although it is possible, it usually makes little sense to attach an informal protocol to something else than NSObject.

Classes that wish to adopt some or all of the informal protocol's methods re-declare the methods in their interface files and, of course, implement them.


Formal protocols

Formal protocols may contain method and property declarations, such as in this example:

@protocol ProtocolName
- (void) foo;
@property(assign) int bar;

@required
// declarations for required methods
@optional
// declarations for optional methods

@end

Declarations that follow the keyword @required are for methods that must be implemented by conforming classes, whereas declarations that follow the keyword @optional are for methods that are, well, optional. By default, methods are @required (method foo() and property bar in the above example).


A class that wishes to adopt one or more protocols does so in the following way:

#import "NSObject.h"
#import "ProtocolFoo.h"
#import "ProtocolBar.h"

@interface ClassName : NSObject <ProtocolFoo, ProtocolBar>
@end

The adopting class does not re-declare any of the protocol methods. In the above extreme case, the class does not even declare any methods of its own.


Protocols may incorporate other protocols in the same way classes do:

@protocol ProtocolName <ProtocolFoo, ProtocolBar>

A class that adopts such a "composite" protocol automatically adopts the "super" protocols, too.

In source code, a reference to a Protocol object (cf. class objects) may be obtained like this:

Protocol* foo = @protocol(ProtocolName);

Typing with protocol names looks like this:

id <ProtocolName> anObject;
ClassName <ProtocolName>* anObject;


To test whether an object conforms to a protocol, NSObject defines the method conformsToProtocol:

BOOL canDoIt = [anObject conformsToProtocol:@protocol(ProtocolName)];

A class may conform to a protocol without formally adopting it, by simply implementing the methods declared in the protocol.

Protocols may be forward declared by the @protocol directive:

@protocol ProtocolFoo;


Message forwarding

If an object receives a message that it cannot handle, the runtime system gives the object a "second chance": it sends the object a forwardInvocation message whose NSInvocation object parameter encapsulates the original message.

If an object implements the forwardInvocation method, it is therefore able to handle all messages sent to it. All objects inherit forwardInvocation from NSObject, however NSObject's implementation simply calls doesNotRecognizeSelector, which throws an exception.

The intention of forwardInvocation is to allow an object to forward a message to some other object (although it is possible to implement any desired message handling, including sending messages to "/dev/null" to prevent errors from occuring). An example implementation:

- (void)forwardInvocation: (NSInvocation*)anInvocation
{
  if ([someOtherObject respondsToSelector: [anInvocation selector]])
    [anInvocation invokeWithTarget:someOtherObject];
  else
   [super forwardInvocation:anInvocation];
}


The return value of the message that is forwarded in this way is returned to the original sender of the message as usual, making the forwarding process entirely opaque to the sender.

Of course, introspection methods such as respondsToSelector or isKindOfClass reveal that there is a difference between the forwarding object and the forwarding target. The forwarding object may wish to override appropriate introspection methods if it is important to hide the difference.


Memory Management

This chapter is the result of reading [3]. In my opinion, the document structure of [3] is poor, the order in which things are explained is confusing. Sometimes even the text itself is confusing (esp. the chapter about NSCopyObject()). I have tried to re-organize everything into a more logical order that assembles knowledge in a sequential way.


Garbage Collection up-front

In Objective-C 2.0 (which is available with Mac OS X 10.5 and later), it is possible to use garbage collection for automatic memory management. Usage of the GC is optional and may be turned on in the build settings of an Xcode project. On the command line this is done via compiler flags. I have not yet familiarized myself with garbage collection; as soon as I have done so (the document to read is [5]) I will add another sub-section to this chapter about memory management.

Note: Garbage collection is not available on iOS!


Object creation

The first step to create an object is to allocate memory for it. There are three alternatives:

  • alloc
  • allocWithZone:
  • new

Messages for all three methods must be sent to the class object. All three methods return the newly created object (of type id).

In the next step, the new object must be initialized by sending it the init message (or any message that starts with init). All init... methods return an object that can then be further used by the client.

Note that the object returned by init is not necessarily the same as the one returned by alloc - init may release the new object and return a substitute. For this reason, an init implementation might look as follows:

- (id) init
{
  self = [super init];
  if (self)
  {
    // initialization code
    // ...

    // if initialization fails, do this:
    // [self release];
    // return nil;
  }
  return self;
}

Often, classes define a method that performs both the creation/initialization operations in a row. Such methods are called "convenience constructors" or "factory methods". Usually, a convenience constructor is named after the class, e.g. for a class ClassName, the method would be named +className.


Reference counts

Cocoa objects have a reference counter, or retain counter.

  • When an object is created it has a retain count of 1.
  • When an object receives a retain message, its retain count is increased by 1.
  • When an object receives a release message, its retain count is decreased by 1.
  • When an object receives an autorelease message, its retain count is decreased by 1 at a later time. Below there is a chapter with more information.
  • When an object's retain count drops to 0, it is freed or deallocated (destroyed in C++) by the runtime system.

An object reveals its retain count when it receives the retainCount message.


Ownership Policy in Cocoa

Cocoa defines that an object is owned by that client who creates it using a method whose name begins with "alloc" or "new", or contains "copy". A client also owns an object if he sends it a retain message.

Owners of an object are responsible for relinquishing ownership when the object is no longer used, by sending it a release or autorelease message. No other clients but the owners may send a release or autorelease message to an object!

If an object is created using a convenience constructor (see above), the class object that defines the constructor owns the new object - not the client who called the constructor in the first place! The reason for this: the name of the convenience constructor does not begin with "alloc" or "new" and does not contain "copy". Since the convenience constructor is the owner, it sends an autorelease message to the object before returning it. See "Delayed release" further down for more details.


Deallocation

An object that is deallocated because its retain count drops to 0, is sent a dealloc message by the runtime system. A client should never call dealloc directly.

If the object's class has instance variables that are objects, and it is responsible for them, it must override dealloc in order to deallocate those instance variable objects. In addition, the dealloc implementation must call super's implementation.

- (void)dealloc
{
  [foo release];
  [bar release];
  [super dealloc];
}


Important: The implementation of dealloc must not trigger asynchronous actions that will access the object later after it was deallocated. One specific example is that dealloc must not trigger autorelease (see next section) because that will cause a release message to be sent to the deallocated object later on. Also, dealloc cannot "rescue" the object by increasing its retain count, or some similar scheme: Once the runtime system has sent dealloc to an object, that object is doomed and all dealloc can do is perform its cleanup duty. Overall, it is advisable to keep implementations of dealloc as simple and straightforward as possible.


Delayed Release (autorelease)

If a method creates a new object and then returns it, the caller should, of course, be able to use the object. Still, the object needs to be deallocated to prevent a memory leak.

If there were only release, the only solution to the dilemma would be for the creating method to delegate the responsibility for releasing the object to the caller. This would be a poor solution: if the caller neglects its duty for whatever reason, there is a memory leak.

Fortunately, there is autorelease. The autorelease message delays decreasing the retain count to a later time (see the next chapter about "autorelease pools" for more information).

The better, and easier, solution to the dilemma therefore is for the creating method to send an autorelease message to the object, just before it returns the object. The caller may now use the object in whatever way he likes, until he is finished. If the object receives no retain message, it will eventually be deallocated.

A convenience constructor should always use autorelease:

+ (FooBar*)foobar
{
  id newFoobar = [[FooBar alloc] init];
  return [newFoobar autorelease];
}


Autorelease pools

Whenever an object receives an autorelease message, the object's retain count is not decreased; instead the object is added to a so-called "autorelease pool" (NSAutoreleasePool).

When an autorelease pool is deallocated, it iterates all the objects that it contains and sends a release message to each of them (if an object is in the pool several times, it receives a corresponding number of release messages). Thus an autorelease pool serves as a kind of "memory" for autorelease messages, transforming them into actual release messages when the pool itself is deallocated.

Autorelease pools are organized in a stack. Each thread has its own stack of pools. When a new autorelease pool object is created, it is placed on the top of the stack. An object that receives autorelease message is always added to the autorelease pool that is on top of the stack.

In the main thread, Cocoa's Application Kit automatically creates an autorelease pool at the beginning of the event cycle, and releases the pool at the end of the cycle.

If an application is not based on the Application Kit, or if an application creates additional threads, it must take care of its own autorelease pools. Typically this is done in the main() function, or in the method that is given as selector for the new thread.

If an autorelease pool is released that is not on top of the stack, this causes all other pools above it on the stack to be released, too.

If an exception occurs and a thread suddenly transfers out of the current context, the pool associated with that context is released.


Example:

NSAutoreleasePool* mainPool = [[NSAutoreleasePool alloc] init];
while (true)
{
  NSAutoreleasePool* loopPool = [[NSAutoreleasePool alloc] init];
  // do something
  [loopPool drain];  // draining leads to deallocation
}
[mainPool drain];


Retain cycles

If two objects retain each other, none will ever be deallocated, because only dealloc will undo the retain.

The only solution to this problem is not to create it. One of the objects should retain, the other should not. If there is a parent-child relationship between the two objects, the parent should retain and the child should not.

When a parent is deallocated, children that have a reference to the parent must be notified of the deallocation so that they don't send any more messages to the parent.


Accessor (getter/setter) methods

The preferred approach for accessor methods that are well-behaved in a multi-threaded environment looks like this:

- (NSString*) title
{
  return [[title retain] autorelease];
}

- (void) setTitle: (NSString*)newTitle
{
  if (title != newTitle)
  {
    [title release];
    title = [newTitle retain];
  }
}

Because the getter retains, the returned value will always remain valid for the client that called the getter, even if someone else (i.e. another thread) calls the setter and supplies a new value.

To become completely thread-safe, locks would be needed around the sections that access the instance variable.

The problem with the approach is that it has increased performance cost. If the getter is called very frequently, another solution would be better. Read [3] for alternatives.

If the getter does not want to expose an internal object, it might return a copy of the object:

- (NSString*) title
{
  return [[title copy] autorelease];
}

Here, the copy message creates a new object that has an automatic retain count of 1. This solution is as good as the above one.


Object copy

An object that allows to make a copy of itself implements the NSCopying protocol, i.e. it implements the method copyWithZone:. Clients usually send a message copy to the object that they want to have copied; the copy method defined in NSObject then calls copyWithZone:.

"Deep or shallow copy?" The behaviour of setter methods (if there are any) might give a clue as to the answer: if a setter method copies its argument before assigning it to the instance variable, the object probably needs to be deep-copied. If the setter merely retains but makes no copy, the object probably can do with a shallow copy.


Not inheriting NSCopying

A class that does not inherit NSCopying, but implements it by itself, must make sure that the instance variables inherited from superclasses are copied as well. Most of the time, alloc, init and set methods should do the job. Example:

- (id) copyWithZone: (NSZone*)zone
{
  ClassName* copy = [[[self class]   // returns the class object
    allocWithZone: zone]             // we have a zone, so we should not use alloc
    initWithFoo:[self foo]];         // get the value of the "foo" instance variable
  [copy setBar:[self bar]];          // get the value of "bar" from the superclass
  return copy;                       // the caller is responsible for the copy
}


Inheriting NSCopying, naive case

If a class that inherits NSCopying has no instance variables, it does not need to do something. If a class that inherits NSCopying has instance variables, it should override copyWithZone: and call the superclass implementation before copying its own instance variables. Example:

- (id) copyWithZone: (NSZone*)zone
{
  ClassName* copy = [super copyWithZone:zone];
  [copy setFoo:[self foo]];   // set our own instance variable
  return copy;                // the caller is responsible for the copy
}

In the above example, we naively assume that the superclass uses alloc/init in its implementation of copyWithZone. The "copy" variable therefore can be assumed to contain an object that is properly initialized: we don't have to care about the superclass' instance variables, we only need to set our own instance variables.


Inheriting NSCopying, assuming NSCopyObject()

The NSCopyObject() function makes an exact shallow copy of an object, down to the last instance variable.

In the above, naive example, the superclass implementation of copyWithZone could have created the copy by using NSCopyObject(). It is now important to examine the nature of the foo instance variable, and how setFoo: does its work. If foo is a pointer to an object, and if the implementation of setFoo: includes a retain, such as in the following example, we are in trouble:

- (void) setFoo: (id) newFoo
{
  if (foo != newFoo)
  {
    [foo autorelease];
    foo = [newFoo retain];
  }
}

If we go back to the naive example, and we again assume that the superclass has used NSCopyObject(), we will realize why simply calling setFoo is not enough: the copied object already has a value in foo (from the shallow copy of NSCopyObject()), so the implementation of setFoo does nothing. Effectively, we end up with two objects having a reference to foo, but the retain count of foo is only 1.

The solution is for copyWithZone to work slightly different:

- (id) copyWithZone: (NSZone*)zone
{
  ClassName* copy = [super copyWithZone:zone];
  copy->foo = nil;            // initialize instance variable first
  [copy setFoo:[self foo]];   // set our own instance variable
  return copy;                // the caller is responsible for the copy
}

By initializing the instance variable first, we have made sure that setFoo works correctly.


Using NSCopyObject()

If a class does not inherit NSCopying and decides to use NSCopyObject() in its implementation of copyWithZone, it must make sure that instance variables of superclasses are initialized to nil values in the same way as the last example in the chapter above has shown.

Especially important is to initialize the retain count: it is stored in the refCount instance variable and must be set to 1:

- (id) copyWithZone: (NSZone*)zone
{
  ClassName* copy = NSCopyObject(self, 0, zone);
  refCount = 1;
  // do other initialization stuff
  return copy;
}


Copying mutable vs. immutable objects

NSCopying and its copyWithZone method create immutable copies, even if the original object was mutable.

NSMutableCopying is for creating mutable copies, even if the original object was immutable. The protocol declares the mutableCopyWithZone method; this method is invoked through the convenience method mutableCopy defined in NSObject.

Creating an immutable copy of an immutable object can be done efficiently like this:

- (id) copyWithZone: (NSZone*) zone
{
  return [self retain];
}


Exceptions

GCC 3.3 and later provides exception support for ObjC, but the resulting code runs only on Mac OS X 10.3 and later.

Usual compiler directives: @try, @catch, @throw, @finally.

The NSException class is helpful for creating exception objects, although it is possible to throw any object

Example:

Cup *cup = [[Cup alloc] init];
@try
{
  [cup fill];
}
@catch (CustomException* ce)
{  
  // catch most specific exception type
} 
@catch (NSException* exception)
{
  // catch more general exception
  NSLog(@"main: Caught %@: %@", [exception name], [exception  reason]);
  @throw;  // re-throw
}
@catch (id ue)
{
  // ultra-general
}
@finally
{
  [cup release];
}

if ([cup temperature] > 60)
{
  NSException* exception = [NSException exceptionWithName:@"HotTeaException"
                                                   reason:@"The tea is too hot"
                                                 userInfo:nil];
  @throw exception;
}


Thread synchronization

The @synchronized directive can be used to obtain a lock. Any ObjC object can be passed as an argument, even self or a class object:

@synchronized(self)
{
  // do stuff
}

@synchronized supports recursive locking, i.e. the same thread can lock the same object multiple times.

If an exception is thrown within the protected code section, the runtime system catches the exception and releases the lock before it re-throws the exception.


Declared Properties

Overview

Objective-C 2.0 introduces a new feature called "declared properties". A declared property is used to

  1. Declare the name of a property of a class
  2. Automatically generate the accessor (getter/setter) methods for the property by the compiler (including the proper method of memory management)
  3. Automatically generate the instance variable that represents the property by the compiler. This does not work for "legacy" runtimes (see Runtime Versions and Platforms in the "Objective-C Runtime Programming Guide").

Note: It is not clear whether applications that use this feature can be run on systems prior to Mac OS X 10.5.


Declaration

A property is declared using the keyword

@property

The declaration can appear anywhere in

  • The method declaration list found in the @interface of a class
  • The declaration of a protocol
  • The declaration of a category

For instance

@interface ClassName : NSObject
{
  float width;
  float height;
  NSColor* color;
}

@property float width;
@property float height;
@property NSColor* color
@end


Additional notes:

  • The declaration of a property named "width" is equivalent to declaring two accessor methods:
- (float)width;
- (void)setWidth:(float)newValue;
  • If a system is targetted that uses the modern (Objective-C 2.0) runtime, no declaration of instance variables is required - the variables are generated automatically by the compiler. The variable name is the property name prefixed with an underscore ("_").
  • Supported types are: any Objective-C class, Core Foundation data types (with some exceptions, for details refer to section of the Objective-C book that describes declared properties), or "Plain Old Data" (POD) types in C/C++ (basically these are fundamental data types, enumerations and pointers; for more details refer to C++ Language Note: POD Types)
  • With some exceptions, properties may be re-declared in a subclass, or when declared in a category or protocol


Implementation

The implementation of accessor methods of a declared property must appear in the @implementation block of a class. The following possibilities exist:

  • Using the @synthesize directive lets the compiler generate the accessor methods for you. Note that @synthesize can only use instance variables from the current class (not a superclass).
  • Using the @dynamic directive tells the compiler that it should not check for an implementation at compile time, but that one will be provided dynamically at runtime.
  • If neither @synthesize nor @dynamic is used, a "manual" implementation of the accessor methods must be present, otherwise the compiler will generate a warning.


Example:

@implementation ClassName

@synthesize width;
@dynamic height;  // suppresses warning

- (NSColor*)color
{
  // implementation
}

- (void)setColor:(NSColor*)newValue
{
  // implementation
}
@end


Additional notes:

  • Using the following syntax, it is possible to let @synthesize generate code that refers to an instance variable whose name is different than the property name
@synthesize propertyName = instanceVariableName;
  • Using the following syntax, it is possible to generate code for more than one property:
@synthesize width, height, propertyName = instanceVariableName;


Accessing properties

Accessing a property may take one of the following forms:

anObject.width = 12.7;                     // dot notation
[anObject setWidth:12.7];                  // access by message
[anObject setValue:12.7 forKey:@"width"];  // key-value coding
anObject->width = 12.7;                    // direct instance variable access


Advanced properties: Attributes

Properties may be declared with additional attributes like this:

@property(attribute [, attribute2, ...]) type name;

The code generated by @synthesize will automatically match the specified attributes. Manual implementations must obviously also match the specification, but how to do this is up to the implementor.

Examples of possible attributes:

// General
@property(getter=isGreen) bool green;     // typically used for isFoo name convention in key-value coding
@property(setter=aMethodName) int value;  // may not be used if property is read-only
@property(readonly) int value;            // only a getter is required
@property(readwrite) int value;           // the default, so usually this is not needed

// Setter semantics
@property(assign) NSString* value;        // no messages are sent when a new value is assigned; this is the default
@property(retain) NSString* value;        // setter sends "retain" to the new value, and "release" to the previous value
@property(copy) NSString* value;          // setter sends "copy" to the new value (to make a copy), and "release" to the previous value

// Atomicity / multi-threaded environment
@property(nonatomic) NSString* value;     // accessors do NOT use locking, and getter does NOT use retain/autorelease idiom
                                          // by default properties are atomic, but there is no attribute to specify so


Additional notes:

  • If "copy" is used, make sure that the receiver of the copy message does not return an immutable copy if what you really want is a mutable copy. NSArray is a candidate for this. In such a case, the setter method needs to be manually implemented.


Interface Builder properties

This is the syntax to specify an Interface Builder property:

@property(nonatomic, retain) IBOutlet NSButton* cancelButton;

Note that "nonatomic" and "retain" are just those attributes that are usually used, but one might also specify different attributes.


Properties and dealloc

@synthesize only generates accessor methods - the dealloc method must be manually implemented to properly release objects. Useful rules are to check the header file and to make sure that

  • Object properties not marked "assign" are released
  • Object properties marked "assign" are not released.


Releasing an object that is referenced by a property is as simple as assigning the property a nil value, because that invokes the setter which in turn will send the referenced object a release message.


Private setters

Class extensions can be used to re-declare a read-only property so that in the private implementation the property actually becomes writeable. For instance:

// ---------- the .h file ----------
@interface ClassName
{
}
@property(readonly) NSString* foo;
@end

// ---------- the .m file ----------
#import "ClassName.h"
@interface ClassName()
@property(readwrite) NSString* foo;
@end

@implementation ClassName

@synthesize foo;

- (void) someMethodMethod
{
  self.foo = @"bar";
}
@end


Blocks

References


Example:

NSString* string1 = @"foo";           // read-only ("const")
__block NSString* string2 = @"bar";   // read-write ("mutable")


       +--- block               +--- parameters with names (as in a function implementation)
       |    name                |
       |                        |                 +--- body of the block
       v                        v                 v
int (^aBlock) (float, int) = ^(float f, int i) { int j = f * i; };
 ^              ^
 |              +-- list of parameter types; parameters are not named, this is just like a function declaration
 |
 +- return type


// Run the block just like a function
aBlock(3.7, 17);

// Block declarations
void (^blockReturningVoidWithVoidArgument) (void);
int (^blockReturningIntWithIntAndCharArguments) (int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10]) (int);
typedef float (^MyBlockType) (float, float);


Notes

  • In the example above, a block variable aBlock was declared and initialised with a block literal
  • A block has access to all variables that are in its scope
    • Both NSString variables in the example can be used read-only
    • But only string2 can be used read-write
    • The address of __block variables can change over time}
  • A block with no arguments must use void to declare this fact; the argument list cannot be omitted entirely
  • The declaration syntax for a block looks just like that of a function pointer, except that ^ is used instead of *


Miscellaneous

Forward declarations

Currently known:

@class
@protocol


Static class variables

Static class variables are not possible. Static variables must always be declared globally, or in a method.

So far, I did not find out how I should define a static const object that has a non-basic type. For instance, the following does not work, the compiler complains with "initializer element is not constant":

static const NSString* myString = [NSString stringWithUTF8String:"foobar"];

There seems to be a way how to define a constant string, but the resulting object has the type NXConstantString, which I don't know anything about (how should I use it to compare strings, etc.):

static const id myString = @"foobar";

[4] states that it is possible to say

NSString* myString = @"foobar";

and that the resulting object can be used as any other NSString (except that it will never be deallocated, but who cares :-)


Enumerations

Enumerations are declared as usual.

However, it seems to be impossible to declare an enumeration in the context of a class, with the purpose to remove the enum values from the global namespace. At least the following construct produced a warning in Xcode ("declaration does not declare anything"):

@interface ClassName : NSObject
{
@public
  enum Foo
  {
    FirstFoo,
    SecondFoo
    // ...
  }
}

To use an enumeration type which is not typedef'ed, it must be prefixed with the keyword enum each and every time. Note that this seems to be C syntax... in C++ the keyword is not required! For instance:

enum Foo                            // no typedef is used
{
  // ...
};

typedef enum                        // notice the typedef!
{
  // ...
} Bar;                              // give the otherwise anonymous enum a name

@interface ClassName : NSObject
{
@private
  enum Foo m_foo;                   // usage with an instance variable
  Bar m_bar;                        // no enum keyword required because of typedef
}

- (void) doIt:(enum Foo)foo;        // usage with an argument type
@property enum Foo foo;             // usage with a declared property

@end


Namespaces

Objective-C does not allow to declare namespaces.


Mixing Objective-C and C++

  • Mixing Objective-C and C++ results in Objective-C++
  • Mixed implementation files must have the .mm file extension to trigger the compiler's Obj-C++ processing
  • It's possible to have a different file extension, but then the compiler must be advised with a special flag what language it is compiling - see the page about GCC
  • Mixed header files do not need a special file extension, they can just use .h
  • A mixed header file cannot be #import'ed by a pure Objective-C implementation file (nor #include'd by a pure C++ implementation file) because the compiler is not in Obj-C++ mode when it includes the header
  • There may be other issues, but this is what I needed to know so far


NSString

Convert from std::string to an NSString works via c_str(), i.e. the char* pointer to the std::string's internal buffer:

NSString* nsFoo = [NSString stringWithCString:foo.c_str() encoding:[NSString defaultCStringEncoding]];

Converting from NSString to std::string works via the std::string(const char*) constructor (basically the reverse of the c_str() thingy above):

NSString* nsFoo = "bar";
std::string foo = [nsFoo cStringUsingEncoding:[NSString defaultCStringEncoding]];

Assigning a constant string:

NSString* myString = @"foobar";


Iterating over collections

The traditional way for iterating over a collection is to use NSEnumerator:

NSEnumerator* enumerator = [aCollection objectEnumerator];
Foo* anItem;
while ((anItem = [enumerator nextObject]) != nil )
{
  [anItem aMessage];  // send aMessage to anItem
}

The other traditional alternative for iterating uses indexes:

for (int i = 0; i < [aCollection count]; i++)
{
  Foo* anItem = [aCollection objectAtIndex:i];
  [anItem aMessage];
}

The final technique is called "fast enumeration" and is available since Objective-C 2.0. While being comfortable to use, and promising faster execution than the previous two methods, the fast enumeration technique limits the application to Mac OS X 10.5 and later because Objective-C is available only since this version of the operating system. Fast enumeration is available for all versions of iOS.

for (Foo* anItem in aCollection)
{
  [anItem aMessage];
}


Questions

Should I still use the "#ifndef FOO_H - #define FOO_H - #endif" construct, even though there now is the blessed #import directive?
Don't use this.
Should I use release or autorelease in the dealloc method?
Officially release should be used. I sometimes prefer autorelease because it prevents timing issues when deallocating interlinked objects. Of course, one might argue that any such issues are caused merely by improper design...
Why should I import a precompiled header, what are the consequences if I do and there is no such header present, and last but not least how do I do it?
No answer yet for precompiled headers in general. In particular, Xcode allows to define a "Prefix header" which is automatically imported at the top of every source file of the project.
What about @public/@protected/@private when it comes to methods and inheritance?
These keywords cannot be used in the context of method declarations and inheritance. For private method declarations use class extensions (Obj-C 2.0) or a privately declared category (Obj-C 1.0). Inheritance cannot be controlled at all.
What happens if no matching method is found for a message?
No answer yet.
What happens if a message is sent to nil?
The message goes to Nirwana. It also returns nil ***IF*** the message is declared to return an object (id), any pointer type, or an integer scalar whose size is <= sizeof(void*). If the return type is something else, the message's return value is undefined.
Is the information in [3] true for other ObjC implementations than Cocoa?
No answer yet.
What is the meaning of a "memory zone" and why is it a good idea to make sure that two objects are located in the same zone?
A memory zone is a "page-aligned area of memory"; this is stuff that has to do with what the computer's MMU does. A refreshing course in computer science might be due to understand the exact meaning.
Is the refCount instance variable mentioned by [3] in relation to NSCopyObject() an instance variable of NSObject?
No answer yet.
Is it advisable at all to use NSCopyObject()? If yes, what is the solution for instance variables in superclasses that are private or not documented?
No answer yet.
Is there a rule about what is the difference between methods whose name starts with "new" and those whose name starts with "alloc"?
No answer yet.


Good practices

Writing a new class

  • Do not override + (id) alloc (there's no need to)


Initializers

  • A class must always override the designated initializer of the superclass (e.g. - init for NSObject)
  • If the class has additional init... methods, one of them must be "declared" designated initializer - document this!
  • The designated initializer of a class must call the designated initializer of super
  • All other init... methods (including an overridden designated initializer!) call the designated initializer of self (either directly, or chained through other init... methods)


Memory management

General advice

  • Use autorelease in all convenience constructors !!!
  • Override - (void) dealloc if new memory is allocated
    • Send release to retained members
    • Set nil to a retained properties to achieve the same effect
  • Getters use retain / autorelease, or copy / autorelease
  • Setters use release on the old value, and retain on the new value (also retain when copying)

Declared properties:

  • Use autorelease if an object is created with alloc / init and then stored in a retain property (otherwise it will end up with retain count = 2)
  • If a property is assign-only, make sure that someone else sends retain / release to the object
  • Break retain cycles: Define a parent-child relationship and let the parent retain / release. The parent must also set its own reference in the child to nil.

Other

  • Create a first autorelease pool just inside a thread's main method, and a second one just inside the main loop


Debugging

  • Implement a - description method in your class so that NSLog and GDB's po command can provide useful information about instances