[back]   [TOC]   [forward]


Chapter 11
Appendix
iSimplePlug Documentation


This chapter contains extra information for those plugin developers looking for more information regarding some of the topics covered in this tutorial. In particular, this chapter has an appendix on alternate command ID caching schemes, and an appendix on the technicals of the ITPlugin convenience methods that are available to plugin developers.


Appendix A
Alternate Command ID Caching Schemes

Questions, Questions, Questions

In this tutorial, a C array was dynamically allocated, and the command ID numbers for each command the plugin responds to was cached in this array. But is this the only method that one can use for caching? And why do we even bother caching? And, for that matter, why does iTonamaton use numeric command IDs anyway?

These are all good questions, and I hope to brifely address all of them in this appendix:

The reason why iTonamaton uses numeric command IDs is partially a matter of speed, partially a matter of scalability, and partially a matter of good programming. It also results in easier to write comparison code than if one were to use the command names directly. Anyway, in early version of iTonamaton, commands were always referred to by name. However the code for checking if a command was equal to a certain value took a line of code like if( [command caseInsensativeCompare:@"value"] == NSOrderedSame] ), which is quite a bit of code for a simple comparison. Furthermore, all this code takes time to execute; indeed, string comparisons are relatively slow operations.

So were the command IDs implemented for speed?; because if it were too long, one could make a convenience method on ITPlugin to do the comparison for you. To a certain extent it was done for speed. Though for most cases the comparison time is small compared to the time between events. It was more a matter of good programming technique, along with scalability in the back of the mind. Many good programmers attempt to make optimizations to their code when possible, especially in their base code. This technique is often applied in situations where it isn't deemed immediately necessary. One of the reasons to observe good coding techniques like this, is that it makes programs scalable. What happens if iTonamaton eventually gains the capability to serve a large number of channels with many services? There could be so many events passing through its system that all that overhead in doing string comparisons really would be a problem. Futhermore, since all the plugins would be written for doing these string comparisons, an optomization in the code to use command IDs would force all the plugin makers to rewrite part of their plugin. However, since the author of iTonamaton did this optimization at the beggining, this particular scalability problem is safely avoided.

Now that leaves us with the question why we even bother caching. The answer to this is much the same. One could compare agasint the command names by using the appropriate convenience method. However this needlessly spends a lot of time generating hash values, comparing strings, getting NSNumber objects, and then unwrapping their encapsulated unsigned values. It is much faster, and hence more scalable, if one caches the command IDs for faster access. Therefore this is the route recommended by the iTonamaton team.

The Statically Allocated C Array

The first alternate caching method I'd like to mention is very similar to the one used by the iSimplePlug technique. It again uses a C Array, however instead of declaring a pointer to the C Array and then dynamically allocating the memory for it, it statically allocates the required amount of memory at compile time. The upside to this is that one doesn't have to fuss with calloc; however there is a downside to this technique. A change in the number of commands declared by the plugin must be reflected in the array's decleration in the header file, hence making two places in two files that need to be changed. For those interested in using this method (which is an acceptable way of doing things), you would declare the C array as an instance variable in the plugin's header file, changing the decleration to look like so:

@interface SimplePlug : ITPlugin
{
    NSArray * commandNameArray;
    unsigned commandIDArray[2];
}
- (void)processCommand:(NSNotification *)note;
@end

Then you would remove the code associated with the memory allocation of the array from the initWithOwner: method, as your array is added to the program's memory automatically at run time. All the other code should work as is by my reckoning.

The NSArray

If one were entierly uncomfortable with C Arrays, they could use an NSArray instead. However there are many downsides to this. The first is that NSArrays are quite a bit slower than C Arrays. C Arrays are very efficient constructs which were made to use very few processor instructions. On the other hand NSArray must use the Obj-C runtime, which passes messages between various objects, using many processor instructions indeed (relatively speaking, at least). The problem is further compounded because an NSArray can only store objects, and unsigned values are not objects. Thus the unsigned value has to be wrapped inside an NSNumber to be stored in the array, and then unwrapped when retrieved.

The NSDictionary

Lastely, one might consider using an NSDictionary. It certainly seems like the logical entity. It would allow one to store the command IDs using their string counterparts, and then access to the IDs would just be a matter of asking it for the ID corresponding to the string. Well, unfortunately, this is probably even worse than using an NSArray. Besides all the problems of an NSArray, using an NSDictioinary would mean that before a value could be retrieved, the command string would have to be hashed, then string compared, slowing things down even more.

If you still think you want to use an NSDictionary for your caching, I might want to make one more observation. It turns out that in the bowels of iTonamaton, the structure that stores the designated IDs for the command strings is our friend the NSDictionary. As it turns out, if one does need to store an association of keys and values, the NSDictionary is still the best choice. So if you still want to use an NSDictionary, I would suggest that you just look up the ID using the convenience method available in the ITPlugin class, because that is pretty much all it really does. Of course, I would hope that instead of persuading you to use this convenience method, I have dissuaded you from using anything but a C Array for caching.


Appendix B
ITPlugin Convenience Methods

The ITPlugin class has three convenience methods that are intended to make life for a plugin programmer easier. The purpose of this appendix is to explain why these particular methods were chosen, as well as demystify what these methods do.

I should note that at the time of writing this appendix, the methods on ITPlugin are still subject to change. Although the functionality of these methods will most likely not change, minor tweaks to the code, for the interests of speed, will probably occur. However, the versions of these methods presented here are fully functional, not to mention easier to follow than any optimized versions.

Reason For Being

When I first started to learn to write plugins for iTonamaton, the API was still in its infancy. (Actually, at the time of writing this, it still is in its infancy; but that is beside the point.) After spending a few minutes looking over the standard plugins code (the only plugin at the time), I started to notice that there were a few fragments of code that were being repeated very often. Being that I was just ten feet away from the creator of iTonamaton at the time, I turned to him and asked, "Wouldn't it be nice if there were convenience methods for a couple of the common tasks?" To which he replied, "Sure. Go ahead and make them if you want." So I did.

Later on we looked at the methods I chose for inclusion as convenience methods and noticed a pattern. They are all things which involve knowing the internal structure of iTonamaton. That is to say, they are shortcuts to tasks that involve other things than just passed notifications and the ITPlugin class. Additionally, they are the ones of those tasks that take the most code to execute. (For example, there isn't one for retrieving the event center; you get that easily enough by asking the owner.) Lastely, we decided we wanted as few of these convenience methods as possible, so as not to clutter things up with a monolithic API.

Anyway, in order to more familiarize you with iTonamaton, I plan on demystifying the black box that is these methods. Of course, if you have the source for iTonamaton, you could look at the code itself. However, it is not annotated nearly as well in the source (or not at all, as the case is at the time of writing). Therefore the following may be beneficial to the curious.

sendMessage:withNotification:

The sendMessage:withNotification: method sends the passed message text to the context in which the original message was sent. (This may be a /msg to a nick, or to a channel). This method is defined as follows. (I apologize if any of the lines are too long for your screen, but I wanted to preserve their full form.)

- (void)sendMessage:(NSString *)message withNotification:(NSNotification *)notification
{
    [owner sendMessage:message to:[[notification userInfo] objectForKey ITMsgReplyToKey]];
}

This single line of code compounds three method calls together, so we will look at each of these seperately:

First, we notice the snippet of code [notification userInfo]. This retrieves the user info dictionary for the passed notification (an NSDictionary).

Next, we see that the objectForKey: method is called on this returned NSDictionary. It is passed the key value contained in the constant ITMsgReplyToKey. This key causes the method to return an object which contains information on the context to which the reply should be sent. We need not concern ourselves with the internals of this object, as we simply pass it away for something else to use.

Lastly, we call the sendMessage:to: method on the owning ITonamaton object, passing it the message and the reply context objects. Thus the owning ITonamaton object routes the text message we want to send to the proper recipient for us.

commandFromNotification:

The commandFromNotification method (whose lines once again may be too long to fit on a narrow screen) takes a notification that contains a command key and returns the unsigned command ID value. It returns zero if there was no command. (Note that zero is never assigned to any command by iTonamaton.) The code for this method is displayed here:

- (unsigned)commandFromNotification:(NSNotification *)notification
{
    return [[[notification userInfo] objectForKey:ITCommandKey] unsignedIntValue];
}

Like the sendMessage:withNotification: method, the single line of code for this method is composed of several method calls. In this case, the chain is a little more linear, and hence easier to follow.

First, the code [notification userInfo] returns the user info NSDictionary for the passed notification.

Next, we get the object in the dictionary which encapsulates a command number by calling the objectForKey: method on the user info dictionary, passing it the key specified by the constant ITCommandKey. Note that if the notification was not for a command, then there will be no object for the key ITCommandKey. As a result, objectForKey: will return nil. This is okay in this case, as we'll see.

Now, the resulting object for the ITCommandKey key is an NSNumber that encapsulates the command ID. To get the command ID as an unsigned value, we call the unsignedInValue method on the NSNumber. (The type unsigned is just a synonym for unsigned int by the way.) Note that if the resulting object from the objectForKey: method call was indeed nil, then the unsignedIntValue method call on nil is ignored, and nil is returned.

Lastely, return returns the value of the whole expression to the caller; this value being the unsgined value retrieved from the unsignedIntValue in the last method.

But what if the the key doesn't exist, and a value of nil is returned by the unsignedIntValue method? If we look at the methods definition, the return type defined for this method is unsigned. Hence any value returned by this method is automatically casted to (i.e. turned into) an unsigned value. The unsigned value of nil is simply zero. Hence, this method returns zero if there is no value for the key ITCommandKey.

identifierForCommand:

The identifierForCommand: method returns the command identifier that corresponds to the command passed into the method (in string form). The code for this method is shown below:

- (unsigned)identifierForCommand:(NSString *)command
{
    if( !ownersCommandRegistry )
        ownersCommandRegistry = [[self owner] commandRegistry];
        
    return [ownersCommandRegistry identifierForCommand:command];
}

This method is roughly in two parts. The first part caches the owner ITonamaton object's command registry the first time this method is called, storing it in an instance variable. The second part fetches the ID number for the command.

If we look at the first part in more detail, we see that we have an if statment, and an assignemnt that is made if this if statement's test expression is true (YES).

Let us first look at the code that will be executed if the test is true. It simply gets the command registry object from the owning ITonamaton object, and makes the instance variable ownersCommandRegistry point at it.

Now let's look at the if statment. The expression !ownersCommandRegistry evaluates to YES if ownersCommandRegistry is nil. This is because a nil pointer evaluates as NO, but the exclamation point (the "not" operator) reverses the value of the expression. (This expression has the same effect as if( ownersCommandRegistry == nil ), but the way ITPlugin does it is better.)

We can now put these two parts together. Since the value of ownersCommandRegistry is initialized to nil, the code in the if statement executes the first time the method is called. The ownersCommandRegistry variable is then told to point at an object, and hence is no longer nil. Thus, the next time the method is called, the if statement evaluates as false and does not execute the assignment code. we have cached the pointer to the command registry in a more local variable. By doing this, we save ourselves function calls to the owner to get the command registry.

Pratically speaking, little time is saved by doing this caching, however implementations of this sort of goes back to that good programming philosophy I talked about before.

We now come to the last line. It should be fairly straightforward by now. It simply forwards the request for an identifier for a command to the ownersCommandRegistry object.


[back]   [TOC]   [forward]
Last Modified: $Date: 2003/12/18 09:55:14 $ by $Author: paploo $