What is the best way to test method arguments in Objective-C?

When encoding a method or function, it is recommended that you check the input arguments to respond to any possible failure scenario.

For instance:

-(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setObject:nameString forKey:@"Name"]; } 

This looks fine, but if nameString is nil , the application crashes. So, we can check if it is nil correct. We can also check if this is an NSString and not an NSNumber or is responding to methods that our method should execute.

So my question is: what is the most comprehensive and elegant way to test these arguments?

+6
source share
5 answers

There are several ways to implement this protection:

  • NSParameterAssert
  • __attribute__((nonnull))
  • and a simple if test.

[EDIT] Starting with the latest versions of Xcode (Xcode 6), Apple has added zero visibility annotations , which is different - and the best way to express if a parameter can be nil / null or not. You should switch to this notation instead of using __attribute__((nonnull)) to make your APIs even more readable.


More details:

  • NSParameterAssert is an Apple-highlighted macro that checks the condition for a method parameter and throws a thrown exception if it does not work.

    • This is Runtime only protection .
    • This macro also considers that passing nil as a parameter is a programming / concept error , given that because of your application workflow it usually does not happen (due to other conditions, for example, nil ), and if it isn’t something really went wrong.
    • It still excludes (if your calling code can @try/@catch if necessary), but it has the advantage that the exception thrown is more explicit (indicating that the parameter should not be nil instead of crashing with an ugly and hard-to-understand callstack / message).
  • If you want to allow the code to call your method using nil , but in this case just do nothing, you can simply if (!param) return at the beginning of the function / method.

    • This holds that nil passing is NOT a programming / concept error and therefore can sometimes happen due to the workflow of your application, so there is an acceptable case that can happen and just should not crash.
  • Less well-known, there is the __attribute__((nonnull)) GCC / LLVM attribute , which is intended to tell the compiler that some parameters of the / function are expected to be non-null . Thus, if the compiler can detect at compile time that you are trying to call your method / function using the nil / null argument (for example, directly call insertNameInDitionary:nil instead of using a variable whose value cannot be determined at compile time), it will immediately issue compilation error so you can fix it as soon as possible.

  • [EDIT] Since the last Xcode 6, you can (and should) use zero visibility annotations instead of __attribute__((nonnull)) . See Apple Blog Examples.


So in short:

If you want to note that your EXPECT method for your parameter was not nil , which indicates that calling it with nil is a logical mistake, 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 using nil might happen and is acceptable , and you just want to avoid a crash in these cases, just follow these steps:

 -(BOOL)insertNameInDictionary:(NSString*)nameString { if (nameString == nil) return NO; [myDictionary setObject:nameString forKey:@"Name"]; return YES; } 

And if you think you can insert the nil object into a dictionary , you can convert the nil value to NSNull in this particular case, so that it inserts the NSNull singleton, which is intended for such use (I used a short form of the ternary operator ?: To make the code more compact):

 -(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setObject:nameString?:[NSNull null] forKey:@"Name"]; } 

And in the latter case, if you want the nil transfer in this particular example to simply remove Name from myDictionary , you can simply run a simple if test and call removeObjectForKey:@"Name" if nameString is nil and calls setObject:forKey: if this is not so ... or you can use KVC and the common setValue:forKey: (the KVC method is not specific to NSDictionary, so you should not be confused with setObject:forKey: , which in the case of NSDictionary has exactly the same behavior (removing the key from dictionary, if we pass nil for the parameter value).

+24
source
 - (void)insertNameInDictionary:(NSString *)nameString { if ([nameString isKindOfClass:[NSString class]]) { [myDictionary setObject:nameString forKey:@"Name"]; } } 
+2
source

Apple suggests using NSAssert for this:

 NSAssert(nameString, @"nil nameString is not allowed"); 

This statement will terminate your program and give an error message explaining what happened.

Above, the part != nil implicit, because Objective-C allows this. You can specify this explicitly for easier readability:

 NSAssert(nameString != nil, @"nil nameString is not allowed"); 

Statements are not limited to nil , as they can take arbitrarily complex conditions. You can verify that the argument has the expected type, that it matches a specific selector, etc. Claims can be disabled in the release code to save processor and battery cycles.

+2
source

I see a couple of problems here. NSMutableDictionary does not have a " setObject " method. You need to specify a key.

 -(void)insertNameInDictionary:(NSString*)nameString { if(nameString) [myDictionary setObject:nameString forKey: @"someKey"]; } 

And you can also do a nil check through the " if(nameString) " bit in my example. If you want to have an empty string, use @"" and not zero.

+1
source

it all depends on the semantics of what you want, if you want the KV pair to be removed, if the value is nil, you can use setValue: forKey:

as:

 -(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setValue:nameString forKey:@"Name"]; } 

or if you want to keep the value that represents the nil value, you can use NSNull

 -(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setObject:nameString? :[NSNull null] forKey:@"Name"]; } -(NSString *)nameInDictionary { NSString * retVal = [myDictionary objectForKey:@"Name"]; return (retVal==[NSNull null]) ? nil : retVal; } 

or you can just register it.

 -(void)insertNameInDictionary:(NSString*)nameString { if(!nameString) { NSLog(@"expected nameString to be not null... silly me %s",__PRETTY_FUNCTION__); return; } [myDictionary setObject:nameString forKey:@"Name"]; } 
+1
source

All Articles