How to test if a variable contains an ObjC object?

Hi,
how can one check whether an AppleScript variable contains a reference to an ObjC object?
I’ve tried with:


use framework "Foundation"
set x to current application's NSFileManager's defaultManager()
class of x is «class ocid» -- false

And, related to that, is there a way to force a handler to accept only a reference to an ObjC object? This does not work:


use framework "Foundation"
on foo(x as «class ocid»)
end
set x to current application's NSFileManager's defaultManager()
foo(x)

If fails with: “Can’t make «class ocid» id «data optr00000000B0B5000000600000» into type «class ocid».”

use framework "Foundation"
set x to current application's NSFileManager's defaultManager()
set y to class of x
if class of y = y then -- it's a Cocoa thing

You can test the class of the argument(s) when they arrive.

I wouldn’t have thought of it! But “class of x = class of class of x” evaluates to true whenever x’s value is a data type. For example:


set x to text
class of x = class of class of x -- true

So, to be really sure that x is a reference to an ObjC object, one should write a very long condition:


class of x = class of class of x and x is not text and x is not integer and x is not grams -- and go on with all AppleScript types...

A bit annoying, but doable. Anyway, what you suggest will work in practice in most cases (passing types as arguments to handlers is not so common) and it is good enough for me.

Thanks!

That doesn’t work because the ASOC designers bodged up the standard AppleScript ‘class’ property to return an object specifier (or “reference” in AS jargon) instead of a type class value (i.e. a symbol). It’s one of ASOC’s various small but annoying warts. (Unfortunately, the AS engineers are very prone to excessive cleverness, inventing problems where none previously existed.)

The following is not 100% reliable - e.g. passing {class:a reference to document 1} will give a false positive - but is probably good enough for your purposes:


(every reference of {class of x} is not {})

AppleScript doesn’t include any built-in coercion handlers for object specifiers, so an object specifier (“reference”) can only be coerced to itself:


x as reference
--> «class ocid» id «data optr00000000100A7011B67F0000»

Coercing an object specifier object to any other type requires sending an [implicit] get VALUE as TYPE event to whatever knows how to 1. resolve that object specifier and 2. convert that result to a the required type. For example, ASOC knows how to convert «class ocid» specifiers to AppleScript strings (assuming the ‘ocid’ specifier points to an NSString instance), so this will work:


on foo(x as string) -- ASOC performs this «class ocid» specifier-to-string conversion
    ...
end foo

set objCDesc to |description|() of x
-- objCDesc is a «class ocid» specifier pointing to an NSString
foo(objCDesc)

I suppose you could use:

on foo(x as reference)

as a first-line check, followed by a more thorough

if (every reference of {class of x} is not {}) then error "x is not an ObjC object." number -1700 from x

test to confirm the object specifier identifies an ObjC object in particular. Though personally I’d probably just omit the ‘x as reference’ coercion [1] as the conditional test will catch this anyway, plus it provides a much better error message.

HTH

[1] While the ability to declare a handler’s parameter types is a great idea in principle [2], the actual implementation in AppleScript 2.4 is so weak as to be virtually useless. You can’t even express simple structures like ‘list of string’, never mind describe meaningful reference requirements (‘a reference to a single TextEdit document’, ‘a reference to zero or more Finder folders’, etc).

[2] My own kiwi language does this extensively, e.g. a ‘list of 4 numbers from 0 to 100’ can be described as ‘list (number(0,100), 4)’, and even assigned its own custom name, e.g. ‘CMYK color’, and description. (Needless to say, I pinched the original idea from AppleScript - specifically application dictionaries - although it’s been greatly expanded and enhanced since then.)

Actually, you could use the same filtering technique to catch the record cheat as well:


((every record of {x} is {}) and (every reference of {class of x} is not {}))

I still wouldn’t like to claim 100% reliability (e.g. you could still write a scriptable app that fools it, and never underestimate the AS team’s ability to screw things up retroactively), but again I’m reasonably confident that it’ll be “Good Enough”. Still wish such “tricks” weren’t necessary in the first place but, ehh, AppleScript.

Why not keep it simple:

try
	|class|() of x
	-- it's Cocoa 
on error
	-- it's AS
end try

Hello Shane.

That is certainly better than the Javascript way of finding if it is a date, or array or stuff, which would have been parallelled by the snippet below in AppleScript as a way to identify an Obj-C obj. :confused:

set objCDesc to |description|() of x

-- objCDesc is a «class ocid» specifier pointing to an NSString

on isObjCObj(objCDesc)
	if "«class ocid»" is in objCDesc then
		return true
	else
		return false
	end if
end isObjCObj

Wow, those are some tricks! Thanks hhas for the deep explanation. I like Shane’s idea, but that could easily be fooled by a script defining a |class|() handler. I thought a bit more and… how about this:


on isObjCReference(x)
	try
		(class of x) as reference
		(contents of class of x is class of x)
	on error
		false
	end try
end isObjCReference

First we make sure that “class of x” is a reference. Then, we test whether dereferencing “class of x” still gives us the same reference. Correct me if I am wrong, but such test should never be true for AS’s references, with the only exception of a reference to itself (that is, “set x to ref x”), which is not something you’ll ever want in your scripts. Nonetheless, that condition is evaluated to true for ObjC references. What do you think? Can you find a counterexample where this will not work?

I think that anyone who overrides -class and + class deserves any problems they get, and then some.