Testing for NSNotFound

I would like to present a new way of testing for the value of the global variable NSNotFound that is returned by various Cocoa methods and functions when a search for an item fails to find the item. For example, NSNotFound is returned by NSArray’s indexOfObject: method if it fails to find the test object in the test array. NSNotFound’s value is defined by Cocoa to be NSIntegerMax, which is a value too large to be represented by an integer in ASObjC. The ASObjC bridge handles this problem by returning a real number approximation to NSIntegerMax (which differs in a 32-bit vs 64-bit environment.) Unfortunately, the ASObjC bridge returns a different value when the NSNotFound variable is directly queried, namely the integer value -1. This difference precludes direct comparison testing to NSNotFound, as illustrated in the following example:

use framework "Foundation"
use scripting additions

set arrayObj to (current application's NSArray's arrayWithArray:{1, 2, 3})

arrayObj's indexOfObject:4
--> returns the real value 9.22337203685478E+18 in a 64-bit environment or 2.147483647E+9 in a 32-bit environment (an approximation of NSIntegerMax)

current application's NSNotFound
--> returns the integer value -1, which clearly differs from the object-not-found value returned by the indexOfObject: method, thus precluding a direct comparison test:

(arrayObj's indexOfObject:4) = (current application's NSNotFound)
--> should return the boolean true value (as is returned in analogous Cocoa Objective-C code), but instead returns the incorrect boolean false value in ASObjC

Several approaches have been suggested to get around this inability to perform direct comparison testing to NSNotFound in ASObjC, including the following (all of which utilize the example described above):

(1) Test for a return value ≥ the 32-bit NSNotFound value 2147483647 (this test will work in both 32-bit and 64-bit environments, since the 64-bit value is much larger):

set ix to (arrayObj's indexOfObject:4)
set objectNotFound to (ix ≥ 2.147483647E+9) --> true

(2) Attempt to coerce the return value to an integer within a try block, which will throw an error if the return value is NSNotFound, which is too large for the coercion:

try
	set ix to (arrayObj's indexOfObject:4) as integer
	set objectNotFound to false
on error
	set objectNotFound to true --> this value will be set, since the "as integer" coercion will throw an error
end try

(3) Attempt to retrieve the object at the returned index within a try block:

set ix to (arrayObj's indexOfObject:4)
try
	(arrayObj's objectAtIndex:ix)
	set objectNotFound to false
on error
	set objectNotFound to true --> this value will be set, since the objectAtIndex: method will throw an error
end

(4) In the case of a string test that returns a range value, test for a range length = 0 (more straightforward than testing for a range location = NSNotFound):

set stringObj to (current application's NSString's stringWithString:"Red Orange Yellow")
set matchingRange to (stringObj's rangeOfString:"Green")
set objectNotFound to (matchingRange's |length|() = 0) --> true

To this plethora of solutions, I would like to add one more that has the minor disadvantage of requiring an initial ASObjC command but that has the following potential advantages:

(1) It will hopefully be immune to any future changes in NSNotFound’s internal implementation or the computer’s bit architecture, and
(2) It mimicks the direct comparison testing to NSNotFound that is done in Cocoa Objective-C code.

The present method amounts simply to setting a variable or property named NSNotFound (different from Cocoa’s NSNotFound global variable!) to a forced ASObjC object-not-found value. Subsequent comparison tests can then be performed to that variable or property value:

use framework "Foundation"
use scripting additions

property NSNotFound : missing value
set my NSNotFound to (current application's NSArray's array()'s indexOfObject:1) -- indexOfObject: returns the current environment's NSNotFound value, since the object 1 is not present in the empty array

set arrayObj to (current application's NSArray's arrayWithArray:{1, 2, 3})
set ix to (arrayObj's indexOfObject:2)
set objectNotFound to (ix = my NSNotFound) -- false, since the object 2 is present in the test array
set ix to (arrayObj's indexOfObject:4)
set objectNotFound to (ix = my NSNotFound) -- true, since the object 4 is not present in the test array

-- And for range testing:

set stringObj to (current application's NSString's stringWithString:"Red Orange Yellow")
set matchingRange to (stringObj's rangeOfString:"Orange")
set objectNotFound to (matchingRange's location() = my NSNotFound) --> false, since the substring "Orange" is present in the test string
set matchingRange to (stringObj's rangeOfString:"Green")
set objectNotFound to (matchingRange's location() = my NSNotFound) --> true, since the substring "Green" is not present in the test string

Edit note: The final example was reworked and expanded since the original submission.

2 Likes