‘AppleScriptObjC Explored’ Version 5: Errata

EL CAPITAN causes problems with several of the sample projects. In particular, you can no longer rely on displayIfNeeded() to update a window. This means, for example, that the project in Chapter 3 never shows the progress bar incrementing. The solution is to use the technique outlined in Chapter 13, either implementing the doEventFetch() method, or using fordEvent() from Myriad Helpers. Note also that use of current application's NSUInteger in doEventFetch() does not actually work as described in Chapter 13. The only way to produce an integer of the correct value for the enum NSAnyEvent from AppleScript is to use a workaround such as current application's NSDecimalNumber's alloc()'s initWithString:"18446744073709551615". In practice, though, using current application's NSUInteger will usually work fine in this context.

THERE have been changes in the way tables are constructed and treated. Before attempting to build any of the table-based projects, you should read this page.

IN Xcode 6.1, the Cocoa framework is no longer added to new projects automatically. You need to do this manually after you create a project. You do this by clicking on the project at the top of the Project Navigator, clicking on the General tab in the editing area, clicking on the disclosure triangle next to "Linked Frameworks and Libraries", clicking on the + button, clicking on Cocoa.framework, then clicking the Add button.

ON page 22, a period is missing from the third-last paragraph, after the word "elements".

ON page 25, and in the code for the Chapter 3 project, the lines that set the value of the isIdle property have been omitted. The first line after the try statment in the doProcess: handler should be:

set my isIdle to false

At the end of the same handler, the following should be inserted both before the on error errmess line, and after it:

set my isIdle to true

ON page 195, the book says:"You can’t return out values when passed pointers to pointers in methods you are overriding." This is incorrect. The technique for doing so is explained on pages 140-141.

‘AppleScriptObjC Explored’ Version 5: Addenda

The compiler shipping in Xcode is getting pickier these days, so the way you call ASObjC from an Objective-C class might need to change. This process is what works in Xcode 6.1.

The first step is to make sure AppleScriptObjC.framework is added to the project, and loaded in main like this:

#import <Cocoa/Cocoa.h>
#import <AppleScriptObjC/AppleScriptObjC.h>

int main(int argc, const char * argv[]) {
 [[NSBundle mainBundle] loadAppleScriptObjectiveCScripts];
 return NSApplicationMain(argc, argv);

Your ASObjC code needs to reside in an AppleScript class file, and unfortunately Xcode has no template for such a file. So you need to choose File -> New -> Other -> Empty, and name it <YourASClassName>.applescript. Let's assume you call it ASClass.applescript.

The class file should contain a single script object with its parent set to NSObject, and handlers should belong to this script object. Here is a sample:

script ASClass
property parent : class "NSObject"

 on doSomethingTo:someString andTo:anotherString
  -- coerce parameters from Cocoa to AppleScript
  set someString to someString as text
  set anotherString to anotherString as text
  set newString to someString & space & anotherString
  display dialog newString buttons {"OK"}
  return newString
 end doSomethingTo:andTo:

end script

The handlers must follow normal Cocoa rules for method names. (But they can call other handlers within the class that do not.)

In the class that will be calling the AppleScriptObjC, you need to declare the class and its methods after any import statements, like this:

#import "AppDelegate.h"

@class ASClass; // declare class

@interface ASClass:NSObject
 // declare methods here
 -(NSString *)doSomethingTo:(NSString*)string andTo:(NSString *)another;

You can declare and call your handlers as class or instance methods (or both). In practice, I've had some issues calling them as class methods in apps that call the same handler thousands of times, so I prefer instance methods. And you need to use instance methods if any AppleScript properties are used.

The results and parameters must not be primitives -- you get crashes if you try to pass an int or a BOOL. If you want stuff usable by AppleScript, you need to stick to NSNumbers and NSStrings, and NSArrays and NSDictionaries consisting of the same. Parameters still need coercing in the receiving handler.

To call a class method, you use something like this:

NSString *result = [[NSClassFromString(@"ASClass") doSomethingTo:@"hello" andTo:@"world"];

To call an instance method, you have two choices. You can instantiate the instance via Xcode by dragging in a blue cube, changing its class to ASClass (or whatever), and making an outlet to it in your Objective-C file like this:

@property (weak) IBOutlet ASClass *asObject; // instance of ASClass

And then calling methods like:

NSString *result = [self.asObject doSomethingTo:@"hello" andTo:@"world"];

Or you can instantiate in code, like this:

ASClass *asObject = [[NSClassFromString(@"ASClass") alloc] init];

NSString *reply = [asObject doSomethingTo:@"hello" andTo:@"world"];

For debugging, you need to preface any log commands in your AS with tell current application to. You can also use current application's NSLog().

You can declare methods that return void, but because that opens the potential for an app to just hang if something goes wrong in the AppleScript, it's probably safer to always return something. Wrapping the AS code in a try and returning any error is probably a good idea for anything more than a trivial call.

‘AppleScriptObjC Explored’ Version 4.x: Addenda and Errata

The current version is 4.0.1 (see inside the cover for the version number).

* IN chapter 8, Drag and Drop, a way of emulating just an open handler is described, using a property, fileList, to store the list of files to open. However, the code provided does not actually set the value of the property; the variable fileList in the application_openFiles_ handler is a local variable.

The solution is to insert the following statement in the application_openFiles_ handler:

set my fileList to fileList

The differences between 4.0.0 and 4.0.1 are as follows:

* CHANGE to the Mastering Build Settings section on pages 232-233. With the release of Xcode 4.6, it is no longer necessary to change compiler. The modified text reads as follows:

If you are running Xcode 4.4 or 4.5, you may want to change the value under Build Options for Compiler for C/C++/Objective-C. Apple includes two compilers — its own LLVM, and LLVM GCC 4.2 — and also offers a Default setting, which chooses LLVM. Normally this should not matter, because the compiler is irrelevant for AppleScriptObjC code. However, LLVM 4.1, which ships with Xcode 4.4 and 4.5, has a problem with applications that use garbage collection when run in 64-bit mode under OS X 10.6. The result is that if you wish your application to be able to run under OS X 10.6, and you are using Xcode 4.4 or 4.5, you should either set LLVM GCC 4.2 as the compiler, or set your application to run in 32-bit mode under OS X 10.6.

This problem has been fixed in Xcode 4.6, where you should use the default setting (Apple LLVM Compiler 4.2). (The GCC 4.2 compiler option will disappear in the next major version of Xcode.)

* THE last paragraph on page 179 is:

log ws's getFileSystemInfoForPath_isRemovable_isWritable_isUnmountable_description_type_("/", specifier, specifier, specifier, specifier, specifier)

As the text that preceeds it implies, that should be:

log ws's getFileSystemInfoForPath_isRemovable_isWritable_isUnmountable_description_type_("/", reference, reference, reference, reference, reference)

* IN the project in chapter 7, A Class of Our Own, the section on reimporting tabbed data begins on page 57. The inital handler on page 57, importDataAsTabbedText_(sender), and the specFromTabbedText_(tabbedText) handler on page 58, both import values for the number of columns and depth of ads as text rather than numbers. The result is that if you try to sort a column by clicking on a column heading after an import, you may get a crash — the values cannot be sorted unless they are all numbers or all text, not a mixture of both.

On page 57, this code:


Should be changed to:

setAdColumns_(adCols as integer)
setAdDepth_(adDep as integer)

And on page 58, this code:

setAdColumns_(item 2 of theValues)
setAdDepth_(item 3 of theValues)

should be changed to:

setAdColumns_((item 2 of theValues) as integer)
setAdDepth_((item 3 of theValues) as integer)

The code in the projects also needs to be changed similarly.

* IN several chapters there are references to Myriad Helpers, and a copy of a project demonstrating them is included with the book. The included version will not compile with older versions of Xcode. If you have this problem, you can download a compatible version here