Tuesday, 30 October 2007

MBFileEvents

Version 1.0
Download

MBFileEvents is a category on NSString which allows you to register to receive notifications when a file is modified. It provides an extremely light-weight interface wrapped around a kqueue/kevent implementation.

You can add a file observer with a single line of code:

BOOL success = [string addFileEventsObserver:self];

Provided that string is a valid path and there was no error opening the file, this method will return YES. If this is the first time you are adding a file observer, a kqueue will be created and a background thread will be spun off to monitor the queue for events.

When an event is detected the observer will receive the following callback, declared as an informal protocol:

- (void)observeFileEvent:(MBFileEvent)event forFilePath:(NSString *)path;

The event is simply a bitfield, and can be masked to determine the components of the event. For example:

if ((event & MBDeleteFileEventMask) == MBDeleteFileEventMask)
DLog(@"File at path '%@' was deleted", path);


Note that you are monitoring the file itself, not the path, so once the file is deleted you won't receive any further callbacks. If you anticipate that the file has been replaced by a new file at the same path, you must add the observer again to begin monitoring the new file.

To remove an observer, simply call the following:

[string removeFileEventsObserver:self];

This isn't necessary if you are using garbage collection.

MBFileEvents is released under the modified BSD license, with no warranty of any kind. Leopard is required, I'm sorry to say, because I'm using the new NSPointerArray class for weak referencing of observers under garbage collection.

MBThreadingProxies

Version 1.0
Download

MBThreadingProxies is a category on NSObject which allows you to send a message to an object and have it delivered asynchronously on another thread. The following proxies are available:

– An
operation queue proxy, which makes use of Leopard's NSOperation API to deliver the message on one or more background threads which are created, managed, and reused by the system. If the return value of the method you are calling is an object you will get back a future proxy.

– A
main thread proxy, which delivers the message on the main thread.

– A
background thread proxy, which delivers the message on a new background thread which exits after the message send completes.


Operation queue proxy

[[object operationQueueThreadingProxy] message];

The message is delivered using a single shared instance of NSOperationQueue. You can control the degree of concurrency used by the queue if you wish. For instance, you can force all your message sends to be executed on a single worker thread like this:

[[NSObject sharedOperationQueue] setMaxConcurrentOperationCount:1];

For methods with non-object return types, the return value is undefined. (Don't count on it being 0!) Methods that return an object will always return a future proxy.

Exceptions raised during the message send are logged to the console.


Future proxy

id futureProxy = [[object operationQueueThreadingProxy] message];
//  Do other expensive stuff here.
NSLog(@"%@", futureProxy);


A future proxy is a placeholder for an object returned by an operation queue proxy method call. The message send will proceed asynchronously as normal, until you attempt to use the future proxy. At that point the thread you're on will block (if necessarily) until the message send has completed and the result can be determined.

The future proxy is extremely transparent - you can treat it as if it actually were the result. The only exception to this is if you need to test whether or not the result is nil. You'll always have a valid future proxy object, even if the result that it acts as a proxy for eventually turns out to be nil, so I've added a method to NSProxy which allows you to test whether or not the target is nil by sending a message to the proxy, like so:

BOOL isNotNil = [futureProxy isNotNil];

The performance of future proxies is quite good. NSInvocation is not required to forward messages to the target, and once the result is resolved for the first time subsequent forwards don't even involve a lock.

In addition to the logging of exceptions mentioned above, if you attempt to use a future proxy and an exception was raised during the operation queue proxy message send, that exception will be thrown. In addition, if you use the shared operation queue to cancel the operation and then attempt to access the future proxy, an NSInvocationOperationCancelledException will be thrown.


Main thread proxy

[[object mainThreadThreadingProxy] message];

Simply uses
-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:] to deliver the message asynchronously on the main thread. This is similar to other third party offerings such as Uli Kusterer's UKMainThreadProxy, although his version appears to be synchronous for some reason.


Background thread proxy

[[object backgroundThreadThreadingProxy] message];

This works in exactly the same way as the main thread proxy, except that it uses
-[NSObject performSelectorInBackground:withObject:] to deliver the message on a one-shot worker thread.

Exceptions seem to be totally swallowed, which is a shame.


Notes

– All proxies retain their targets. If you're not using garbage collection, having an object retain a proxy to itself will lead to a retain cycle.

– As you might imagine, you can use these proxies from any thread. You can even use the same proxy from more than one thread at once.

– Methods declared by NSProxy or the NSObject protocol are for the most part passed directly to the target on the current thread, and so have valid return values. The only exceptions are the methods
retain, retainCount, release, autorelease, and self, which are handled by the proxy itself.

– For obvious reasons, this is Leopard only.

– This code is released under the modified BSD license, with no warranty of any kind.