Nifty Box News

Wednesday, December 27, 2006

MacSanta is over

A huge THANKS to the guys at Rogue Amoeba for organizing the MacSanta sales! It's been a great opportunity for me and other small developers out there. When you start as a new developer it's kind of hard to get the word out and make people notice your app.

Thursday, December 21, 2006

My Own Head

How about getting your self-named Cocoa notepad application with your own icon? Then visit this new site myownhead!
And yes, you can win a copy of Nifty Box!

Wednesday, December 20, 2006

Worker Thread - NSInvocation Part 2

NSInvocation is not only nice to post delayed multi-argument messages, you can also use it for constructing a lightweight worker thread implementation.

Sometimes it happens that your app has to do lot of work, that you would like to do it in the background as you think it would be a nice thing if the gui would stay responsive to the user actions. Sometimes invoking a NSThread is a valid solution, but if you want to schedule many tiny tasks to the background creating a new thread for each of them might not be the best idea.

Another possibility is using Distributed Objects (DO). But DO for intra-application communication might be a bit of an overkill and during my experimenting I experienced, that outsourcing a working thread via DO and using the NSRunLoop is not the ideal solution: if the runloop of the worker thread is currently busy working on a past message you have to wait on the main thread if you want to post a new DO message until the worker thread's runloop is free again.

So a solution I am using for the background Spotlight export of my app is based on the producers and consumer model: I create a thread safe queue in which the producer (main) thread posts new tasks and a consumer thread performs these tasks. This is very lightweight and the main thread does not block during the posting of the tasks.

How does it work? Again using NSInvocations. The tasks, that are written to the queue are just NSInvocations, so you can post any message the target object understands.

So here's an example:

In the main thread you initialize NBInvocationQueue and start the worker thread


NBInvocationQueue *finderEventQueue = [[NBInvocationQueue alloc] init];
[NSThread detachNewThreadSelector:@selector(runQueueThread) toTarget:finderEventQueue withObject:nil];


Now the NBInvocationQueue is ready to take messages. You post them like this:


[[finderEventQueue performThreadedWithTarget:spotlightExporter]
exportFinderCommentAtPath:path
tags:tagArray
comment:comment]

This will invoke [spotlightExporter exportFinderCommentAtPath:...] on the worker thread.

The methods to queue messages are:

-(id)performThreadedWithTarget:(id)target;
-(id)performThreadedWithTarget:(id)target afterDelay:(NSTimeInterval)delay;


If you want to stop the worker thread you can use the method:

-(void)stopThreadWaitUntil:(BOOL)wait;

which waits until all open tasks have been de-queued if the flag "wait" is set.

The source code is here:

NBInvocationQueue.h and NBInvocationQueue.m

Feel free to use this class as you like, NO WARRANTY!

Inspiration for this class has been producers and consumer model and CInvocationGrabber from ToxicSoftware.

Labels: , ,

Tuesday, December 19, 2006

MacSanta

Maybe you've already heard about the latest Mac software promotion MacSanta organised by Rogue Amoeba. This is a great idea and I am happy to join the crowd with Nifty Box.

Purchase Nifty Box with a 20% discount through December 25: just enter the promotion code "MACSANTA" in the purchase process.

Labels:

Tuesday, December 12, 2006

NSInvocation cleans code

Update: (2008-08-07)
There's been a small (but potent) bug in the given implementation. I somehow move the release of the invocation:
[myInvocation release]; myInvocation = nil;
into the dealloc method of the _UFLatePerformer class. This is bollocks, because the invocation contains _UFLatePerformer as target itself. Therefore I have to release the invocation as soon as it has been invoked. Otherwise there is a retain-cycle and the dealloc-method of _UFLatePerformer is never called.
The code below has been updated accordingly.


During the development of Nifty Box I've grown to use NSInvocations more and more. One thing I am using this class especially for is a wrapper for NSObject's performSelector:withObject:afterDelay:. This method invokes the selector with one argument after a specified delay. My problem was that I can only pass one argument for the selector, but it often happens that I want to invoke a method with more than one argument. So I had to pack all the arguments into a NSDictionary, write a proxy method, inside this proxy function unpack the arguments from the dictionary and invoke the original method. This is messy.

For this I have written a small category add-on for NSObject:

@interface _UFLatePerformer : NSObject {
NSInvocation *myInvocation;
id target;
}
-(id)initWithTarget:(id)theTarget;
-(void)performLate;
@end

@interface NSObject (NSObject_laterInvocation)
-(id)performAfterDelay:(NSTimeInterval)delay;
@end

@implementation _UFLatePerformer

- (id) initWithTarget:(id)theTarget {
[super init];
if (self != nil) {
target = theTarget;
[target retain];
}
return self;
}

- (void) dealloc {
[target release]; target = nil;

[super dealloc];
}


-(void)forwardInvocation:(NSInvocation *)invocation;
{
myInvocation = invocation;
[myInvocation retain];
[myInvocation retainArguments];
}

-(BOOL)respondsToSelector:(SEL)aSelector;
{
BOOL result = [super respondsToSelector:aSelector];
if (result == NO)
result = [target respondsToSelector:aSelector];
return result;
}

-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector;
{
NSMethodSignature *result = [super methodSignatureForSelector:aSelector];
if (!result)
result = [target methodSignatureForSelector:aSelector];
return result;
}


-(void)performLate;
{
[myInvocation invokeWithTarget:target];
[myInvocation release]; myInvocation = nil;
}

@end


@implementation NSObject (NSObject_laterInvocation)

-(id)performAfterDelay:(NSTimeInterval)delay;
{
_UFLatePerformer *latePerformer = [[_UFLatePerformer alloc]
initWithTarget:self];
[latePerformer performSelector:@selector(performLate)
withObject:nil afterDelay:delay];
[latePerformer release];
return latePerformer;
}

@end


This basically works like the NSUndoManager's [[undoManager prepareWithInvocationTarget:self] myMethod:someArgument anotherArgument:secondArgument]

You use this new method like that:

[[myObject performAfterDelay:0.0] someMehodWithArgument1:argument1 argument2:argument2]


This is an implementation of the Trampoline Object.

Feel free to use this snippet as you wish. No warranty however.

Labels: , ,

Sunday, December 10, 2006

New feature for 1.1: Spotlight export

The last couple of weeks I've working on the export of tags and comments to Spotlight. This has been much more complicated than I expected. Nifty Box now writes the tags or comments to the Spotlight Comment field in the Finder's "Get Info..." window (for file items). This is a bit tricky.

First of all only the Finder can edit this field, which means one has to use AppleScript or AppleEvents. This is ok, there exists some source code from Apple (MoreFinderEvents) to do this. But during the testing sometimes strange things happened: for long comments written to this field it seems, that the Finder looses information about all the comments and icon positions in the folder of the changed item. After some experimenting I found, that the Spotlight Finder comment is written to the hidden .DS_Store file in the folder of the specific file. The .DS_Store contains all specific Finder information (like icon position and Spotlight comments) of all the files in the folder. But this information is somehow coded in blocks, which means that there is a maximum length set implicitly for the Spotlight comments. If you try to save a longer comment, the .DS_Store gets corrupted. By trial-and-error I concluded, that the combined length of the comment plus the length of the filename must not be greater than 1012 UTF-16 characters. Nifty Box will deal with this. For web bookmarks I dont use the Spotlight comment field, but provide an own Spotlight importer, which is much more convenient.

The way the Spotlight finder comments are used by Nifty Box is as follows: first all tags are written into the field, whereby these can be prefixed by a special character like "|" or "&". If you choose to use such a prefix, the Spotlight search can be narrowed down only to the Nifty Box items. An example: if you like to search in Spotlight for items tagged with "Apple" you would get a zillion hits if you just type in "Apple" in the search field, because - as you might guess - the word "Apple" can be found quite a lot on the mac. But if you only want to view the Nifty Box items, tagged with "Apple", you can define just a tag prefix like "|" and search in Spotlight for "|Apple" which should only give you the Nifty Box items.

After the tags you can choose to export your notes too. At the end a marker "_nybx_" is inserted into the comment field. After this marker the original comment from the Spotlight comment field is preserved. If the complete string (Nifty Box additions plus original comment) gets too long for the .DS_Store file, the Nifty Box additions will be shortened leaving the original comment complete.

Just to give you an impression, here is the new preferences tab for the Spotlight Export settings:



This will export a Spotlight Finder comment like this:



And here is an example Spotlight search for the tag "Mars": (please note the prefix "|")

In the screenshot you can also see the tagged web bookmarks at the top, which are provided by an Spotlight importer and not via the Finder's Spotlight comment field.

Using the Finder's Spotlight comment field for the file items also gives you information directly in the Finder if a file is tagged by Nifty Box: you can activate the "Comments" table column in the Finder window, which will show you the tag information for all files in a given folder:



So this export functionality is now implemented and in beta test. I am now working on the second new feature for version 1.1. I hope I will finish the development in December such that the new version can be released at the beginning of the new year.

Labels: