ImDevinC
ImDevinC

Reputation: 528

Updating UI in a dispatch_async crashes

I have the following code

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:SOMEURL];


    NSError *err;

    NSData *response = [NSURLConnection sendSynchronousRequest:urlRequest
                                             returningResponse:nil error:&err];

    if (err)
    {
        // Do some error stuff
    }

    err = nil;

    NSDictionary *downloadedDictionary = [NSJSONSerialization JSONObjectWithData:response options:0 error:&err];

    if (err)
    {
        // Do some error stuff
    }

    dispatch_sync(dispatch_get_main_queue(), ^{
        NSDictionary* item = [downloadedDictionary objectForKey:@"user"];

        NSString* name = [item valueForKey:@"name"];

        [txtName setText:name];
    });
});

However I crash every time on [txtName setText:name]. txtName is set in the header and synthesized properly. In fact, if I do [txtName setText:@"Random text"] it works just fine. And I can also do NSLog(@"%@", name) and it will log the information just. It's just when I try to mix the two together that it crashes. Any ideas? My error is

 Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI length]: unrecognized selector sent to instance 0x109c66bd0'

Upvotes: 1

Views: 361

Answers (2)

David Hoerl
David Hoerl

Reputation: 41632

Try this, and make sure you have a Xcode breakpoint on exceptions:

dispatch_sync(dispatch_get_main_queue(), ^{
        assert(store);
        assert([store isKindOfClass:[NSDictionary class]]);
        NSString* name = [store valueForKey:@"name"];
        assert(name);
        assert([name isKindOfClass:[NSString class]]);
        assert([self.txtName isKindOfClass:[UITextField class]]); 

        [self.txtName setText:name]; // note the user of "self"
    });

Upvotes: 1

Tommy
Tommy

Reputation: 100612

You've done the correct thing there by hopping back onto the main queue in order to update your UIKit objects. As you've presumably noticed from the documentation, UIKit isn't generally safe for use on background threads or queues, subject to a few exceptions that Apple mentions in a fairly disorganised ad hoc fashion.

That being said, it appears that [store valueForKey:@"name"] is an array, not a string. So your mistake is failing to validate and/or correctly to parse your returned JSON. If you NSLog it (or, if you want to be really sure, [name class]) you should see that.

Similarly if you inspect the JSON you'll probably see square brackets around the result.

You probably want something like:

NSString *name = nil; // or don't bother with "= nil" if using ARC
NSArray *names = [store valueForKey:@"name"];
if([names isKindOfClass:[NSArray class]] && [names count] > 0)
    name = names[0];

if(name)
    [txtName setText:name];

Assuming you are expecting exactly one name. The two ifs there act as validation, and Objective-C exactly follows the C rule for conditional evaluation — if the isKindOfClass: fails then the call to count will never occur, so it's safe to test for type on one side of a conditional and then assume it on the other.

Upvotes: 0

Related Questions