Search code examples
iphoneobjective-ciosnullobjective-c-category

String isNullOrEmpty as a category


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.