1

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;
}
Rahul Patel
  • 5,858
  • 6
  • 46
  • 72
Jason
  • 37
  • 1
  • 11
  • 1
    Update you question with execution stack using BT commend on debugger. – Tirth Nov 08 '13 at 09:58
  • @iAmbitious Sorry, I do not understand. – Jason Nov 08 '13 at 10:05
  • Which line is the exception thrown on? – Wain Nov 08 '13 at 10:07
  • @Wain autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([FWAppDelegate class])); I think Xcode is not very helpful this time. – Jason Nov 08 '13 at 10:10
  • Please show the declaration of `MQTTClient_message`. – trojanfoe Nov 08 '13 at 10:16
  • 1
    By the way - that is terrible code. Why is there a separate `free` call for `message` and `message->payload`? Why are you even calling `free` within *that* method at all? Why are you passing pointer-to-pointer in the first `free` call? Why not use an `enum` for these different types, instead of a string? Why call `if`, rather than `else if` to test the same variable? – trojanfoe Nov 08 '13 at 10:22
  • If the problem is memory related you might find it if you [enable NSZombie](http://stackoverflow.com/questions/5386160/how-to-enable-nszombie-in-xcode) – Matthias Bauch Nov 08 '13 at 10:24
  • @MatthiasBauch I've done that, doesn't work. – Jason Nov 08 '13 at 10:30
  • @trojanfoe I just use the IBM's MQTT library. The documentation about how to build a MQTT client is written in C, and I imitate that with objective-C. I consider so many free is about the memory management. Sample code:http://pic.dhe.ibm.com/infocenter/wmqv7/v7r5/index.jsp?topic=%2Fcom.ibm.mq.javadoc.doc%2FWMQMQxrCClasses%2Fpubasync.html The declaration of MQTTClient_message is not showed in the page. Please download the SDK, it is right there. – Jason Nov 08 '13 at 10:39
  • That's fair enough; it's IBM writing crap code rather than you. However my other points stand. If you use an `enum` to identify the messages, you can use a `switch` statement, which will be easier to follow. – trojanfoe Nov 08 '13 at 10:40
  • Add damn breakpoint and paste a line which crashes - why it's so hard for you ? :DDDD – Grzegorz Krukowski Nov 08 '13 at 10:55
  • @GrzegorzKrukowski I did what you said, [[NSNotificationCenter defaultCenter] postNotificationName:@"MessageCome" object:nil userInfo:myDictionary]; when I step into the notification, it crashed. – Jason Nov 08 '13 at 11:03
  • Post code from there - from the function which is reacting on that message – Grzegorz Krukowski Nov 08 '13 at 11:12
  • That `object:nil` looks wrong... – trojanfoe Nov 08 '13 at 11:13
  • Nope, object:nil is just fine. – Grzegorz Krukowski Nov 08 '13 at 11:13
  • Well it's not causing this error, however it's usual to provide that information if an observer has asked to be notified for all messages sent by a particular object. There is no reason to pass `nil`. – trojanfoe Nov 08 '13 at 11:17
  • So on which line it stops exactly ? Can you post also stack trace ? – Grzegorz Krukowski Nov 08 '13 at 11:21
  • Ok, object:nil is not a good practice. – Jason Nov 08 '13 at 11:21
  • @GrzegorzKrukowski hanlde:notification is not been executed. Whether I step over or step into postNotification, it will crash. – Jason Nov 08 '13 at 11:23
  • Why are you stepping into it? Put a breakpoint at the start of the handler; you shouldn't be interested in *how* the notification is posted, only how it's handled (i.e. just your code). – trojanfoe Nov 08 '13 at 11:23
  • @trojanfoe ...It crashed at [self.tableView reloadData] – Jason Nov 08 '13 at 11:30
  • Is there a chance you can just post that project somewhere ? That would be easiest – Grzegorz Krukowski Nov 08 '13 at 11:36
  • @GrzegorzKrukowski OK, wait, I'll post it to Github – Jason Nov 08 '13 at 11:41
  • @GrzegorzKrukowski it's best if all the relevant code is posted here, if possible, to keep the question self contained. Anyway, we finally got there, we have enough of the code to diagnose the issue. – JeremyP Nov 08 '13 at 11:49

3 Answers3

1

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.

Grzegorz Krukowski
  • 18,081
  • 5
  • 50
  • 71
  • 1
    As I advice - add exception breakpoint and find it :) I'm no a miracle to tell you that without whole project sources. It's definitely not in the part you pasted. – Grzegorz Krukowski Nov 08 '13 at 10:14
  • 1
    So this is more general advice than a concise answer? – trojanfoe Nov 08 '13 at 10:15
  • its run time miracle, questioner treating somewhere array data but he did not getting array instead of he got NSString. – Tirth Nov 08 '13 at 10:17
  • 1
    No - I gave you concrete answer pointing you the problem you have. It's nothing about ARC or memory. You are just calling a method on wrong class. Post the whole project than I can help you - or just use that expection breakpoint - it will stop exactly in the line you are doing things wrong - and that's general advice, but it will solve your problem – Grzegorz Krukowski Nov 08 '13 at 10:17
  • 1
    My bet is that something receives the notification and takes the array from the userInfo dictionary but the code casts it to an NSString. – David Snabel-Caunt Nov 08 '13 at 10:27
  • @GrzegorzKrukowski Are you confusing me with the OP? Your answer is pointless; 90% of people here know what *unrecognised selector* errors mean and the other 10% should be directed towards the 1000-odd questions on the subject which litter SO. – trojanfoe Nov 08 '13 at 10:29
  • Say what ?... Did you just tried using exception breakpoint instead of wasting time for such long comments ? :) Do it and paste a code, function which crashes, instead of having that pointless discussion. – Grzegorz Krukowski Nov 08 '13 at 10:30
  • It's probably not a writing problem of the OP (I hope he did not write `[array length]` in his code). But it's a good advice since, the zombie pointer seems to be a string that try to call `length` – KIDdAe Nov 08 '13 at 10:31
  • @GrzegorzKrukowski What? Me? I am not the OP!!! If your answer is "set an expection breakpoint" then that is a comment and not an answer. – trojanfoe Nov 08 '13 at 10:31
  • No my answer is you are calling length instead of count on an array. This is an answer. Breakpoint is an advice how to find where it is happening. – Grzegorz Krukowski Nov 08 '13 at 10:32
  • @GrzegorzKrukowski Me? you do not understand that I **did not ask this question** do you? – trojanfoe Nov 08 '13 at 10:33
  • @DavidCaunt Yes, my method handle the notification do cast it to an NSString, what the problem is? – Jason Nov 08 '13 at 10:56
  • Add exception breakpoint and show a function which breaks - how many times I have give that advice.... – Grzegorz Krukowski Nov 08 '13 at 10:58
1

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.

trojanfoe
  • 120,358
  • 21
  • 212
  • 242
  • @JeremyP yeah it took long enough to get all the required info. Cheers. – trojanfoe Nov 08 '13 at 11:57
  • @AlexSteven You're not "out of the woods" yet though. I think this line will cause issues too: `[[array valueForKey:@"description"]`. – trojanfoe Nov 08 '13 at 12:20
  • @trojanfoe Indeed, this line works well, but I also think it is not a good practice. Cause I have to mix objective-C with C, I have no idea how to take characters out of the array. The message delivered by MQTT is defined as void * – Jason Nov 08 '13 at 12:27
  • @AlexSteven Sounds like a new question then. – trojanfoe Nov 08 '13 at 12:29
  • @trojanfoe Uh-oh... Great programmer, you have a lot of reputation. I thought it means nothing to you. LOL – Jason Nov 08 '13 at 13:00
1

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.

JeremyP
  • 84,577
  • 15
  • 123
  • 161