When coding a method or function, is a good practice to check the input arguments to respond to any possible fail scenario.
For example:
-(void)insertNameInDictionary:(NSString*)nameString
{
[myDictionary setObject:nameString forKey:@"Name"];
}
This looks ok, but if nameString
is nil
, the app will crash. So, we can check if it's nil
, right?. We can also check if it is a NSString and not a NSNumber or if it is responds to the methods that our method needs to call.
So my question is: Which is the most complete and elegant way to check these arguments?
There are multiple ways to implement such protection:
NSParameterAssert
__attribute__((nonnull))
if
test.[EDIT]
Since the latest Xcode versions (Xcode 6), Apple added nullability annotations which is another — and better — way to express if a parameter can be nil
/ null
or not. You should migrate to that notation instead of using __attribute__((nonnull))
to make your APIs even more readable.
In details:
NSParameterAssert
is Apple's dedicated macro that checks a condition on a method parameter and throws a dedicated exception if it fails.
nil
as a parameter is a programmation/conception error, considering that due to your application workflow it should normally never happen (due to other conditions ensuring the param never being nil
for example), and if it's not something really went wrong.@try/@catch
if necessary), but it has the advantage that the exception thrown is more explicit (telling that the parameter was expected not to be nil
instead of crashing with an ugly and hard-to-understand callstack/message).If you want to allow your code to call your method with nil
but just do nothing in that case, you may simply if (!param) return
at the beginning of the function/method.
nil
is NOT a programmation/conception error and thus can sometimes happen due to the workflow of your application, so that's an acceptable case that can happen and just shouldn't crash.Less commonly known, there is the __attribute__((nonnull))
GCC/LLVM attribute that is dedicated to tell the compiler that some parameters of a function/method are expected to be not-null. This way, if the compiler can detect at compile time that you try to call your method/function with a nil
/NULL
argument (like directly calling insertNameInDitionary:nil
instead of using a variable which value can't be determined at compile time yet), it will emit a compilation error right away to let you fix it ASAP.
[EDIT] Since latest Xcode 6, you can (and should) use nullability annotations instead of __attribute__((nonnull))
. See examples in Apple's Blog post.
So in summary:
If you want to mark that your method EXPECT your parameter to be non-nil
, thus indicating that calling it with nil
is a logic error, you should do the following:
- (void)insertNameInDictionary:(NSString*)nameString __attribute__((nonnull))
{
// __attribute__((nonnull)) allows to check obvious cases (directly passing nil) at compile time
NSParameterAssert(nameString); // NSParameterAssert allows to check other cases (passing a variable that may or may not be nil) at runtime
[myDictionary setObject:nameString forKey:@"Name"];
}
If you think that calling your method with nil
can happen and is acceptable, and you just want to avoid a crash in those cases, simply do stuff like this:
-(BOOL)insertNameInDictionary:(NSString*)nameString
{
if (nameString == nil) return NO;
[myDictionary setObject:nameString forKey:@"Name"];
return YES;
}
And if you think that you should be able to insert a nil
object in your dictionary, you can convert the nil
value to NSNull
in that specific case so that it insert the NSNull
singleton that is dedicated to such usage (I used the short form of the ?:
ternary operator to make the code more compact):
-(void)insertNameInDictionary:(NSString*)nameString
{
[myDictionary setObject:nameString?:[NSNull null] forKey:@"Name"];
}
And last case, if you want that passing nil
in that particular example simply removes the Name
from myDictionary
, you can simply either do a simple if
test and call removeObjectForKey:@"Name"
if nameString
is nil
and call setObject:forKey:
if it's not… or you could use KVC and the generic setValue:forKey:
(KVC method, not specific to NSDictionary at all, so not to be confused with setObject:forKey:
) that in the case of NSDictionary exactly has the same behavior (removing the key from the dictionary if we pass nil
for the value parameter).
Apple suggests using NSAssert
for that:
NSAssert(nameString, @"nil nameString is not allowed");
This assertion would terminate your program, and produce an error message explaining what has happened.
Above, the != nil
part is implicit, because Objective-C allows it. You can state it explicitly for somewhat better readability:
NSAssert(nameString != nil, @"nil nameString is not allowed");
Assertions are not limited to nil
checking, because they can take arbitrarily complex conditions. You can check that an argument is of the expected type, that it responds to a particular selector, and so on. Assertions can be disabled in release code to save CPU cycles and the battery.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With