Reputation: 14845
Here's my code:
NSArray *allIds = [self.purchasableObjects valueForKey:@"productIdentifier"];
NSInteger index = [allIds indexOfObject:productId];
if (index == NSNotFound)
return;
Which one to compare to NSNotFound
... NSInteger
or NSUInteger
and why?
Upvotes: 2
Views: 1944
Reputation: 56
The reason why he's asked this question is because the AppKit is inconsistent with Foundation indexes, especially for NSMenu
and NSTableView
:
id obj = ...;
NSMenu * menu = ...;
/* This returns an NSInteger, returning -1 (which when casted, is NSNotFound) */
NSInteger index = [ menu indexOfItemWithRepresentedObject:obj ];
/* This returns an NSUInteger, since arrays cannot have negative indices. */
NSUInteger idx = [ [ menu itemArray ] indexOfObjectIdenticalTo:obj ];
Because of this, one must cast back and forth, but this creates new problems:
Because indexes cannot be negative, the number of rows (or submenus, etc) is lower BY HALF of what normal arrays can be.
If you attempt to read or insert an object to a negative index (-2) in a UI element, you do not get range exceptions. If you cast to unsigned, the index is undefined. So this code, which is wrong, compiles without error:
id obj = ...;
NSMenu * menu = ...;
/* Or the result of some integer arithmetic. */
NSInteger idx = -2;
/* Compiles, doesn't throw exception. Index COULD be NSIntegerMax - 1, but the ending index is undefined. */
[ menu insertItem:obj atIndex:idx ];
/* Compiles, but throws NSRangeException. */
[ [ menu mutableArrayValueForKey:@"items" ] insertObject:obj atIndex:( NSUInteger )idx ];
In the first case, when inserting an item beyond menu.numberOfItems
(which is an NSInteger
), in general (but not guaranteed), the method is equivalent to [ menu addItem ]
. So, index is transformed thus:
[ menu insertItem:obj atIndex:MIN( idx, menu.numberOfItems ) ];
But in older versions of AppKit, index is rounded up!:
[ menu insertItem:obj atIndex:MAX( MIN( idx, menu.numberOfItems ), 0 ) ];
So a programmer must take great care when inserting and/or retrieving indexes from UI elements, or use an NSArrayController
instead.
For all cases, though it is safe to check both [ menu indexOfItem:obj ]
and [ array indexOfObjectIdenticalTo:obj ]
are equal NSNotFound
, so long as you use an explicit cast. While NSNotFound
is has a declared type of NSUInteger
, it is defined as NSUIntegerMax
, which when cast equals -1. NEVER check if a value is less than NSNotFound
as you will create yet another infinite loop. But feel free to:
NSInteger index = [ menu indexOfItem:nonexistantObject ];
if( ( NSUInteger )index == NSNotFound ) {
...blah...
}
or
NSUInteger index = [ array indexOfItem:nonexistantObject ];
if( index == NSNotFound ) {
...blah...
}
However, if the return type is NSInteger
, you should not assume that if the returned index isn't NSNotFound
that it is a valid index (especially if you're using a 3rd-party framework or library). Instead, you should check to see if the returned index is in a valid range:
NSInteger index = [ menu indexOfItem:nonexistantObject ];
if( ( NSUInteger )index == NSNotFound ) {
/* Can't find it, so insert or do something else. */
} else if( !( index >= 0 && index <= menu.numberOfItems ) ) {
/* Throw an invalid range exception. There's a major bug! */
} else {
/* woo hoo! we found it! */
}
Of course that check is computationally expensive, so it should only be used in code that runs infrequently, is not in a loop, or in debug code. If you want to ensure that you always get a valid index as quickly as possible, you should use block enumeration, which is faster than just about any other method:
id obj = ..;
/* Get an array from the UI, or just use a normal array. */
NSArray * array = [ menu itemArray ];
/* Find an index quickly */
NSUInteger index = [ array indexOfObjectWithOptions:NSEnumerationConcurrent passingTest:^BOOL( NSUInteger idx, id testObj, BOOL * stop ) {
if( obj == testObj ) {
*stop = TRUE;
return TRUE;
}
} ];
The returned index is guaranteed to be valid, either NSNotFound
or in NSMakeRange( 0, array.count - 1 )
. Notice I did not use -isEqual:
as I was checking for a specific instance not an object that could be interpreted as the same as another.
If you need to do something with the indices in the loop, you can use the same technique (notice the indices are passed to the block, and are always valid):
[ array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^( NSUInteger idx, id obj, BOOL * stop ) {
/* do something to this object, like adding it to another array, etc */
} ];
Using the block methods on arrays always get you valid indices, that can be cast to NSIntegers
for use in UI elements. There are also range-specific block methods that can restrict the values even further. I'd recommend reading through the NSArray and NSMutableArray documentation for pointers. It's also quite easy to misuse blocks, by adding code to the [enumerate...withBlock:][3]
methods, especially if you are adding the objects to another collection class:
NSArray * source = ...;
NSMutableArray * dest = ...;
[ source enumerateObjectsUsingBlock:^( NSUInteger idx, id obj, BOOL * stop ) {
if( /* some test */ ) {
/* This is wasteful, and possibly incorrect if you're using NSEnumerationConcurrent */
[ dest addObject:obj ];
}
} ];
It's better to do this instead:
/* block removed for clarity */
NSArray * source = ...;
NSMutableArray * dest = ...;
NSIndexSet * indices = [ source indexesOfObjectsPassingTest:testBlock ];
[ dest addObjects:[ source objectsAtIndexes:indices ] ];
Sorry for the long-winded response, but this is a problem with Cocoa indexing (and especially NSNotFound
) that my developers have to solve and re-solve, so I thought it would be helpful to share.
Upvotes: 2
Reputation: 215
You should use NSUInteger
The reason behind this is "The array index is not going to minus (i.e object at index -5)"
typedef int NSInteger;
typedef unsigned int NSUInteger;
NSInteger must be used when there are probability to get values in plus or minus.
Hope it helps. Thanks
Upvotes: 1