Easier boolean logic with NSPredicates

March 12th, 2008

GTBooleanLogic is an addition to Foundation Kit's NSPredicate class, available in Mac OS X 10.4 and newer. The code is released under the MIT licence.

Last updated on March 12, 2008.

Download the Code (about 4KB; licence included in source files.)

Boolean logic with NSPredicates is possible, but lengthy. Consider XORing two predicates. XOR can be implemented in a few ways, but the most obvious is "(A or B) but not (A and B)." To implement it with the methods provided in Foundation Kit, one would need to write this lengthy statement:

Objective C:
NSPredicate *xor = [NSCompoundPredicate andPredicateWithSubpredicates:
   [NSArray arrayWithObjects:
      [NSCompoundPredicate orPredicateWithSubpredicates:
         [NSArray arrayWithObjects: a, b, nil]
      ],
      [NSCompoundPredicate notPredicateWithSubpredicate:
         [NSCompoundPredicate andPredicateWithSubpredicates:
            [NSArray arrayWithObjects: a, b, nil]
         ]
      ],
      nil
   ]
];

With these additional methods, it's much shorter:

Objective C:
NSPredicate *xor = [[a or: b] and: [[a and: b] not]];

Of course, an -xor: method is also provided:

Objective C:
NSPredicate *xor = [a xor: b];

Twitter

March 6th, 2008

I'm now on twitter as grynspan. Send me a peep. (No, I will not provide mFurc tech support there.)


[NSString stringWithFormat:] caution

January 14th, 2008

A note of caution to anyone developing for Mac OS X 10.4 Tiger or earlier. If you use +[NSString stringWithFormat:], CFStringCreateWithFormat(), or any other Cocoa or Core Foundation method or function that involves format strings, do not use PRIuMAX, PRIiMAX, SCNxMAX, or any other of the type specifiers that operate on the intmax_t or uintmax_t types. They devolve to "%jx" etc., and the "j" size specifier is not supported in Cocoa or Core Foundation until Mac OS X 10.5 Leopard.

If you want to use them (as I do), you can add the following preprocessor instructions to your prefix header. They will redefine the various type specifiers to use the "ll" length specifier instead of the "j" length specifier, which will still be correct on all versions of Mac OS X compiling with GCC. Be warned that this is not necessarily portable to other systems.

C:
#if !defined(GT_INTMAX_REDEFINITIONS)
#define GT_INTMAX_REDEFINITIONS

#undef PRIdMAX
#undef PRIiMAX
#undef PRIoMAX
#undef PRIuMAX
#undef PRIxMAX
#undef PRIXMAX

#define __GT_PRI_MAX_LENGTH_MODIFIER__ "ll"

#define PRIdMAX __GT_PRI_MAX_LENGTH_MODIFIER__ "d"
#define PRIiMAX __GT_PRI_MAX_LENGTH_MODIFIER__ "i"
#define PRIoMAX __GT_PRI_MAX_LENGTH_MODIFIER__ "o"
#define PRIuMAX __GT_PRI_MAX_LENGTH_MODIFIER__ "u"
#define PRIxMAX __GT_PRI_MAX_LENGTH_MODIFIER__ "x"
#define PRIXMAX __GT_PRI_MAX_LENGTH_MODIFIER__ "X"

#undef SCNdMAX
#undef SCNiMAX
#undef SCNoMAX
#undef SCNuMAX
#undef SCNxMAX

#define __GT_SCN_MAX_LENGTH_MODIFIER__ "ll"

#define SCNdMAX __GT_SCN_MAX_LENGTH_MODIFIER__ "d"
#define SCNiMAX __GT_SCN_MAX_LENGTH_MODIFIER__ "i"
#define SCNoMAX __GT_SCN_MAX_LENGTH_MODIFIER__ "o"
#define SCNuMAX __GT_SCN_MAX_LENGTH_MODIFIER__ "u"
#define SCNxMAX __GT_SCN_MAX_LENGTH_MODIFIER__ "x"

#endif /* !defined(GT_INTMAX_REDEFINITIONS) */

mFurc 3.1

November 24th, 2007

mFurc 3.1 is out, including full Leopard support and new, bigger icons.

Please note, future updates to mFurc are on hold due to contractual obligations. I hope to get back to mFurc soon.


Autoreleasing in Core Foundation

August 26th, 2007

GTCFAutoreleaseRegion is a supplement to Core Foundation's memory management scheme. The code is released under the MIT licence.

Last updated on December 15, 2007.

Download the Code (about 4KB; licence included in source files.)

For some reason, Apple decided to forego autoreleasing in Core Foundation. Now, with the upcoming garbage collection in Leopard, this may seem to be somewhat of a moot point. But if you're developing for Tiger or earlier, or if you just don't trust garbage collection in C (hi mom!) then you've probably been miffed once in a while when you've had to work with Core Foundation. See, without autorelease pools, the following Objective-C one-liner:

Objective C:
NSString *greeting = [NSString stringWithFormat: NSLocalizedString(@"GREETING_%@", nil), NSUsername()];

Is equivalent to the more tedious Core Foundation block:

C:
CFStringRef gFormat = CFCopyLocalizedString(CFSTR("GREETING_%@"), NULL);
CFStringRef uName = CSCopyUsername(FALSE);
CFStringRef cfGreeting = CFStringCreateWithFormat(NULL, NULL, gFormat, uName);
/* use cfGreeting */
if (cfGreeting)
   CFRelease(cfGreeting);
if (uName)
   CFRelease(uName);
if (gFormat)
   CFRelease(gFormat);

I'm aware it seems like a petty complaint, but less typing is always good when programming is involved. The fewer keystrokes you make, the fewer errors and bugs you risk. So with that in mind, I present Autorelease Regions for Core Foundation.

You might notice that I don't call them autorelease pools. This is because, in Cocoa, autorelease pools are objects and can be manipulated as such. In my implementation, there are no objects or values you can manipulate, so the name change reinforces the idea that this is a different beast. Also, because autorelease regions are coded in pure C (with POSIX-specific and Core Foundation-specific code), they are not interchangeable with autorelease pools. You can still use autorelease pools, of course. This implementation is thread-safe.

Here's a usage example, based on the previous examples. It's still not as terse as the Objective-C equivalent, but it is shorter and cares less about memory management of individual Core Foundation values. Note that the usefulness of autorelease regions becomes more apparent when you're working with a lot of Core Foundation values. Also note that you can safely pass NULL to GTCFAutorelease():

C:
GTCFAutoreleaseEnterRegion();
{
   CFStringRef gFormat = GTCFAutorelease(CFCopyLocalizedString(CFSTR("GREETING_%@"), NULL));
   CFStringRef uName = GTCFAutorelease(CSCopyUsername(FALSE));
   CFStringRef cfGreeting = GTCFAutorelease(CFStringCreateWithFormat(NULL, NULL, gFormat, uName));
   /* use cfGreeting */
}
GTCFAutoreleaseExitRegion();

The functions GTCFAutoreleaseRegionIsPresent() and GTCFAutoreleaseInstallMissingRegionHandler() are included, which can aid you when you're mixing with code that might not know about autorelease regions.

The functions GTCFAutoreleaseGetRegionValueCount() and GTCFAutoreleaseCopyRegionValues() are also present, and may be useful for debugging.

This code also includes a few useful symbols: GTCFPrint(), GTCFPrintv(), GTCFPrintValue(), kGTCFIndexMin, and kGTCFIndexMax.


mFurc 2.2

June 27th, 2007

mFurc 2.2 is now available, featuring eye candy and a new spam filter called Xparve.


My code's in Parallels!

June 22nd, 2007

If you're using Parallels, you might be interested to know that code I wrote is apparently included with it. As TUAW has reported, Parallels includes a compiled copy of MacFUSE, which lets it mount NTFS drives and do some other nifty stuff.

I didn't write MacFUSE, but I did write the GTResourceFork class. And that class is included in MacFUSE to provide some sort of resource fork support (not quite sure how it's used, but it's in there.)

Steve Jobs showed a glimpse of Parallels during his most recent keynote. So, through my amazing skills of deductive reasoning, I can say: I was on stage at WWDC07!


LSMinimumSystemVersion broken in 10.4.10

June 22nd, 2007

For any Mac developers reading this: the LSMinimumSystemVersion Info.plist key does not work if set to 10.4.10. You therefore cannot use it to require that your users have the latest Tiger update.

Have a nice day!


I live!

June 2nd, 2007

So, you may have noticed that this domain was, shall we say, spamalicious for the past two or so weeks. It was registered with RegisterFly back in 2002, before anybody really knew that they were an unspeakable creeping horror. Well, RegisterFly imploded before I had a chance to renew the domain for this year, and then the domain expired while RegisterFly was suck. Fortunately for me, they didn't re-sell the domain to somebody else, but I wasn't able to renew it until GoDaddy took over control of it.

So now I'm back! Hopefully, I didn't miss any important emails. (Come to think of it, I know I did.)

Strangely, I never lost access to my own content at ghosttiger.com.


Converting CGImageRef to NSImage *

May 29th, 2007

Perhaps one of the most blatant oversights in Apple's APIs is that, given the eight or nine different kinds of images, converting betwixt types is often difficult. So I present to you a quickie NSImage category to convert from a CGImage to an NSImage. There may be more efficient ways to do this, but it works fine as-is. Note that this is Tiger-only code.

Update: Anybody who has attempted to use the CIImage-based version of this code has no doubt noticed that it leaks like a banshee. (…what?) I've updated the code to be less horrid.

Update: The fallback code (if NSCGImageRep is not available) now just draws directly into an NSImage and doesn't bother creating the NSBitmapImageRep backing store for it.

The header!

Objective C:
/* header file */
@interface NSImage (GTImageConversion)
+ (NSImage *)gt_imageWithCGImage: (CGImageRef)image;
@end

And the implementation!

Objective C:
/* implementation file */

@implementation NSImage (GTImageConversion)
+ (NSImageRep *)_gt_CGImageRepFromCGImage: (CGImageRef)image {
   NSImageRep *rep = nil;

   /* NSCGImageRep is a private AppKit class which, if available, will be more
      trustworthy than drawing the image into a new image rep */

   Class nsCGImageRepClass = objc_lookUpClass("NSCGImageRep");
   if (nsCGImageRepClass && class_getInstanceMethod(nsCGImageRepClass, @selector(initWithCGImage:))) {
      rep = objc_msgSend(nsCGImageRepClass, @selector(alloc));
      rep = objc_msgSend(rep, @selector(initWithCGImage:), image);
      rep = objc_msgSend(rep, @selector(autorelease));
   }

   return rep;
}

+ (NSImage *)gt_imageWithCGImage: (CGImageRef)image {
   if (!image)
      return nil;

   NSImage *result = nil;
   
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   {
      NSImageRep *rep = [self _gt_CGImageRepFromCGImage: image];
      if (rep) {
         /* wrap the image rep in an NSImage -- the image is released upon method return */
         result = [[NSImage alloc] initWithSize: [rep size]];
         [result addRepresentation: rep];
      } else {
         /* failed to use NSCGImageRep class -- instead, just create the image and draw into it */
         size_t width = CGImageGetWidth(image);
         size_t height = CGImageGetHeight(image);

         result = [[NSImage alloc] initWithSize: NSMakeSize(width, height)];
         [result lockFocus];
         {
            CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
            if (context) {
               /* NOTE: your particular colorimetric needs may require some tweaking */
               CGContextSetInterpolationQuality(context, kCGInterpolationNone);
               CGContextSetRenderingIntent(context, kCGRenderingIntentAbsoluteColorimetric);

               CGContextDrawImage(context, CGRectMake(0.0, 0.0, width, height), image);
            }
         }
         [result unlockFocus];
      }
   }
   [pool release];
   
   return [result autorelease];
}
@end