2

I'm trying to zip files using the command-line utility through NSTask.

pseudocode:

controller:
  init:
    register_self_as_observer_of_nstask_notifications

  startZip(file):
    file = somefileobject 
    task = "zip" with file path as argument
    task.launch

  notification_listener(notification):
    task = notification.get_object
    file = task.??? 

So how can I find out which file object the notification pertains to? I usually use the userInfo dictionary for such things, but NSTask has no such option. From Apple Dev: This notification does not contain a userInfo dictionary.

Thanks!

PengOne
  • 48,188
  • 17
  • 130
  • 149
Alexandre
  • 5,035
  • 7
  • 29
  • 36
  • Does one controller instance spawn more than one task concurrently? –  Jun 09 '11 at 21:27
  • Yes, it does. That's why I need a way to associate tasks with files. – Alexandre Jun 09 '11 at 21:44
  • That's one of the first things I tried, but NSDictionary attempts to copy the task when it is used as a key, and NSTask does not implement NSCopying. – Alexandre Jun 09 '11 at 21:50

2 Answers2

2

Use the associated object API to attach a user info dictionary to the task instance. This would be the cleanest approach, but it cannot be used prior to the introduction of the associated object API with Mac OS X 10.6.

Alternatively, you can use a dictionary that maps from task to user info. Creating a dictionary mapping from task to user info is not as straightforward as it sounds:

  • You can't just [taskInfoDict setObject:userInfo forKey:task] because NSTask does not conform to NSCopying, but NSDictionary relies on copying its keys.
  • Using the process identifier wrapped as an NSNumber as a proxy for the task object mostly works. But process IDs can be reused, and a task doesn't get a PID till after it's been launched. The root of the problem is: You don't control the process ID; the underlying OS does.

Using the address of the task object seems to be the best solution:

[taskInfoDict setObject:userInfo forKey:[NSValue valueWithPointer:task]]

Assuming a reference-counted environment, the task object's address will be stable for its lifetime, and its lifetime is entirely under control of your application. A copying garbage collector would throw a wrench in this solution, but in that case, you could use a collection class that can handle the pointer directly (NSMapTable).

Jeremy W. Sherman
  • 35,901
  • 5
  • 77
  • 111
  • That's one of the first things I tried, but NSDictionary attempts to copy the task when it is used as a key, and NSTask does not implement NSCopying. – Alexandre Jun 09 '11 at 21:45
  • 1
    Could you use the task process ID (wrapped in an `NSNumber`) as the key? – Wevah Jun 09 '11 at 21:55
  • @Alexandre: Use an `NSValue` wrapping the address of the task as the key instead of the `NSTask` object itself. And the associated object approach is unaffected by whether `NSTask` conforms to `NSCopying` or not. – Jeremy W. Sherman Jun 09 '11 at 22:07
  • I ended up going with Wevah's suggestion (see above) of using the NSTask's pid as the dictionary key and it worked beautifully. Can you see any reason not to? – Alexandre Jun 09 '11 at 22:59
  • Process IDs can be reused, and a task does not receive a process ID till it has been launched. The address of the task is fully under your application's control. (The associated object approach is the cleanest of them all, though.) – Jeremy W. Sherman Jun 09 '11 at 23:40
  • But only compatible with 10.6. :( Rather than PID, how would I use the address of the task as the dictionary key? How do I get an object that represents the task's address? – Alexandre Jun 10 '11 at 00:36
  • Figured it out! [NSValue valueWithPointer:file] does the trick as the dictionary key. Worked perfectly! – Alexandre Jun 10 '11 at 00:46
  • Can you change the accepted answer? I will move some of the content from the comments here into my actual response. – Jeremy W. Sherman Jun 10 '11 at 01:05
  • Just did. Thanks a bunch for the help Jeremy! – Alexandre Jun 10 '11 at 03:16
1

Consider using associative references to associate the file URL/path to each task instance. Every object can have multiple associated objects, and each associated object has a corresponding key that is used to reference the associated object when needed.

In your controller, create a static variable that represents the file URL/path key:

static char fileURLKey;

When creating an NSTask instance, associate the corresponding file URL to that instance:

NSURL *fileURL = …;
NSTask *task = …;
objc_setAssociatedObject(task, &fileURLKey, fileURL, OBJC_ASSOCIATION_RETAIN);

When the task has finished executing, get the task from the notification object and then get the file URL from the task:

NSTask *task = [notification object];
NSURL *fileURL = (NSURL *)objc_getAssociatedObject(task, &fileURLKey);
  • I ended up going with Wevah's suggestion (see above) of using the NSTask's pid as the dictionary key and it worked beautifully. Can you see any reason not to? – Alexandre Jun 09 '11 at 23:06
  • @Alex Well, I find associative references more simple and general. You ended up using a dictionary and manually managing it whereas associative references do that work for you automatically. –  Jun 09 '11 at 23:08
  • Alright, I will try to replace the Dict approach with your approach. Thanks! – Alexandre Jun 09 '11 at 23:11
  • Oh oh, just noticed this technique is 10.6 only. Still a lot of Leopard users out there. Wevah's technique seems more compatible. – Alexandre Jun 09 '11 at 23:33
  • @Alex Jeremy’s is the most suitable answer if you’re targeting Leopard. –  Jun 10 '11 at 02:34