I have created a scriptable stay-open ASObjC application called “Utility Handlers” that serves as a general purpose library of utility handlers that scripts can utilize. The problem is that the application freezes after a certain number of times commands are sent to the application. Before going into the problem in more detail, I will start with a description of the application.
Each of the app’s handlers takes its own distinct set of input parameters. Because it is a general purpose library, it must be able to accommodate input parameters and returned values of any arbitrary class. For simplicity of coding, rather than create a separate NSScriptCommand file for each handler (and thus a separate command that would be sent to the application), the application responds to a single command, namely xeq, with a single direct object argument in the form of an Applescript record. The record has a handlerName property, which is the name of the handler to execute, along with whatever other properties that particular handler requires as input arguments. Each handler is responsible for parsing the input record for its required parameters. By way of example, if the handler is getSumOfTwoNumbers, then the calling program might issue the following command:
tell application "Utility Handlers" to set theSum to xeq {handlerName:"getSumOfTwoNumbers", firstNumber:[...first number goes here...], secondNumber:[...second number goes here...]}
The getSumOfTwoNumbers handler would expect to find the firstNumber and secondNumber properties in the input record and would return the sum of those two numbers.
The NSScriptCommand file for the application is named ExecuteHandler.applescript. The direct object input parameter is expected to be an Applescript record, which is enabled by setting the type to type=“any” in the sdef file and which the NSScriptCommand file accesses with the construct
my directParameter() as record
The value returned by NSScriptCommand file is also an Applescript record. This is enabled by specifying type=“any” in the sdef file and is achieved by converting the Applescript record to be returned to an NSAppleEventDescriptor (the only way I could discern to return a list or record to the calling program; perhaps there are other ways??) Not knowing of a straightforward way to convert an Applescript record to an NSAppleEventDescriptor, I ended up using an ugly hack, namely getting a text version of the results record using the forced error message trick
|| of {...record...}
then setting that text as the source for an NSAppleScript, whose return result is always an NSAppleEventDescriptor. Ugly, but it works and can accommodate Applescript values of virtually any class. Here is the code for the NSScriptCommand file with two utility handlers, namely getSumOfTwoNumbers and replicateString (error checking and other niceties removed for the sake of brevity):
script ExecuteHandler
property parent : class "NSScriptCommand"
property NSAppleScript : class "NSAppleScript"
property inputRecord : missing value
on performDefaultImplementation()
set my inputRecord to my directParameter() as record
set handlerName to my inputRecord's handlerName
if handlerName = "getSumOfTwoNumbers" then
set handlerResult to my getSumOfTwoNumbers()
else if handlerName = "replicateString" then
set handlerResult to my replicateString()
else if handlerName = "someOtherHandler" then
-- etc
end if
set resultsRecord to {theResult:handlerResult}
set resultsRecordAsText to my getValueAsText(resultsRecord)
set resultsRecordAsNSAppleEventDescriptor to NSAppleScript's alloc()'s initWithSource_(resultsRecordAsText)'s executeAndReturnError_(missing value)
return resultsRecordAsNSAppleEventDescriptor
end performDefaultImplementation
on getValueAsText(theValue)
try
|| of theValue
on error m
set o to offset of "|| of " in m
set theValueAsText to m's text (o + 6) thru -2
end try
return theValueAsText
end getValueAsText
on getSumOfTwoNumbers()
-- takes two input parameters: firstNumber, secondNumber
tell my inputRecord to return (its firstNumber) + (its secondNumber)
end getSumOfTwoNumbers
on replicateString()
-- takes two input parameters: replicationFactor, theString
set replicatedString to ""
repeat inputRecord's replicationFactor times
set replicatedString to replicatedString & inputRecord's theString
end repeat
return replicatedString
end replicateString
end script
The sdef file is coded with type=“any” for both the direct object input parameter and the return value so that they may be coded as Applescript records with properties whose values may be of any arbitrary class in the calling script. Here is the sdef file:
<?xml version="1.0" encoding="UTF-8"?><suite name="Standard Suite" code="^sts" description="Common classes and commands for all applications.">
<class name="application" code="capp" description="The application's top-level scripting object.">
<cocoa class="NSApplication"/>
<property name="name" code="pnam" type="text" access="r" description="The name of the application."/>
<property name="frontmost" code="pisf" type="boolean" access="r" description="Is this the active application?">
<cocoa key="isActive"/>
</property>
<property name="version" code="vers" type="text" access="r" description="The version number of the application."/>
</class>
</suite>
<suite name="Utility Handler Suite" code="^UtH" description="Utility handlers.">
<command name="xeq" code="^UtH^xeq" description="Execute a utility handler.">
<cocoa class="ExecuteHandler" />
<direct-parameter description="A record whose handlerName property is the name of the handler to be executed and whose remaining properties are the handler's arguments.">
<type type="any"/>
</direct-parameter>
<result description="The handler return value.">
<type type="any" />
</result>
</command>
</suite>
The application works as intended. The problem is that after a certain number of handler calls (“xeq” commands), the application freezes and must be quit and re-launched in order to become responsive again. The number of times a given handler call must be issued before the application freezes seems to be inversely related to the complexity of the task that handler performs. Some very simple handlers may run a few hundred times before the application reproducibly freezes at the exact same number of times the command is sent to it, whereas some particularly complex handlers may run only once then reproducibly freeze on the second try. (Thus far, at least, even the most complex handlers execute at least once successfully.) The striking finding is the constancy of the number of handler calls before freezing ensues for a given handler and set of input parameters. That constancy makes me wonder if the underlying problem is some kind of memory overflow or object leak, but these are only guesses from a relatively inexperienced OO programmer.
Examples (note the last example, which freezes the 2nd time the handler call is made):
tell application "Utility Handlers"
xeq {handlerName:"getSumOfTwoNumbers", firstNumber:3, secondNumber:7}
-- > {theResult:10}; freezes the 222nd time the command is sent
xeq {handlerName:"replicateString", replicationFactor:2, theString:"x"}
--> {theResult:"xx"}; freezes the 207th time the command is sent
xeq {handlerName:"replicateString", replicationFactor:2, theString:"With malice toward none, with charity for all, with firmness in the right as God gives us to see the right, let us strive on to finish the work we are in, to bind up the nation's wounds, to care for him who shall have borne the battle and for his widow and his orphan, to do all which may achieve and cherish a just and lasting peace among ourselves and with all nations."}
--> {theResult:...the string replicated twice...}; freezes the 47th time the command is sent
xeq {handlerName:"replicateString", replicationFactor:25, theString:"With malice toward none, with charity for all, with firmness in the right as God gives us to see the right, let us strive on to finish the work we are in, to bind up the nation's wounds, to care for him who shall have borne the battle and for his widow and his orphan, to do all which may achieve and cherish a just and lasting peace among ourselves and with all nations."}
--> {theResult:...the string replicated 25 times...}; freezes the 2nd time the command is sent
end tell
I would be most grateful for help in understanding the cause of the application freeze. Also, as an ancillary question, I would like to ask (1) if there is a better way of returning an arbitrary Applescript value from the stay-open application other than the type=“any”/Applescript record as NSAppleEventDescriptor technique I am using, and (2) if there is a better way of converting an arbitrary Applescript value to an NSAppleEventDescriptor other than the NSAppleScript technique I am using.
Model: MacBook Pro
Browser: Safari 536.30.1
Operating System: Mac OS X (10.8)