Vincent Gable’s Blog

January 12, 2009

Stopping an AMWorkflow Workaround

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code | , , ,
― Vincent Gable on January 12, 2009

-[AMWorkflowController stop:] does not stop a workflow from executing (more info). Here is a workaround that will stop all workflows your application is be running.

When you execute a worklfow using Automator.framework, the workflow is run in its own process, which insulates it from the invoking application, and vica verca 1

. At least on Leopard, the program that runs the workflow is /System/Library/CoreServices/Automator Runner.app/Contents/MacOS/Automator Runner . It has the bundle-identifier com.apple.AutomatorRunner , and it will be a sub-process of your application, since your application kicked it off.

That gives us enough information to kill just the instances of Automator Runner that are running your worklfows. This will stop workflow execution dead in it’s tracks.


- (void) killAutomatorRunnerSubprocesses; {
   ProcessSerialNumber   PSN = {kNoProcess};
   while(GetNextProcess(&PSN) == noErr) {
      NSDictionary *info = (NSDictionary*)ProcessInformationCopyDictionary(&PSN,kProcessDictionaryIncludeAllInformationMask);
      if(info) {
         NSString *theirBundleID = [info objectForKey:@"CFBundleIdentifier"];
         if([theirBundleID isEqualTo:@"com.apple.AutomatorRunner"] && [self processWasLaunchedByUs:PSN]){
            NSLog(@"Killing AutomatorRunner sub-process: PSN={high=%u low=%u}", PSN.highLongOfPSN, PSN.lowLongOfPSN);
            KillProcess(&PSN);
         }
         CFRelease((CFDictionaryRef)info);
      }
   }
}

- (BOOL) processWasLaunchedByUs:(ProcessSerialNumber)PSN; {
   ProcessInfoRec processInfo = {0};
   //we use GetProcessInformation(), and not the more modern ProcessInformationCopyDictionary()
   //because ProcessInformationCopyDictionary stores the "ParentPSN" as a 32 or 64-bit number,
   //and it is not clear to me what the endian-safe way to transform it into a ProcessSerialNumber
   //structure is, see http://lists.apple.com/archives/carbon-dev/2007/Mar/msg00283.html
   if(GetProcessInformation(&PSN,&processInfo) != noErr)
      return NO;
   Boolean   theyAreOurChild = FALSE;
   ProcessSerialNumber ourPSN = {0,kCurrentProcess};
   OSStatus err = SameProcess(&processInfo.processLauncher, &ourPSN, &theyAreOurChild);
   return !err && theyAreOurChild;
}
   

Please let me know if this works for you, of if you have a better solution.


1
Automator.app does not appear to spawn an Automator Runner when it runs a workflow. I’m not sure why. But since the stop button works in Automator.app, understanding why might lead to a better work around. Or not.

December 31, 2008

Mac OS X Redesign: Feedback for “Hold Keys”

Filed under: Design,MacOSX,Usability | , , , ,
― Vincent Gable on December 31, 2008

To prevent particularly bad slips (physical, not cognitive, mistakes), Apple makes certain keys hold keys. That means you have to hold them down for a while before they do their thing, unlike any other button that you just tap to use. This prevents accidentally engaging the hold keys, because a quick tap isn’t enough to trigger them. Unfortunately, it causes major problems for users. Apple could fix things by simply adding 3 words to the key-pressed-indicators they already use, and displaying it immediately.

The eject key is a hold key on MacOSX 10.4.9+. It’s a pretty bad thing to accidentally activate, because it takes several seconds to put a physical disk back into a tiny slot . By comparison the most common slips (typos), can be corrected in a split-second with 2 keystrokes(delete + the right letter). The eject key is located just above the delete key (which behaves like backspace on other computers), and right next to F12, which by default is mapped to Dashboard. Both of these keys are relatively high traffic, so it’s a sure bet that many a slipped finger will tap the eject-key. Making the eject-key a hold key prevents accidental taps, because a tap isn’t enough to trigger it.

caps lock on new keyboards is a hold key to turn on (but not off). Anything that makes caps lock harder to engage is great, since there’s no good reason to have a caps lock key anyway.

But there is a big problem with what Apple has done here. There isn’t any indication that hold keys are special. Worse, if you try to use one the way you use every other button on the keyboard, mouse, or computer — by tapping them – nothing happens. It’s like the hold keys are broken. here’s a video showing how confusing the eject key is.

One of the first things I noticed after updating Mac OS X Tiger to 10.4.9 was that my CD eject key didn’t seem to work anymore

Unfortunatly, that is a common reaction.

All hold keys should give immediate feedback when tapped, and also indicate that they need to be held down to trigger their action. Macs already flash an translucent eject indicator when the eject key’s action is performed:

Media Eject Symbol

Immediately displaying the following indicator, and pulsing the symbol when the action is triggered does everything we need:

Media Eject Symbol + 'hold key down' message

This is just a quick mock-up to get the idea across, it has a lot of flaws.

Unlike the eject symbol, the text has no drop shadow — this makes it less busy, and easier to read; but arguably unaesthetic.

The text is in all lowercase, because all words on Apple keyboards are in all lowercase. The idea is that it more closely associates the instructions with the keyboard. It’s probably a mistake, since it does not follow the capitalization rules for Mac OS X, but it made sense to me at the time.

The text also should have a stronger border, so it will show up clearly no matter what it is displayed over. And it should be bigger. And in a better font.

But hopefully this is enough to get my idea across.

December 29, 2008

Thread Local Storage in Cocoa

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code | , ,
― Vincent Gable on December 29, 2008

[[NSThread currentThread] threadDictionary] gives you an NSMutableDictionary that you can use for thread-specific storage.

December 28, 2008

Fast Enumeration In Objective-C 1.0

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code | , ,
― Vincent Gable on December 28, 2008

I have used ForEach.h to do “Fast Enumeration” in Mac OS X 10.4 “Tiger”, without any problems. You must set the “C Language Dialect” in Xcode to C99 for it to work. Credit to Michael Tsai for the code, you can find other implementations of it on his website.

With ForEach.h you can do:

#import "ForEach.h"

foreach(thing, collection) {
   /*do stuff with thing*/
}

foreacht(NSString, name, allNames)
   ;//name is typed as an NSString*

With a large collection, the foreach macro should be faster then using NSEnumerator or objectAtIndex:, because it calls the nextObject function directly, without going through Objective-C’s message sending. I have not benchmarked it, because I haven’t had a reason to optimize the mechanics of my for-loops, so I don’t know by how much. But it should give you the fast in Fast Enumeration.

If you can’t use Objective-C 2.0’s Fast Enumeration (requires Leopard or, iPhone), then I highly recommend ForeEach.h.

Open Radar

Filed under: Announcement,MacOSX,Programming | ,
― Vincent Gable on December 28, 2008

Open Radar is a place for developers to share bug reports they have filed with Apple. Please post bugs there after you have filed them with Apple.

Understandably, third-party developers don’t have access to Apple’s bug database.

But that means they can’t see bugs someone else has filed, hence Open Radar. (Update 2009-01-04: to give you an idea of how difficult this can be, recently Apple closed some of bugs I filed as “Duplicate”, but I can’t read the original bug report they are tied back to. So I have no way of knowing what the status of the fix is, if they have a work around, or even if it’s already been fixed.)

Amazingly “(it was) less than 24 hours from idea proposition to (Open Radar) being built, deployed, and used.” That should give you an idea of how useful this can be.

Open Radar is still nascent. There’s no RSS feed for example. But with community involvement, it can only get better.

I’ve filled my bugs, have you?

Bug: @encode(long double)

Filed under: Bug Bite,MacOSX,Objective-C,Programming | , , , ,
― Vincent Gable on December 28, 2008

UPDATE 2010-08-16: I ran a test today, on Mac OS X 10.6.4 (i386), and the issue has been resolved, @encode(long double) evaluates to "D". I’m not sure when this change happened, or what platforms it effects.

@encode(long double) returns the same string, "d", as @encode(double), at least on i386, Mac OS X 10.5.6 / 9G55. But a long double and a double are not identical:


double aDouble = M_PI;
long double aLongDouble = M_PI;
NSLog(@"sizeof(aDouble) = %d, sizeof(aLongDouble) = %d",

   sizeof(aDouble), sizeof(aLongDouble));
NSLog(@"aDouble = %f", aDouble);
NSLog(@"aLongDouble printed as a double (%%f) = %f", aLongDouble);
NSLog(@"aLongDouble printed as a long double (%%Lf) = %Lf", aLongDouble);

sizeof(aDouble) = 8, sizeof(aLongDouble) = 16
aDouble = 3.141593
aLongDouble printed as a double (%f) =
   -88796093704928900002674917893032219152220160.000000
aLongDouble printed as a long double (%Lf) = 3.141593

I haven’t tested another configuration. TYPE_LONGDOUBLE_IS_DOUBLE in ConditionalMacros.h is 1 under __ppc__, so this might not be a problem on PowerPC.

Submitted as radar://6468314.

December 26, 2008

Always Update the View From the Main Thread

Filed under: Announcement,Bug Bite,Cocoa,Interface Builder,MacOSX,Objective-C,Programming,Quotes | , ,
― Vincent Gable on December 26, 2008

I wish I’d read this years ago:

AppKit, the GUI framework, is not thread safe. In order for things to work properly, you (almost) always need to update GUI classes from the main thread

Dave Dribin (slightly edited)

I’ve run into UI + threading problem before, but I’d just never seen this limitation of AppKit spelled out.

Dave’s article explains how to call code on the main thread better then I can.

December 22, 2008

Resources in Unit Tests and Other Frameworks

Filed under: Bug Bite,Cocoa,MacOSX,Objective-C,Programming,Sample Code | , , , ,
― Vincent Gable on December 22, 2008

To load a resource inside a unit test or other bundle, do:

NSBundle *unitTestsBundle = [NSBundle bundleForClass:[self class]];
NSString *pathToResource = [unitTestsBundle pathForResource:name ofType:fileExtension];

[NSBundle mainBundle] points to the bundle for the current executable, so it’s handy in an application (where it will point to your .app bundle). But since unit tests are loaded into another program or test-harness to run, it’s not appropriate to use it in a unit test.

How To Multi

Avoid distributed computing unless your code is going to be run by a single client with a lot of available hardware. Being able to snarf up CPU cycles from idle hardware sitting around in the user’s house sounds cool but just doesn’t pay off most of the time.

Avoid GPGPU on the Mac until Snow Leopard ships unless you have a really good application for it. OpenCL will make GPGPU a lot more practical and flexible, so trying to shoehorn your computationally expensive code into GLSL or CoreImage today just doesn’t seem worth it.

Using multiple processes is a good idea if the subprograms are already written. … If you’re writing your code from scratch, I don’t recommend it unless you have another good reason to write subprocesses, as it’s difficult and the reward just isn’t there.

For multithreading, concentrate on message passing and operations. Multithreading is never easy, but these help greatly to make it simpler and less error prone.

Good OO design will also help a lot here. It’s vastly easier to multithread an app which has already been decomposed into simple objects with well-defined interfaces and loose coupling between them.

Mike Ash (emphasis mine, line-breaks added). The article has more detail and is very much worth reading.

One point that this advice really drives home for me is that you need to focus on making good code first, and defer micro-optimizations. If taking the time to clean up some code makes it easier to parallelize, then you are optimizing your code by refactoring it, even if at a micro-level you might be making some of it slower by, say, not caching something that takes O(1) time to compute.

Apple does not sell a Mac that’s not multi-core, and even the iPhone has a CPU and a GPU. There’s no question that optimization means parallelization. And all signs point to computers getting more parallel in the future. Any optimization that hurts parallelization is probably a mistake.

December 20, 2008

Automatically Freeing Every @property

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code | , ,
― Vincent Gable on December 20, 2008

Here’s a category of NSObject that can simplify a dealloc method. It adds the method, setEveryObjCObjectPropertyToNil, that sets every property of the receiver to nil. (Properties that are readonly or not an Objective-C object are ignored.) This frees the underlying object, if the property is declared copy or retain; and it does no harm if it was declared assign.

If every ivar (member variable) in your object has a property declared for it, then your dealloc method can often be replaced by this macro, or it’s two-line expansion:

#define PROPERTY_ONLY_OBJECT_DEALLOC \
- (void) dealloc { \
   [self setEveryObjCObjectPropertyToNil]; \
   [super dealloc]; \
}

Limitations

Pointers

Any pointers (eg char*) will not be set to NULL; this includes pointers to an Objective-C object (eg NSError** outError). Of course NSObject* obj will be set to nil since it is considered an Objective-C object, even though it is written as if it were a pointer.

It is easy to build on setEveryObjCObjectPropertyToNil and have something that sets pointers to NULL as well. But I felt it was too risky. Sending a message to nil is valid, but dereferencing a NULL pointer is a “bus error” crash. [nil release]; does no harm, but free(NULL); is bad news. A settable @property that takes a raw pointer is a hybrid Objective-C, and “old”-C creature — I’ve never seen such a thing, so I’m wary of assuming that feeding it a NULL would be valid. Plus, opening the door to pointers means dealing with handles (pointers-to-pointers) and their ilk.

ivars

Any ivars (member variables) with no settable @property declared on them will not be freed. You can inspect your own ivars like you can @propertys (example), but it would not be safe to automatically release them. Some of them might be weak-links, meaning the object they point to was not sent a retain message. Weak-links are not terribly rare, for example objects always have a weak link to their delegate.

The Code

You will still need to download the source to get helper functions like SetterNameFromPropertyName() for this to actually run, but this should give you an idea of how it works:


@implementation NSObject(CleanUpProperties)
- (void) setEveryObjCObjectPropertyToNil;
{
   unsigned int i, propertyCount = 0;
   objc_property_t *propertyList = class_copyPropertyList([self class], &propertyCount);
   if(propertyList){
      for(i = 0; i < propertyCount; i++){
         const char *propertyAttrs = property_getAttributes(propertyList[i]);
         if(PropertyIsObjCObject(propertyAttrs) && PropertyIsSettable(propertyAttrs)) {
            NSString *setterName = SetterNameFromAttributes(propertyAttrs);
            if(!setterName)
               setterName = SetterNameFromPropertyName(property_getName(propertyList[i]));
            [self performSelector:NSSelectorFromString(setterName) withObject:nil];
         }
      }
      free(propertyList);
   }
}
@end


Download The Code


And please let me know how it works for you. I’ve started using setEveryObjCObjectPropertyToNil even if I only have one @property, because it means I’ll never forget to free one.

Warning (Update 2009-05-29)

Uli Kusterer gives a good reason not to use this code,

Don’t Use Accessors in Constructors or Destructors

This may sound a bit odd, but there is a reason to this madness. Constructors (i.e. -init methods in ObjC) and destructors (i.e. -dealloc or -finalize) are special methods: They are called before your object has fully been initialized, or may be called after it has already been partially torn down.

If someone subclasses your class, your object is still an object of that subclass. So, by the time your -dealloc method is called, the subclass has already been asked to do its -dealloc, and most of the instance variables are gone. If you now call an accessor, and that accessor does anything more than change the instance variable (e.g. send out notifications to interested parties), it might pass a pointer to its half-destructed zombie self to those other objects, or make decisions based on half-valid object state. The same applies to the constructor, but of course in reverse.

Now, some people that accessors should not be doing anything but change instance variables, but that is utter nonsense. If that was all they’re supposed to do, we wouldn’t need them. Accessors are supposed to maintain encapsulation. They’re supposed to insulate you from the internal details of how a particular object does its work, so you can easily revise the object to work in a completely different way, without anyone on the outside noticing. If an accessor could only change an instance variable, you would have very limited means to change this internal implementation.

Moreover, I don’t think Apple would have introduced Key-Value-Coding and Key-Value-Observing if they didn’t agree at least partially that it’s fine to do a bunch of things in response to an accessor being used to mutate an object.

Update 2009-11-29

I’m amused at the prevalent “Apple knows best” attitude. Bindings, garbage collection, NSOperationQueue, and so many other things, Apple has gotten wrong and burned me in the process. I always trust my own evaluation over their recommendations.

Mike Ash, Using Accessors in Init and Dealloc

« Newer PostsOlder Posts »

Powered by WordPress