iOS 8 works but iOS 7 crashes with -[NSNull length]: unrecognized selector sent to instance

1.4k views Asked by At

I've been searching for answers for this but still haven't been able to solve how to correct the problem. This -[NSNull length]: unrecognized selector sent to instance and this [NSNull length]: unrecognized selector sent to instance 0x43fe068 did not help.

I'm working on a chat app with a Parse back-end and I was having a timestamp problem with a chat message showing up out of order so I deleted the rows that were out of order from my Parse database using the Databrowser. When I tested the app, that seemed to fix the problem on my iPhone 6 Plus and on the iPhone 6 simulator both running iOS 8. However, when opening up the same chat room on my iPhone 5s running iOS 7, the app crashes consistently with the following error.

-[NSNull length]: unrecognized selector sent to instance

I have no idea why deleting a row would cause this to happen and why only on iOS 7? I set an All Exceptions Breakpoint and here is the offending line along with a screenshot.

    self.lastMessageLabel.textColor = [UIColor redColor];

enter image description here

I still get the NSNull length crash even when I comment out the above line, but it breaks at the generic main.m.

Any suggestions on how to solve this would be appreciated. Thanks.

EDIT 1: Here's the code from my ChatView.m that's being loaded by my PrivateInbox.

- (void)loadMessages {

    if (isLoading == NO)
    {
        isLoading = YES;
        JSQMessage *message_last = [messages lastObject];

        PFQuery *query = [PFQuery queryWithClassName:PF_CHAT_CLASS_NAME];
        [query whereKey:PF_CHAT_ROOM equalTo:chatroomId];

        if (message_last != nil) {
            [query whereKey:PF_CHAT_SENTDATE greaterThan:[self.dateFormatter stringFromDate:message_last.date]];
        }

        [query includeKey:PF_CHAT_USER];
        [query orderByAscending:PF_CHAT_SENTDATE];
        [query addAscendingOrder:PF_CHAT_CREATEDAT];
        [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
        {
            if (error == nil)
            {
                for (PFObject *object in objects)
                {
                    PFUser *user = object[PF_CHAT_USER];
                    [users addObject:user];

                    if(![object[PF_CHAT_TEXT] isKindOfClass:[NSNull class]]) {

                        NSDate* sentDate;
                        if(object[PF_CHAT_SENTDATE] != nil)
                            sentDate = [self.dateFormatter dateFromString:object[PF_CHAT_SENTDATE]];
                        else
                            sentDate = object.createdAt;

                        JSQTextMessage *message = [[JSQTextMessage alloc] initWithSenderId:user.objectId senderDisplayName:user.objectId date:sentDate text:object[PF_CHAT_TEXT]];
                        [messages addObject:message];

                    } else if(object[PF_CHAT_PHOTO] != nil) {

                        NSDate* sentDate;
                        if(object[PF_CHAT_SENTDATE] != nil)
                            sentDate = [self.dateFormatter dateFromString:object[PF_CHAT_SENTDATE]];
                        else
                            sentDate = object.createdAt;

                        PFFile* photoFile = object[PF_CHAT_PHOTO];
                        JSQPhotoMediaItem *photoItem = [[JSQPhotoMediaItem alloc] init];
                        JSQMediaMessage *photoMessage = [[JSQMediaMessage alloc] initWithSenderId:user.objectId
                                                                                senderDisplayName:user.objectId
                                                                                             date:sentDate
                                                                                            media:photoItem];
                        [messages addObject:photoMessage];

                        {
                            [photoFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
                                photoItem.image = [UIImage imageWithData:data];
                                [self.collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObjects:[NSIndexPath indexPathForItem:[messages indexOfObject:photoMessage] inSection:0], nil]];
                            }];
                        }

                    } else if(object[PF_CHAT_VIDEO] != nil) {

                        NSDate* sentDate;
                        if(object[PF_CHAT_SENTDATE] != nil)
                            sentDate = [self.dateFormatter dateFromString:object[PF_CHAT_SENTDATE]];
                        else
                            sentDate = object.createdAt;

                        PFFile* videoFile = object[PF_CHAT_VIDEO];
                        JSQVideoMediaitem *videoItem = [[JSQVideoMediaitem alloc] initWithFileURL:[NSURL URLWithString:[videoFile url]] isReadyToPlay:YES];
                        JSQMediaMessage *videoMessage = [[JSQMediaMessage alloc] initWithSenderId:user.objectId
                                                                                 senderDisplayName:user.objectId
                                                                                     date:sentDate
                                                                                       media:videoItem];
                        [messages addObject:videoMessage];
                    }
                }

                if ([objects count] != 0) {
                    [JSQSystemSoundPlayer jsq_playMessageReceivedSound];                    
                    [self resetUnreadCount];
                    [self finishReceivingMessage];
                }
            }
            else [ProgressHUD showError:@"Network error."];
            isLoading = NO;
        }];
    }
}

EDIT 2: I tried NSNullSafe from Nick Lockwood https://github.com/nicklockwood/NullSafe and that allowed the Private Inbox to open without crashing and gets me past the NSNull Length error but I think that just masks the problem and I still don't know why it didn't crash on iOS 8 but did crash on iOS 7.

3

There are 3 answers

1
Andrea On

I don't think that this could be related to the differences between the 2 operative systems.
The crash is pretty clear, you are sending a message to an object of class NSNUll that can't handle it.
The fact you are using parse or a web services in general makes me think that this object was generated by the back end as a null in a JSON and translated into a NSNull object by the JSON parsing.
You should find a way to handle NSNull object probably at parsing level.

0
Hemang On

@Andrea is correct, if you can't figure it out from your API (server) side then, here's categories of NSDictionary and NSArray which removes any NSNull object and your app wouldn't crash.

@interface NSDictionary (NullReplacement)

- (NSDictionary *)dictionaryByReplacingNullsWithBlanks;

@end

@interface NSArray (NullReplacement)

- (NSArray *)arrayByReplacingNullsWithBlanks;

@end

@implementation NSDictionary (NullReplacement)

- (NSDictionary *)dictionaryByReplacingNullsWithBlanks {
    const NSMutableDictionary *replaced = [self mutableCopy];
    const id nul = [NSNull null];
    const NSString *blank = @"";

    for (NSString *key in self) {
        id object = [self objectForKey:key];
        if (object == nul) [replaced setObject:blank forKey:key];
        else if ([object isKindOfClass:[NSDictionary class]]) [replaced setObject:[object dictionaryByReplacingNullsWithBlanks] forKey:key];
        else if ([object isKindOfClass:[NSArray class]]) [replaced setObject:[object arrayByReplacingNullsWithBlanks] forKey:key];
    }
    return [NSMutableDictionary dictionaryWithDictionary:[replaced copy]];
}

@end

@implementation NSArray (NullReplacement)

- (NSArray *)arrayByReplacingNullsWithBlanks  {
    NSMutableArray *replaced = [self mutableCopy];
    const id nul = [NSNull null];
    const NSString *blank = @"";
    for (int idx = 0; idx < [replaced count]; idx++) {
        id object = [replaced objectAtIndex:idx];
        if (object == nul) [replaced replaceObjectAtIndex:idx withObject:blank];
        else if ([object isKindOfClass:[NSDictionary class]]) [replaced replaceObjectAtIndex:idx withObject:[object dictionaryByReplacingNullsWithBlanks]];
        else if ([object isKindOfClass:[NSArray class]]) [replaced replaceObjectAtIndex:idx withObject:[object arrayByReplacingNullsWithBlanks]];
    }
    return [replaced copy];
}

@end

Caution : Only appropriate if you're parsing small amount of data.

Source : https://stackoverflow.com/a/16702060/1603234

0
John Davis On

The other two answers are correct - this is not an issue of OS version, but rather that you are being passed a NULL value and are not catching it.

The line you caught in the Exception:

 self.lastMessageLabel.textColor = [UIColor redColor];

Is indicating a symptom, not the cause. I am not 100% sure how _setTextColor actually works, but I would make a strong bet that it works by using the length attribute of NSString/NSAttributedString to make a NSRange so that it knows where to start applying the color and end applying the color. If the data is NULL, then as the other users mentioned, you are trying to access the length attribute of the wrong class, causing your crash.

Judging from your stack trace, lines 5 (setMessage:forUser) and 6 (cellForRow...) are where you should be trying to catch the NULL value. Either that, or you should modify your data controller so that when a NULL value is passed back in the JSON, you replace it with a placeholder, such as "No Message".

At either rate, your crash is probably happening when you assign a value to self.lastMessageLabel.text when you are building your tableView cells in cellForRow.... Check the NSString that you use there for class type (isKindOfClass[NSNULL class]) and see if that helps.