Objective-C Concepts


Contents

Encapsulation

What object encapsulation does Objective-C provide?

Object encapsulation can be discerned at two levels: encapsulation of instance variables and of methods. In Objective-C, the two are quite different.

Instance variables:
The keywords @public , @private and @protected are provided to secure instance variables from prying eyes to some extent.

If not explicitly set, all instance variables are @private . Note: Instance variable encapsulation is enforced at compile-time. At run-time, full typing information on all instance variables is available, which sort-of makes all variables @public again. This information is for instance used to do instance variable lookup by NeXTSTEP's `loadNibSection:owner:' method, making it completely safe.
Methods:
To the Objective-C runtime, all methods are @public. The programmer can only show his/her intention of making specific methods not public by not advertising them in the class' interface. In addition, so-called private methods can be put in a category with a special name, like `secret' or `private'.

However, these tricks do not help much if the method is declared elsewhere, unless one reverts to indicating the object's type at compile time. And the runtime doesn't care about all this and any programmer can easily circumvent the tricks described. Thus, all methods really are always @public.


Protocols

Protocols are an addition to Objective-C that allows you to organize related methods into groups that form high-level behaviors. Protocols are currently available in NeXTSTEP (since 3.0) and GCC (since 2.4).

Multiple Inheritance Protocols address the multiple inheritance issue. When you design an object with multiple inheritance, you usually don't want *all* the features of both A and B, you want feature set X from A and feature set Y from B. If those features are methods, then encapsulating X and Y in protocols allows you to say exactly what you want in your new object. Furthermore, if someone changes objects A or B, that doesn't break your protocols or your new object. This does not address the question of new instance variables from A or B, only methods.

Type Checking Protocols allow you to get type-checking features without sacrificing dynamic binding. You can say "any object which implements the messages in Protocol Foo is OK for this use", which is usually what you want - you're constraining the functionality, not the implementation or the inheritance.

Reuse Protocols give library builders a tool to identify sets of standard protocols, independent of the class hierarchy. Protocols provide language support for the reuse of design, whereas classes support the reuse of code. Well designed protocols can help users of an application framework when learning or designing new classes.

An Example Here is a simple protocol definition for archiving objects:


    @protocol Archiving
    -read: (Stream *) stream;
    -write: (Stream *) stream;
    @end

Once defined, protocols can be referenced in a class interface as follows:
    // MyClass inherits from Object and conforms to the Archiving protocol.  
    @interface MyClass: Object < Archiving >
    @end

Unlike copying methods to/from other class interfaces, any incompatible change made to the protocol will immediately be recognized by the compiler (the next time the class is compiled). Protocols also provide better type checking without compromising the flexibility of untyped, dynamically bound objects.


    MyClass *obj1 = [MyClass new];

    // OK: obj1 conforms to the Archiving protocol.
    id < Archiving > obj2 = obj1;

    // Error: obj1 does not conform to the TargetAction protocol.
    id < TargetAction > obj3 = obj1;

Another use of protocols is that you can declare an ID to conform to some protocol in order to help the compiler to resolve method name conflicts:

    
    @interface Foo: Object
    -(int) type;
    @end

    @protocol Bar
    -(const char *) type;
    @end
 

    -blah1: d
    {
        id t = [d someMethod];
        do_something_with ([t type]);
    }

    -blah2: d
    {
        id < Bar > t = [d someMethod];
        do_something_with ([t type]);
    }
    
In this example, there are two kinds of the `-type' method. In the method `-blah1:', the compiler doesn't know what return type to expect from `[t type]', since it has seen both declarations of `-type'. In method `-blah2:', it knows that `t' conforms to the `Bar' protocol and thus that `t' implements the `-type' method returning a `const char *'.


Garbage Collection

Currently, there are a number of implementations of garbage collection which can be used in Objective-C programs. Most methods use very different approaches.

In an Uncooperative Environment

This implements garbage collection of chunks of memory obtained through (its replacement of) malloc(3). It works for C, C++, Objective-C, etc.

Through Class Abstraction

This implements garbage collection through class abstraction (and hence is Objective-C specific). Anything to be garbage collectible must be an object (instance of a subclass of a specific class) or have such an object for a wrapper.

Apart from the obvious radical difference, another difference currently is also noteworthy: The first method automatically protects objects pointed to from the stack, bss or data segments; the second doesn't.

Through Reference Counting

Reference counting is a garbage collection scheme (which some do not or refuse to recognize as such) used by OpenStep and its predecessors FoundationKit and Portable Distributed Objects (PDO).

When contemplating PDO, reference counting comes naturally: every remote proxy of an object (there is proxy in each remote process referencing the object) must account for a single reference to the local object. When all remote proxies have lost interest, the local object can be removed (unless other local objects are also interested in that particules object, of course).

Cyclic Referencing

Straightforward reference counting, where an object decides to deallocate itself only upon reaching a reference count of 0, introduces the problem of cyclic referencing. This problem can be alleviated by introducing the notion of ownership. (Of couse, ownership can't be used in all situations, but at least it shows a way of thinking about the problem.)

What does ownership mean? It means that the owner can tell the object being owned that its life cycle has ended and that it should cleanup (for instance by invoking a `-cleanup' method). The object goes about releasing all its instance variables and sits around waiting for the final release which will cause its deallocation. Because the object releases all things it has retained, it is guaranteed that the object will not end up being part of an unreferenced cycle. The only thing needed for this paradigm is that the owner does a `-cleanup' before the `-release'. (There are a few other quircks, which I won't mention here.)

Put differently: ownership introduces a meta level of deallocation, where the meta level deallocation must have been performed before the non-meta level. The meta level deallocation removes the object's functionality (how much can you do when you've got nothing to remember?) and the non-meta level removes the object's storage.


dekorte@symnet.net