alpha version, October 2000
The CXObject is the base class for all the XSdk classes. Therefore, you can freely use its methods on any object you create or get from other routine or obtain any other way. The only exception are the plain Epoc objects, used in contexts where the proper XSdk API is not finished yet.
Note that since class objects are abjects as well, all the methods of the CXObject--even the instance ones, preceded by a minus--can be used on any class as well.
-BOOL isXObject;
This method checks whether the receiver is a proper CXObject-derived object. It is pretty safe to use it under almost all circumstances--which means that the code
void *xxx=foreignFunctionWhichShouldReturnAnObjectButImNotSure();
if (xxx->isXObject())
xxx->anyXFoundationMethod();
would crash with considerably less probablility than should you call the anyXFoundationMethod method directly. This way you can ensure much better stability of your applications, for you can catch the error (of getting a nonsense instead of an object) early and take some reasonable cure, instad of just having the application to crash (destroying all the user's non-saved data) like it would with the plain C++ Epoc objects and Symbian SDK. The isXObject method protects even the virtual table pointer in the object--in case it happens to be rewritten somehow, the isXObject returns NO.
There is just about one condition when even the isXObject method crashes, which is the case the "object" pointer points into such place in the address space which generates a MMU exception. In future we hope we will be able to check even that, but it seems to be quite a difficult job (to do so without a prohibitive performance penalty).
Note you don't need to check every time every object pointer using this method; it is needed (a) only in cases you got the object from an unsecure source (like a loadable dynamic library, or perhaps your own code you are not sure you have written properly), and (b) in case you are using the deprecated C++ method call primitive "object->method()", for with the standard primitive [object method] (see the Preprocessor), the isXObject check is done for you automatically:
void *xxx=foreignFunctionWhichShouldReturnAnObjectButImNotSure();
[xxx anyXFoundationMethod]; // an equivalent to the above example, but much more easily readable
Note that whilst xxx->anyXFoundationMethod will with non-object xxx crash destroying all the user's non-saved data, the [xxx anyXFoundationMethod] would raise an exception, allowing your application to constinue working and save the data (unless the MMU exception is generated, as explained above).
Note also it would be no sense in sending this method using the primitive [object method]; if you do need to use it directly, use the C++ primitive object->isXObject().
-id init;
The proper place for the instance initialization. Sometimes we will call the init methods initializers or constructors; should we ever want to mention the C++ constructors, the "C++" will be used. In case you need to use some arguments, use the conventional form initWith, or, in case the overloading system might be confused, initWith<Something>:
-id initWith(int number);
-id initWith(id object);
-id initWith(CXString *fmt,...); // printf-like format
-id initWithList(CXString *fmt,VA_LIST arglist);
Note that the arglist might get confused with the ... from the previous init method, thence the name had to be changed to initWithList.
Unlike the C++ constructor rot, this methods are properly inherited; thence, any initialization you prepare for some particular class will be available with no limitations to its subclasses. On the other hand, in case you re-implement any init method yourself, remember to call the superclass' implementation:
-id init
{
[super init];
...
return this;
}
The returning of this is a convention more or less meaningless in the C++, but it will get really handy as soon as the Objective C and Java get available.
In case you are preparing a class with more initializers, it is worth it to use the convention of a designated or primitive initializer. Just select one of them (generally, but not necessarily, the one with the biggest number of arguments), and make all the "real initialization" in it; all the others initializers then will be implemented just by calling the designated one:
-id initWithObjects(CXArray *moreObjects) // designated
{
[super init];
...the real initialization...
return this;
}
-id initWithObject(id object)
{
return initWithObjects([CXArray arrayWithObjects:object,nil]);
}
-id initWithCString(const char *cp)
{
return initWithObject([CXString stringWith:cp]);
}
-id init
{
return initWithObject(nil);
}
This way, in a subclass at most the designated initializer should have to be reimplemented. All the remaining initializers will work automatically properly even for the new class (a nice advantage of having all things inheritable and virtual!).
Quite often, the same convention is used even for all the methods: some of them are designed to be primitive, and all the others are implemented just by proper using of the primitive ones. That means a subclass needs to reimplement the primitive methods only, so that all the methods work properly.
For instance, the CXArray class has some primitive mthods, and a non-primitive method lastObject. Should you make any CXArray subclass which implements properly all the primitive methods, the inherited lastObject one will always work properly (returning the last object in the array, whatever way it is particularly implemented).
-void dealloc;
The object destructor. Again, the C++ desctructor is quite unuseable, since it is neither inheritable nor virtual. The proper XSdk destructor just releases all the object's properties, and finally calls the implementation from its superclass:
-void dealloc
{
...release all the properties here...
[super dealloc];
}
Note it is quite all right to add code which does not use the object's properties even after the actual destruction:
...
[super dealloc];
printf("Object deallocated at 0x%x",(unsigned)this);
}
-id beStatic;
By calling the beStatic method the receiver is made static. That means it will never be deleted, regardless how many times the release methods (see below) is called.
The framework keeps a hashed table of all the static objects, and in case you make an object static twice, only the first one is really kept static and returned. From the programmer's viewpoint it means you must use the returned value:
id obj=...,obj1=...;
obj=[obj beStatic]; // all right
[obj1 beStatic]; // BAD!!!
In the second case, it is possible the hashed table already contained another instance, which isEqual: (see below) to the obj1; if so, the obj1 is not made static; instead, the equal already static instance is returned, and thence the obj1 variable contains an improper value!
Be cautious when making objects static, so as the application's needs of the memory does not get too big.
-BOOL isStatic;
Enquires whether the object is static or not.
-id retain;
By calling the retain method, you ensure you will be able to use the object till you release it by release (or let it to be released automatically by autorelease). The objects sharing is described in detail in the Object ownership document.
-void release;
By calling the release method, you free an object you previously retained by retain (and the object will be destroyed, unless it is used by another piece of code). The objects sharing is described in detail in the Object ownership document.
-id autorelease;
By calling the autorelease method, you ask for freeing an object you previously retained by retain (so that the object is eventually destroyed, unless it is used by another piece of code). The object will be released automatically in near future; it is safe to use it in the remaining of the method which called the autorelease method, and it is safe to return autoreleasead objects. The objects sharing is described in detail in the Object ownership document; see also the CXAutoreleasePool class.
-Class myclass;
Returns the class for the object. If this method is called for a class object, it returns Nil. Inside an implementation you can use the macro MyClass, which equals to this->myclass(), casted to the proper class type.
Incidentally, this method should be named class, but in the C++ rot is it not possible, for the class is a keyword there (instead of a standard identifier).
-Class superclass;
Returns the superclass, ie. the class which the object's class was inherited from. If called for a CXObject instance returns Nil.
-BOOL isKindOfClass(Class cls);
Checks whether the object is an instance of the given class or any of its subclasses. This is the most often used method to check if a particular API can be used:
id *object=genericObjectFromForeignSource();
if ([object isKindOfClass:CXStringClass]) {
CXString *string=object;
...use string API...
}
-BOOL isMemberOfClass(Class cls);
Checks whether the object is an instance of the given class. In practice this is seldom used, for the subclasses usually fit the needs as well.
-CXString *description;
Returns a string which describes the object by some human-readable way. By default, it just writes out the object's class name and the memory address the object is placed to, but for particular classes is usually reimplemented by some meaningful way. For example, a string returns its contents, an array lists all the objects it contains, and so forth.
-CXString *implementationDllPath;
Returns the DLL path where the receiver's class is implemented. Particularly useable for dynamically loaded DLLs, but works properly even for the main application DLL.
-BOOL isEqual(id anotherObject);
Checks if the object is equal to any other object. By the default implementation, an object is equal only to itself, but the method is usually reimplemented by some meaningful way. For example, strings are equal if their contents are equal, arrays are equal if they contain the same number of objects, and the objects at appropriate indices equal recursively, and so forth.
-unsigned hash;
Returns a hash number of the object. The hash number is a value which must never differ for objects which are equivalent to each other (see the isEqual method above for the meaning of equivalency), but might and need not differ for different objects. Naturally, the more different objects return different hash values, the better.
Generally, you would need to reimplement the hash method for the same classes which reimplement the isEqual method.
The hash value allows for efficient and user-friendly implementation of many special classes, which can contain other objects and find them extremely quickly, like the CXDictionary or CXSet.
-int intValue;
Just returns some more or less meaningful integral representation of the object. Will be removed from the CXObject class and moved to a generic interface as soon as the preprocessor supports protocols.
+id alloc;
Creates a new uninitialized object. Hardly ever you would use this method directly; more often specialized class construction methods (like stringWith or arrayWithObject) are used. The typical implementations will look like this:
@implementation CXString
...
+CXString *stringWith(CXString *anotherString)
{
return (CXString*)[[(CXString*)[self alloc] initWith:anotherString] autorelease];
}
...
@end
@implementation CXArray
...
+CXArray *arrayWithObject(id obj)
{
return (CXArray*)[[(CXArray*)[self alloc] initWithObject:obj] autorelease];
}
...
@end
In case you do use the alloc method directly, don't forget to call the initializer afterwards! Because of the C++ fatuous type checking you would have to cast twice:
CXString *s=(CXString*)[(CXString*)[CXString alloc] initWith:...];
Do not forget also that an object made this way is not autoreleased. You must release or autorelease it manually, lest it becomes a memory leak.
In case you implement your own class, do not reimplement the alloc class method. In principle it should be just inherited from the CXObject class; it is technically impossible in the C++ rot, so it must be in fact reimplemented, but the XSdk framework does it automatically for you.
-BOOL respondsTo(CXString *methodName);
The method returns YES in case the object does implement a method of a given name, and NO otherwise. Thus, in case it seems to be more convenient, you can check directly for a method instead of a class:
...
SomeClass *obj=(SomeClass*)whatever...;
...
if ([obj respondsTo:@"aMethod"])
[obj aMethod];
Beware that in the C++ rot the responding, though, does not guarantee the method will be called properly! That comes from the fact the blasted parody of a programming language does not support polymorphism properly, thus in case you have two classes which both happen to implement the same method, you cannot call it regardless the actual class:
@interface ClassA...
...
-void aMethod;
...
@end
@interface ClassB...
...
-void aMethod;
...
@end
...
ClassA *a=new ClassA;
ClassB *b=new ClassB;
...
[(ClassA*)b aMethod]; // CRASHES!
[(ClassB*)a aMethod]; // ditto
though both objects will correctly say they responds to a method "aMethods". To be quite sure, check for the class:
if ([obj isKindOfClass:ClassAClass])
[(ClassA*)obj aMethod]; // this cannot fail
There is one context, though, where the respondsTo method can be used with full confidence, and that is checking whether object responds to a method which is to be sent to it using the fully dynamic perform:with: method:
+BOOL instancesRespondTo(CXString *methodName);
The class can be asked whether its instances respond to a method. That might come handy in case you do not have any instance, for you would make one only in case it would respond--instead of a somewhat clumsy and ineffective code
id obj=[SomeClass newObjectWith...];
if ([obj respondsTo:method])
[obj perform:method with:nil];
// obj will be used no more, just letting the garbage collector to eat it!
a better code can be written:
if ([SomeClass instancesRespondTo:method])
[[SomeClass newObjectWith...] perform:method with:nil];
-id performwith(CXString *methodName,id value);
The named method is performed by the receiver object; the value is redirected to the method as its argument, and the return value of the dynamically called method is returned from the perform:with: one. Note that means that the method called this way must have just one argument, and it must be convertable from the id; analogically, its return value must be convertable to id. Besides, so as a method can be called this way, it must be an action--see the XPreprocessor for a detailed description how the actions are declared.
With these limitations in mind, it can be said the following two commands are functionally equivalent:
[anObject aMethod:aValue]; // or even anObject->aMethod(aValue)
[anObject perform:@"aMethod" with:aValue];
The differences are
the dynamic version is somewhat slower;
where the static call would crash, the dynamic will raise the exception X_NON_IMPLEMENTED_PERFORM.
Of course, you can use the method really fully dynamically--somewhat quite unprecedented in the C++ rot. For example, the following code is absolutely correct:
int i=gimmeNumber();
...
CXString *m=[CXString stringWith:@"method%d",i];
[anObject perform:m with:nil];
Whatever the number returned by the gimmeNumber function, an appropriate method method0, method1, method1245102, etc. will be either properly called from the object anObject (in case it happens to implement it), or the exception X_NON_IMPLEMENTED_PERFORM will be raised.
-id performIfPossiblewith(CXString *methodName,id value);
Quite often you would write some code like
if ([obj respondsTo:method]) [obj perform:method with:value];
or perhaps more or less functionally equivalent
X_DURING
[obj perform:method with:value];
X_HANDLER
if (![[localException name] isEqual:X_NON_IMPLEMENTED_PERFORM])
[localException raise];
X_ENDHANDLER
The performIfPossible:with: method is just a convenient wrapper for that: it check whether the method given can be performed in the receiver; if so, it performs it, otherwise it does nothing (and returns nil). Thus, in the following code:
[obj performIfPossible:method with:value];
no exception can occur (unless raised inside the obj's method performed).
-BOOL conformsTo(CXString *protocolName);
This method checks whether an object conforms to (ie. implements) a protocol given. It is absolutely indispensable for a dynamic code, which co-operates with some foreign object, sending it messages depending on the protocol(s) the object conforms to:
@protocol WindowResizeProtocol
-XSize windowwillBeResizedTo(CXWindow *win,XSize newSize);
...
@end
@protocol WindowCloseProtocol
-BOOL windowWillClose(CXWindow *win);
...
@end
...
@interface CXWindow:...
{
id delegate;
...
}
-void setDelegate(id del);
-void resizeTo(XSize newSize);
-void close;
...
@end
...
@implementation CXWindow
...
-void setDelegate(id del)
{
delegate=del;
}
-void resizeTo(XSize newSize)
{
if ([delegate conformsTo:@"WindowResizeProtocol"])
newSize=[WindowResizeProtocolCast(delegate) window:this willBeResizedTo:newSize];
... // resizing code
}
-void close
{
if ([delegate conformsTo:@"WindowCloseProtocol"])
if (![WindowCloseProtocolCast(delegate) windowWillClose:this]) return;
... // closing code
}
...
@end
Note that with this API, the programmer can just use the window without even considering the delegate--as long as the special services are not needed. As soon as they are needed, the delegate can be dynamically added and used; all appropriate methods from protocols it conforms to will be called, while methods from protocols it omit (since they are not needed in the particular code) are automatically skipped.
In case delegation is used smartly, there is hardly ever a need to subclass any of the basic API classes; it leads to considerably cleaner, more robust, and hitherto easily supportable and upgradeable code than the widely used C++ way of making a subclass for each trifle!
See the XPreprocessor for more information of protocols.
-id nestedCellsAddressesForHeapCheck;
<<<documentation forthcoming>>>
Incidentally, you might find out in some very special cases the CXObject is not memory-savvy enough: in case you make scores of very small objects, you must take into consideration the object memory overhead, which is sixteen bytes for a CXObject (including the virtual table pointer).
Under normal circumstances, this is never an issue. Though, should you ever make, say, hundreds of thousands or even more objects, each of which contains just one of two bytes of information, you should consider using the plain C++ objects for the task.
Copyright © 1999-2000 X.soft, all rights reserved