Jason
Jason

Reputation: 37

NSString under ARC may cause -[__NSArrayM length]: unrecognized selector sent to instance?

I am using NSData to send and receive messages such as text, picture and voice. In order to differ the type of message, I append a header. When messages arrive, I assign the NSString object with the header and use [header isEqualToString:@"txt"] to determine different operations.

This is my method to handle the arrived message:

int msgarrvd (void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
    void *payloadptr = message->payload;
    int payLoadLen = message->payloadlen;
    NSLog(@"%d", payLoadLen);

    NSMutableArray *msgArray = [NSMutableArray array];
    NSData *dataHeader = [NSData dataWithBytes:payloadptr length:3];
    NSStringEncoding strEncode = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingUTF8);
    NSString *header = [[NSString alloc] initWithData:dataHeader encoding:strEncode];
    NSLog(@"header:%@", header);

        if ([header isEqualToString:@"txt"]) {
            // text message
            NSData *content = [NSData dataWithBytes:payloadptr length:payLoadLen];
            NSString *msgContent = [[NSString alloc] initWithData:content encoding:strEncode];
            NSString *subStr = [msgContent substringFromIndex:3];
            [msgArray addObject:subStr];
        }
        if ([header isEqualToString:@"pic"]) {
          // pic message
        }
        if ([header isEqualToString:@"voc"]) {
            // voice message
            NSMutableData *voiceData = [NSMutableData data];
            [voiceData appendData:[NSData dataWithBytes:payloadptr length:payLoadLen]];
            [voiceData replaceBytesInRange:NSMakeRange(3, payLoadLen) withBytes:payloadptr];
            NSFileManager *fm = [NSFileManager defaultManager];
            NSString *voicePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:@"sound.wav"];
            [fm createFileAtPath:voicePath contents:voiceData attributes:nil];
            NSURL *voiceURL = [NSURL fileURLWithPath:voicePath];
            [msgArray addObject:voiceURL];

        NSMutableDictionary *myDictionary = [NSMutableDictionary dictionary];
        if ([header isEqualToString:@"txt"]) {
            [myDictionary setObject:header forKey:@"header"];
        }
        if ([header isEqualToString:@"pic"]) {
            [myDictionary setObject:header forKey:
             @"header"];
        }
        if ([header isEqualToString:@"voc"]) {
            [myDictionary setObject:header forKey:@"header"];
        }

        [myDictionary setObject:msgArray forKey:@"content"];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"MessageCome" object:nil userInfo:myDictionary];

        MQTTClient_freeMessage(&message);
        MQTTClient_free(payloadptr);

        return 1;
    }

The Xcode throw out-[__NSArrayM length]: unrecognized selector sent to instance 0x17dc7bb0 and Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM length]: unrecognized selector sent to instance 0x17dc7bb0'

What on earth the problem is?

Any help should be appreciate!

More

 - (void)handle:(NSNotification *)notification
    {
        NSDictionary *message = [NSDictionary dictionaryWithDictionary:[notification userInfo]];
        NSString *msgType = [message objectForKey:@"header"];
        if ([msgType isEqualToString:@"txt"]) {
            NSArray *array = [message objectForKey:@"content"];
            NSString *msg = [[array valueForKey:@"description"] componentsJoinedByString:@""];
            [self.messages addObject:msg];
            NSLog(@"%@&%lu", msg, (unsigned long)[self.messages count]);
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.tableView reloadData];
            });

        }
        if ([msgType isEqualToString:@"pic"]) {
            // process pic
        }
        if ([msgType isEqualToString:@"voc"]) {
            [self.messages addObject:[message objectForKey:@"content"]];
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.tableView reloadData];
            });
        }
    }

Here is my observer

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle:) name:@"MessageCome" object:nil];



    context void *  NULL    0x00000000
topicName   char *  "/test" 0x16dcbe24
*topicName  char    '/' '/'
topicLen    int 0   0
message MQTTClient_message *    0x16dcc064  0x16dcc064
struct_id   char [4]    ""  
struct_version  int 4   4
payloadlen  int 28675   28675
payload void *  0x4409004   0x04409004
qos int 1   1
retained    int 0   0
dup int 0   0
msgid   int 25864   25864
payloadptr  void *  0x4409004   0x04409004
payLoadLen  int 28675   28675
msgArray    __NSArrayM *    @"1 object" 0x16dcc280
[0] NSURL * @"file:///var/mobile/Applications/417171BD-60C5-4DF6-989D-2983426B9CAD/Library/Documentationsound.wav"  0x16dd0760
dataHeader  _NSInlineData * 3 bytes 0x16d48420
strEncode   NSStringEncoding    4   4
header  __NSCFString *  @"voc"  0x16dca530
myDictionary    __NSDictionaryM *   2 key/value pairs   0x16dd0830
[0] (null)  @"content" : @"1 object"    
[1] (null)  @"header" : @"voc"  
content NSData *    nil 
msgContent  NSString *  nil 
subStr  NSString *  nil 
voiceData   NSMutableData * nil 
fm  NSFileManager * nil 
voicePath   NSString *  nil 
voiceURL    NSURL * nil 

This is my tableView dataSource

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{

    return [self.messages count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        cell.selectionStyle = UITableViewCellSelectionStyleGray;
        cell.textLabel.text = [self.messages objectAtIndex:indexPath.row];
    }


    // Configure the cell...
    cell.textLabel.text = [self.messages objectAtIndex:indexPath.row];
    return cell;
}

Upvotes: 1

Views: 2313

Answers (3)

JeremyP
JeremyP

Reputation: 86651

This line (in msgarrvd)

[myDictionary setObject:msgArray forKey:@"content"];

sets the content key of a dictionary to an array (a mutable one in fact).

This line (in the notification handler)

[self.messages addObject:[message objectForKey:@"content"]]

takes the object for the content key (an array remember) and adds it to the messages array.

This line (in tableView:cellForRowAtIndexPath:)

cell.textLabel.text = [self.messages objectAtIndex:indexPath.row];

takes an object from the messages array (which was itself an array, remember) and tries to assign it to a property called text which is expected to be a string. Then somewhere inside the Cocoa library something wants to find out how long the string is, but it is not a string, it's an array and arrays don't respond to -length.

Upvotes: 1

trojanfoe
trojanfoe

Reputation: 122391

Here is the error:

if ([msgType isEqualToString:@"voc"]) {
    [self.messages addObject:[message objectForKey:@"content"]];   // HERE
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.tableView reloadData];
    });
}

As you've already shown that @"content" is an array:

myDictionary    __NSDictionaryM *   2 key/value pairs   0x16dd0830
[0] (null)  @"content" : @"1 object"    // HERE
[1] (null)  @"header" : @"voc"  

And your table code expects it to be a string:

cell.textLabel.text = [self.messages objectAtIndex:indexPath.row];

Hence the unrecognised selector error, as the label's text is expected to be an NSString object and not an NSArray object.

Note the difference in how you populate your data source for @"voc" and @"txt" type messages.

Upvotes: 1

Grzegorz Krukowski
Grzegorz Krukowski

Reputation: 19802

To get size of NSMutableArray you need to call:

 [array count]

not length

The error tells you are calling "length" on a NSMutableArray class - this is why it crashes. It's not in the code you pasted - so best put a exception breakpoint and check from which line it is coming.

Upvotes: 1

Related Questions