String isNullOrEmpty as a category
-
14-12-2019 - |
Question
I'm trying to create a method which checks for a null/nil/empty string, and I'm trying to get it working as a category but having no luck.
I'm using this code, based on answers in this topic:
@implementation NSString (NSStringExtension)
- (BOOL)isNullOrEmpty {
return self == nil ||
self == (id)[NSNull null] ||
[@"" isEqualToString:self] ||
[[self stringByReplacingOccurrencesOfString:@" " withString:@""] length] == 0||
[self isEqualToString:@"(null)"]
|| ([self respondsToSelector:@selector(length)] && [(NSData *) self length] == 0)
|| ([self respondsToSelector:@selector(count)] && [(NSArray *) self count] == 0)
|| [[self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0;
}
@end
Yet when I try to use this this is what I get:
NSLog([@"" isNullOrEmpty] ? @"1":@"0"); // prints 1
NSString *s1 = nil;
NSLog([s1 isNullOrEmpty] ? @"1":@"0"); // prints 0
NSLog([args.itemName isNullOrEmpty] ? @"1":@"0"); // prints 0
NSLog([(NSString*)nil isNullOrEmpty] ? @"1":@"0"); // prints 0
This is baffling me, and I can only assume that some combination of iOS5/ARC is causing the nil object to be coerced to a blank string/pointer. The debugger shows the string as 0x0, yet when I use my isNullOrEmpty
method, I get false.
Solution
return self == nil
This can never happen. If you try to send isNullOrEmpty
(or any other message) to nil
, nothing happens (objc_msgSend()
, the function responsible for message dispatch, checks for a nil
reciever as one of the first things it does and aborts).
self == (id)[NSNull null]
This will also never happen. If you send isNullOrEmpty
to an object that's an instance of NSNull
, your method here, which is a method on NSString
, will not be called. Instead, NSNull
's version (which probably doesn't exist) will be.
Likewise, ([self respondsToSelector:@selector(count)] && [(NSArray *) self count])
is never going to happen. If the object is an NSArray
, then isNullOrEmpty
will never run, because, again, it's a method of NSString
.
Correspondingly, [(NSData *) self length]
doesn't do what you think it does. NSString
instances do respond to length
, but casting the object to NSData
doesn't use the NSData
version of the method -- it still ends up as the NSString
version of length
, because the object actually is an NSString
(casting only happens at compile-time; it can't change anything at run-time).
[self isEqualToString:@"(null)"]
Here you appear to be checking for nil
again, but you are being misled by the representation that NSLog
chooses when it prints nil
:
NSLog(@"%@", nil);
This displays (null)
in the console, but that doesn't mean that the object itself is a string with those characters. NSLog
just chooses that string to display for nil
.*
Several of the things you are doing would require this to be in a category on NSObject
, so that the method would in fact be called even if the object was not an NSString
.
To check for a string consisting only of whitespace, all you need is the comparison to the empty string @""
after trimming whitespace:
NSString * trimmedSelf = [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
// Then either:
[trimmedSelf isEqualToString:@""];
// Or:
([trimmedSelf length] == 0);
*And even better, doing NSLog(@"%@", [NSNull null]);
displays <null>
(angle brackets instead of parentheses), wonderfully confusing the first few times you encounter NSNull
.
OTHER TIPS
Another approach can be to define a simple macro.
#define NSStringIsNullOrEmpty(str) ((str==nil) || [(str) isEqualToString:@""])
It's simple and effective. If you do not like macros you can always convert it to a function call without affecting the rest of your code.
-- Update:
@Bryan has raised a good point. An inline function is a great way to go. Here is an updated macro that will evaluate str only once.
#define NSStringIsNullOrEmpty(str) ({ NSString *_str=(str); ((tmp==nil) || [tmp isEqualToString:@""]);})
In Objective-C, sending a message to nil will always return 0 (or NO
, a zeroed-out struct, NULL, etc., depending on the declared return type). The isNullOrEmpty
method that you wrote won't actually be invoked when you send isNullOrEmpty
to nil
. See the accepted answer to Sending a message to nil? for more information.
Perhaps you could change your method to be isNotNullOrEmpty
. Then a return value of 0 when sending isNotNullOrEmpty
to nil
will make sense.
You aren't calling your method, but sending a message to nil.
This is expected behavior. You are sending a message to nil
after all. So it is returning either nil (or some other 0 value). Which short circuits to false so that '0' is printed in the cases shown below:
NSLog([s1 isNullOrEmpty] ? @"1":@"0"); // prints 0
NSLog([(NSString*)nil isNullOrEmpty] ? @"1":@"0"); // prints 0
You can even confirm your message is not being called for those cases by setting a breakpoint in your new category method.
Like others have said, calling [nil isNullOrEmpty];
will not actually run your method. The nil
object is just that : empty itself.
As a solution, I'd like to say that it's not because you're in an Object-Oriented language that you must never use functions.
BOOL IsStringNilOrEmpty(NSString*)str
{
return str == nil ||
str == null ||
[@"" isEqualToString:str] ||
[[str stringByReplacingOccurrencesOfString:@" " withString:@""] length] == 0||
[str isEqualToString:@"(null)"]
|| ([str respondsToSelector:@selector(length)] && [(NSData *) str length] == 0)
|| ([str respondsToSelector:@selector(count)] && [(NSArray *) str count] == 0)
|| [[str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0;
}
actually I just fixed this problem by turning it around like so
-(BOOL) isNotNullOrWhiteSpace
{
return [self length] != 0 && [[self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] != 0;
}
so instead of isNullOrWhiteSpace it's isNotNullOrWhiteSpace.
Here's my method of checking null/empty
-(NSString*)NULLInputinitWithString:(NSString*)InputString {
if( (InputString == nil) ||(InputString ==(NSString*)[NSNull null])||([InputString isEqual:nil])||([InputString length] == 0)||([InputString isEqualToString:@""])||([InputString isEqualToString:@"(NULL)"])||([InputString isEqualToString:@"<NULL>"])||([InputString isEqualToString:@"<null>"]||([InputString isEqualToString:@"(null)"])||([InputString isEqualToString:@"NULL"]) ||([InputString isEqualToString:@"null"])))
return @"";
else
return InputString ;
}
Have you thought about creating a class method on a category that extends NSString?
NSString+NSStringExtensions.h
#import <Foundation/Foundation.h>
@interface NSString(NSStringExtensions)
+(BOOL)isNilOrEmpty:(NSString*)string;
@end
NSString+NSStringExtensions.m
#import "NSString+NSStringExtensions.h"
@implementation NSString(NSStringExtensions)
+(BOOL)isNilOrEmpty:(NSString*)string
{
if (nil == string)
{
return YES;
}
if (string.length == 0)
{
return YES;
}
return NO;
}
@end
Then you use it like this:
#import "NSString+NSStringExtensions.h"
...
NSLog([NSString isNilOrEmpty:@""] ? @"1":@"0");