Unexpected result of identity testing of a Cocoa class constant

I recently have been working on a script in which the need arose to distinguish whether an object saved to a variable is a Cocoa class constant itself or an instance object of that class. One way to make this determination is to test if the object equals its own class, which will return true for a class object and false for an instance object of that class. For example:

use framework "Foundation"
use scripting additions

set classObject to current application's NSString
set instanceObject to current application's NSString's stringWithString:"foobar"

classObject = classObject's |class|() --> true
instanceObject = instanceObject's |class|() --> false

The following are different ways of performing the class identity test, varying from one another in (1) how the class constant is specified (“class name” vs “class expression” method); (2) what the class constant is assigned to (local variable vs property); (3) how the class constant is assigned to a property (at declaration vs in a subsequent set statement); and (4) how the equality testing is done (the equal sign operator = vs ASObjC’s isEqual: method). The unexpected result is shown last:

use framework "Foundation"
use scripting additions

set classConstant_1 to current application's NSString -- "class name" assignment method

set classConstant_2 to class "NSString" -- "class expression" assignment method

property classConstant_3 : missing value
set my classConstant_3 to current application's NSString

property classConstant_4 : missing value
set my classConstant_4 to class "NSString"

property classConstant_5 : class "NSString"

(classConstant_1's isEqual:(classConstant_1's |class|())) --> true
(classConstant_2's isEqual:(classConstant_2's |class|())) --> true
(classConstant_3's isEqual:(classConstant_3's |class|())) --> true
(classConstant_4's isEqual:(classConstant_4's |class|())) --> true
(classConstant_5's isEqual:(classConstant_5's |class|())) --> true

(classConstant_1 = (classConstant_1's |class|())) --> true
(classConstant_2 = (classConstant_2's |class|())) --> true
(classConstant_3 = (classConstant_3's |class|())) --> true
(classConstant_4 = (classConstant_4's |class|())) --> true
(classConstant_5 = (classConstant_5's |class|())) --> false !!!

The unexpected result occurs only when (A) the class constant is assigned using the “class expression”, not the “class name”, assignment method; (B) the class constant is assigned to a property, not a local variable; (C) the assignment takes place in the property’s declaration statement, not in a subsequent set statement; and (D) the equal sign operator = is used for equality testing, not ASObjC’s isEqual: method. Any help in understanding this puzzling behavior is much appreciated.

As an ancillary question, what is the difference under the hood between the equal sign operator = vs ASObjC’s isEqual: method for equality testing. My understanding is that Objective C’s double equal sign operator == tests for true identity, i.e., two pointers pointing to the same location in memory, whereas isEqual: seems to be implemented differently depending on the class in which it is used and at least in some cases tests for measures of equality but not necessarily true identity. But where does ASObjC’s equal sign operator = fit in, and how does it differ from ASObjC’s implementation of the isEqual: method?

Thank you.

The short answer is that you shouldn’t use what you’re calling the “class expression” method. Apart from anything else, it only works in a particular scope.

The failure stems from the fact that there’s no reason for the classConstant_5 property to resolve it’s reference, whereas those belonging to AppleScript (ie, preceded by “current application’s”) are resolved as a matter of course.

isEqual: checks the objects are equal, and how that’s defined depends on the class involved. AppleScript’s equals sign uses comparisons that take into account the considering ignoring values.

That’s very helpful information, Shane. Thank you.

That explains nicely why (classConstant_5 = (classConstant_5’s |class|())) fails. But I’m still a bit puzzled why (classConstant_5’s isEqual:(classConstant_5’s |class|())) works? Could it be that the isEqual: method forces the property to resolve its reference before it performs the equality test?

Thanks again for the clarifications and for the important advice about staying with the current application’s … (“class name”, if you will) class assignment method.

Browser: Firefox 88.0
Operating System: macOS 10.14