NSArrayWithObject:nil asserts, but NSArrayWithObjects:nil doesn't. Bug or feature? - ios

Whilst debugging a problem with UICollectionView -reloadItemsAtIndexPaths, I traced an assert to a condition where I (normally) need to pass this method a single element array (I have only one cell to reload). Hence, somewhat obviously, I used the equivalent of the following to generate the necessary array:
NSIndexPath *foo = ...
NSArray *bar = [NSArray arrayWithObject:foo];
[mycollectionview reloadItemsAtIndexPaths:bar];
However, in the special case when mycollectionview is still empty then the indexpath foo is nil (ie no cell to reload), it appears arrayWithObject: asserts with the error:
2014-10-11 12:45:08.066 Xulu[26594:90b] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]'
HOWEVER, if I instead use
NSArray *bar = [NSArray arrayWithObjects:foo,nil];
Everything is fine.
So my question is, is the fact that [NSArray arrayWithObject:nil] asserts a bug, or an undocumented feature? The Apple docs saying nothing about the parameter having to be non-nil. I see lots of posts about using nil with arrayWithObjects, but nothing about arrayWithObject and nil. And this seems like a not-that-unusual situation...

By definition, arrayWithObject's parameter must be a pointer which is legal as an element of an NSArray. An NSArray cannot contain nil, so it's a design error to pass nil to this method — hence the exception.
On the other hand, arrayWithObjects takes a nil-terminated list of arguments. Passing nil alone constitutes passing an empty list, creating an empty array. Nothing wrong with that.
However, in modern ObjC it's best to use neither method. Instead, use array literals:
NSArray *bar = #[foo];
This makes your intent clearer to future readers of your code, catches problematic use of nil with a clearer error message, and avoids the kind of silent failures that can arise from use of arrayWithObjects and unintended nils.

You can't put nil into an array - You have to store an NSNull object
NSArray *bar = [NSArray arrayWithObjects:foo,nil];
creates a one element array containing foo - nil is the sentinel indicating the end of the list of objects to put into the array - it isn't put into the array
Now, if foo itself is nil then this is the same as saying
NSArray *bar = [NSArray arrayWithObjects:nil,nil];
The first nil terminates the list and the second nil is ignored - so you end up with an empty array
NSArray *bar = [NSArray arrayWithObject:foo];
Creates a one element array and puts the specified object into it - but your object is nil and you can't store nil so you get the assertion failure.

To followup on my own question, after further digging - and in lieu of the comments above - this is probably working-as-designed; ie an arguably ill-documented 'feature', not a bug... In particular, Apple's documentation for insertObjectAtIndex: does clearly state:
(void)insertObject:(id)anObject atIndex:(NSUInteger)index
Parameters
anObject
The object to add to the array's content. This value must not be nil.
Important: Raises an NSInvalidArgumentException if anObject is nil.
Probably the arrayWithObject: documentation could benefit with something similar. Thnx for those that took the time to respond. This can be closed.

Related

Error/Exception handling in a method that returns bool

In my custom framework, I have a method like the one shown below which fetches value from dictionary and converts it into BOOL and returns the boolean value.
- (BOOL)getBoolValueForKey:(NSString *)key;
What if the caller of this method passes a key that does not exist. Should I throw a custom NSException saying key does not exist(but throwing exception is not recommended in objective c) or add NSError parameter to this method as shown below?
- (BOOL)getBoolValueForKey:(NSString *)key error:(NSError **)error;
If I use NSError, I will have to return 'NO' which will be misleading since 'NO' can be a valid value of any valid key.
The API for this is long-established by NSUserDefaults, and should be your starting point for designing your API:
- (BOOL)boolForKey:(NSString *)defaultName;
If a boolean value is associated with defaultName in the user defaults, that value is returned. Otherwise, NO is returned.
You should avoid creating a different API for fetching bools from a keystore unless you have a strong reason. In most ObjC interfaces, fetching a non-exixtant key returns nil and nil is interpreted as NO in a boolean context.
Traditionally, if one wants to distinguish between NO and nil, then call objectForKey to retrieve the NSNumber and check for nil. Again, this is behavior for many Cocoa key stores and shouldn't be changed lightly.
However, it is possible that there is a strong reason to violate this expected pattern (in which case you should definitely note it carefully in the docs, because it is surprising). In that case, there are several well established patterns.
First, you can consider fetching an unknown key to be a programming error and you should throw an exception with the expectation that the program will soon crash because of this. It is very unusual (and unexpected) to create new kinds of exceptions for this. You should raise NSInvalidArgumentException which exists exactly for this problem.
Second, you can distinguish between nil and NO by correctly using a get method. Your method begins with get, but it shouldn't. get means "returns by reference" in Cocoa, and you can use it that way. Something like this:
- (BOOL)getBool:(BOOL *)value forKey:(NSString *)key {
id result = self.values[key];
if (result) {
if (value) {
// NOTE: This throws an exception if result exists, but does not respond to
// boolValue. That's intentional, but you could also check for that and return
// NO in that case instead.
*value = [result boolValue];
}
return YES;
}
return NO;
}
This takes a pointer to a bool and fills it in if the value is available, and returns YES. If the value is not available, then it returns NO.
There is no reason to involve NSError. That adds complexity without providing any value here. Even if you are considering Swift bridging, I wouldn't use NSError here to get throws. Instead, you should write a simple Swift wrapper around this method that returns Bool?. That's a much more powerful approach and simpler to use on the Swift side.
If you wish to communicate passing a non-existent key as a programmer error, i.e. something that should actually never occur during runtime because for instance something upstream should have taken care of that possibility, then an assertion failure or NSException is the way to do it. Quoting Apple's documentation from the Exception Programming Guide:
You should reserve the use of exceptions for programming or unexpected runtime errors such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, and losing the connection to the window server. You usually take care of these sorts of errors with exceptions when an application is being created rather than at runtime.
If you wish to communicate a runtime error from which the program can recover / can continue executing, then adding an error pointer is the way to do it.
In principle it is fine to use BOOL as the return type there even if there is a non-critical error case. There are however corner cases with this in case you intend to interface with this code from Swift:
If you are accessing this API via Swift, NO always implies that an error is thrown, even if in your Objective-C method implementation you do did not populate the error pointer, i.e. you would need a do / catch and handle specifically of a nil error.
The opposite actually is also valid, i.e. it is possible to throw an error in the success case (NSXMLDocument for instance does this to communicate non-critical validation errors). There is to my knowledge no way to communicate this non-critical error information to Swift.
If you do intend to use this API from Swift, I would perhaps box the BOOL to a nullable NSNumber (at which case the error case would be nil, and the successful NO case would be an NSNumber with NO wrapped in it).
I should note, for the specific case of a potentially failable setter, there are strong conventions that you should follow, as noted in one of the other answers.
You pinpoint the major weakness in Apples error handling approach.
We are dealing with those situations by guaranteeing that the NSError is nil in success cases, so you actually check the error:
if (error) {
// ... problem
// handle error and/ or return
}
As this contradicts Apples error handle, where an Error is never guaranteed to be nil, but is guaranteed to be not nil in failure cases, affected methods have to be well documented to the clients know about this special behaviour.
This is not a nice solution, but the best I know.
(This is one of the nasty things we do not have to deal with any more in swift)
If You want all these
Distinguish between failure and success cases
Work with the bool value only if it is a success
In case of failure, caller mistakenly does not think return value is the value of the key
I suggest to make a block based implementation. You'll have a successBlock and errorBlock to clearly separate.
Caller will call the method like this
[self getBoolValueForKey:#"key" withSuccessBlock:^(BOOL value) {
[self workWithKeyValue:value];
} andFailureBlock:^(NSError *error) {
NSLog(#"error: %#", error.localizedFailureReason);
}];
and the implementation:
- (void)getBoolValueForKey:(NSString *)key withSuccessBlock:(void (^)(BOOL value))success andFailureBlock:(void (^)(NSError *error))failure {
BOOL errorOccurred = ...
if (errorOccurred) {
// userInfo will change
// if there are multiple failure conditions to distinguish between
NSDictionary *userInfo = #{
NSLocalizedDescriptionKey: NSLocalizedString(#"Operation was unsuccessful.", nil),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(#"The operation timed out.", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(#"Have you tried turning it off and on again?", nil)
};
NSError *error = [NSError errorWithDomain:#"domain" code:999 userInfo:userInfo];
failure(error);
return;
}
BOOL boolValue = ...
success(boolValue);
}
We use this
- (id) safeObjectForKey:(NSString*)key {
id retVal = nil;
if ([self objectForKey:key] != nil) {
retVal = [self objectForKey:key];
} else {
ALog(#"*** Missing key exception prevented by safeObjectForKey");
}
return retVal;
}
Header file NSDictionary+OurExtensions.h
#import <Foundation/Foundation.h>
#interface NSDictionary (OurExtensions)
- (id) safeObjectForKey:(NSString*)key;
#end
In this case, I would prefer returning NSInteger with returning 0, 1 and NSNotFound if caller passes key that doesn't exist.
From the nature of this method, It should be caller judgement to handle NSNorFound. As I can see, returning error is not very encouraging to user from the method's name.

How to compare two numbers in iOS Objective-C

What is the safe way to compare to NSNumbers? If I do isEqualToNumber: and one of the numbers is nil, I still get an
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber compare:]: nil argument'
I thought nil defaulted to zero in iOS? Please help me understand some fundamentals if possible. Thanks.
my code is
numberB=[numberB isKindOfClass:[NSNull class]]?0: numberB;
if([numberA isEqualToNumber: numberB]){...}
It seems that the NSNull check is ignored because my logging shows that numberB is still (null) instead of 0. I am really trying, here. So thanks for any help.
If the argument might be nil, use isEqual:, not isEqualToNumber:. The isEqual: method is documented to accept a nil argument.
What defaults to nil (or a zero-like quantity) in Objective-C is the return value of a message when you send the message to nil. For example, [nil isEqualToNumber:#7] returns NO; [nil copy] returns nil; [nil integerValue] returns 0. Thus it is generally safe to send any message to nil. It is not necessarily safe to pass nil as an argument to a message if the message isn't documented to accept nil as an argument.
UPDATE
Based on the code you added, you could do this:
numberB = (numberB == [NSNull null]) ? #0 : numberB;
if ([numberA isEqualToNumber:numberB]) {
...
}
Note that #0 represents an NSNumber with value 0. Also, there is only one instance of NSNull, so you can check for it with ==.
UPDATE 2
If NSLog is printing (null), then numberB is nil, which is different that [NSNull null]. You can check for both possibilities like this:
numberB = (numberB == nil || numberB == [NSNull null]) ? #0 : numberB;
if ([numberA isEqualToNumber:numberB]) {
...
}
If you are just worried that numberB could be nil and want to ignore that case:
if([numberA isEqualToNumber:numberB?: #(NSIntegerMax)]){...};
Note that this will also ignore any case where numberA is nil.
NSLog for numberB is null cause numberB is nil.
Make a test:
NSLog(#"NumberB is nil? %#", numberB == nil ? #"YES":#"NO"); --> displays 'NumberB is nil? YES'
when you compare a nil to [NSNull null], it returns NO
I suppose that you get numberB from a code like:
NSNumber *numberB = (NSNumber*)[[NSUserDefaults standardUserDefaults] valueForKey:#"numberB"];
it returns 'nil' value.
you should then compare numberB to nil, but not to [NSNull null].
Example
numberB = numberB != nil? numberB : #0;
if ([numberA isEqualToNumber:numberB) {
...
}
Nil defaults to zero (or the boolean NO) in logical statements. So for example if(nilObject) will evaluate to false. Nil arguments by default do not evaluate as 0. It's up to the specific method's implementation.
In this case, NSNumber compare: expects the argument to be non-nil. It's not a universal rule in Objective-C, just an NSNumber implementation detail.
You could first convert your NSNumber objects into primitive types, e.g. by calling [myNumber doubleValue] and then compare those. By the way, getting the primitive value from a nil NSNumber will give you 0.
I finally got it to work by doing
numberB= (numberB == nil)?[NSNumber numberWithLongLong:0]:numberB;
So the problem seems that #0 or simple 0 was being converted to nil. I don't quite understand it, but now it works. Thanks and +1 for everyone for all the help.
While the other answers are correct, let me add some more basic explanation:
In programming we're dealing with values and pointers. A value is a piece of data such as a numeric value or a string of characters. A pointer is a value too, but a specialized one - it is the address of a value in memory. Hence the name - it points to a piece of usable data.
If you do something like NSNumber *happyNumber = #7;, happyNumber is a pointer and it points to the value 7. Well, not exactly, because your 7 is actually wrapped in an object, but that's another story.
Now, sometimes a pointer is still around while the value it pointed to became invalid or is about to. Or the value doesn't even exist yet. But a pointer can never be "empty" and will always point at some memory address, so using such a pointer may result in undefined and random behavior. And to prevent this, we're using nil to give the pointer something meaningful to point to. It is sort of a placeholder, representing the "empty" state for a pointer and at the same time makes accidential use of the pointer relatively safe.
This means that you can throw messages at a nil object and quite literally, nothing happens. If you feed a nil pointer or object to another object though, it might not be prepared or able to handle "emptiness". This is what happened with isEqualToNumber:: it makes no sense to compare two numbers if one isn't even a number. In this case even just returning a boolean NO would be wrong in a way too, because it would imply that we are dealing with two perfectly healthy numbers which just happen to be different. So instead, NSNumber resolves the situation by throwing an exception.
NSNull is similar to nil in that it represents absence of something useful, however it is not the same. NSNull is a class and you can get a (singleton) instance of it. It is used where the absence of something needs to be represented by an actual, live object.
That's why the first version of your ternary operation failed - numberB was nil, not [NSNull null] so you where just checking for the wrong type of "emptiness" (#0 results in a perfectly fine NSNumber, not nil).
You can of course check for both possibilities, but if you default on using nil for yourself (which you should) this will seldom be necessary.
Hope that helps. Cheers!

Objective-C: Recursive method that changes property of self

I have a recursive method that changes a property of self (the object on which the method is defined).
I get the error:
...uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x755f670> was mutated while being enumerated...'
I read about this error, but I'm not sure how to apply a solution to my problem. Recursion is crucial to the solution, as is updating this particular property. I'm very new to Objective-C, so perhaps I'm missing something or designing this solution poorly.
currentPlayer is the property
recursiveMethod is, obviously, the recursive method that's producing the error
This recursive method is called from within a for in loop.
- (void) recursiveMethod:(id <Team>)team atIndex:(int *)i withPlayer:(id <Player>) {
[self.currentPlayer replaceObjectAtIndex:i withObject:nextPlayer];
if // some conditional that's unimportant to this question
{
// grab another team
// grab another index
// grab another player
[self recursiveMethod:nextTeam atIndex:i withPlayer:nextPlayer];
}
}
A lot of the details are unimportant. I stripped it down; really it's just a recursive method that will update a property (in this case an array) of the object on which the method is defined.
Just read the exception message:
NSArrayM [...] was mutated while being enumerated.
You can't do that.

Zero object in array crashes the program, also after certain count

I have a program that grabs data from Instagram, puts it into dictionary and then I parse it.
Problems begin when I try to use that data, magically item number 0 crashes my program.
Also, after certain number next item also crashes the program.
Here is the code for UITableViewCell, that grabs text from the array/dictionaries and puts it into cell. As it looks, it crashes, if I add another check to start with number 1, it works, but crashes later when it reaches some count.
I don't understand why this happens, since I have the method that tells the TableView how many rows are in table, and it returns [self.loader.dataArray count], so it can't possibly try to load the thing that is out of bounds.
if (self.loader.dataArray[indexPath.row] != nil )
{
cell.textLabel.text = [[[self.loader.dataArray[indexPath.row] objectForKey:#"caption"] objectForKey:#"from"] objectForKey:#"full_name"];
}
Here is the error I am getting:
instagramClient[8254:907] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull objectForKey:]: unrecognized selector sent to instance 0x3bb8f090'
The exception that you're getting does not indicate an out of bounds issue, it indicates that you have got an NSNull in your dataArray (or possibly in the "caption" object of one of the dictionaries in your dataArray or in the "from" object of one of those dictionaries, and so on). Try logging all of self.loader.dataArray when you get updates to that array and see if it contains any NSNull objects.

Serialization array of custom objects iOS

I know there is a lot of resources about that here but yet, I can't find what's going wrong with my code:
I have a class Level and two subclasses of Level: GameLevel and TransitionLevel. Each of this class implements the NSCoding protocol.
Then, I've got an array of Level (so it contains both classes of GameLevel and TransitionLevel). Saving the archive seems to work fine :
NSString *archivePath = [DOCUMENT_DIRECTORY stringByAppendingPathComponent:#"levels.archive"];
[NSKeyedArchiver archiveRootObject:levels toFile:archivePath];
Note that levels is the array I want to save. I can see the file, it's created and seems to contains what it's supposed to contain.
But when I want to retrieve this array :
NSString *archivePath = [DOCUMENT_DIRECTORY stringByAppendingPathComponent:#"levels.archive"];
levels = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath];
I've got this exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
I'm not sure what I'm missing here.
Either your implementation of initWithCoder: returns nil, either some part of your code tries to insert a nil value into an array.
You may go in the Breakpoint Navigator (⌘6) and add an Exception Breakpoint. Then, when the application raises an exception, the Debug Navigator will display the stack of the functions and methods currently executed. This would allow you to know precisely which method is trying to insert nil into an array.

Resources