lukaskollmer / objc

🔮 NodeJS ↔ Objective-C bridge (experimental)
MIT License
98 stars 20 forks source link

Passing an NSURL to Cocoa turns it into a dictionary? #8

Closed erikjalevik closed 6 years ago

erikjalevik commented 6 years ago

First of all, big thanks for putting this library together. I was looking hard and long for something up-to-date for calling native Mac APIs and only discovered this through a comment hidden beneath a SO question.

I'm trying to use it to send an item to the Trash on MacOS via NSWorkspace.recycleURLs, but I'm not sure I've quite got the hang of how to pass arguments into Cocoa functions. The method is expecting an NSArray of NSURL objects, but whatever I do, the call fails with the error:

NSInvalidArgumentException -[__NSDictionaryM isFileURL]: unrecognized selector sent to instance 0x103c71d10

Since isFileURL is a method on NSURL, I draw the conclusion that Cocoa somehow gets a dictionary instead of an NSURL on which it tries to call isFileURL.

I've tried various different ways of creating the array, but no luck so far. Here is my code:

    objc.import("AppKit");

    const {
      NSWorkspace,
      NSURL,
      NSArray
    } = objc;

    const nsUrl = NSURL.fileURLWithPath_isDirectory_(
      objc.ns(filePath),
      false
    );

    // This logs what looks like an actual NSURL object
    log.log("nsUrl", nsUrl);

    const nsArray = [nsUrl];
    //const nsArray = objc.ns([nsUrl]);
    //const nsArray = NSArray.arrayWithArray_([nsUrl]);

    // This logs an empty array if I use either of the latter two lines above
    log.log("nsArray", nsArray);

    const workspace = NSWorkspace.sharedWorkspace();

    // Bam!
    workspace.recycleURLs_completionHandler_(nsArray, null);

What am I missing?

lukaskollmer commented 6 years ago

where do you get the filePath from?

erikjalevik commented 6 years ago

Oh that's just a local string to a full, absolute path.

For this test, I used:

/Users/ejal/dev/filur/sandbox/test target/.gitignore

lukaskollmer commented 6 years ago
  1. i found the bug and fixed it (the new version is already pushed to npm, not sure how long it takes to propagate but you can fix it for the time being by using this workaround):

    // instead of objc.ns([nsUrl]);
    const nsArray = NSMutableArray.array();
    nsArray.addObject_(nsUrl);
  2. you probably should replace the -[NSWorkspace recycleURLs:completionHandler:] call with -[NSFileManager trashItemAtURL:resultingItemURL:error:]. The first one is asynchronous and requires a properly set up run loop (which you'd need to do manually and which probably wouldn't be trivial). The NSFileManager one is synchronous, which means that you can simply call it w/out having to implement anything else to make it work:

    const trashUrl = objc.allocRef();
    const error    = objc.allocRef();
    const success  = NSFileManager.defaultManager().trashItemAtURL_resultingItemURL_error_(nsUrl, trashUrl, error);
erikjalevik commented 6 years ago

Amazing. Thanks for the quick turnaround.

I verified that both the NSMutableArray approach and the updated 0.16 version work.

Thanks for the hint about NSFileManager too. Seems like it's the more modern approach.

👍