Question

I get a json array delivered on a web-request and store it in a NSMutableArray. The first run works fine. But when I do a refresh of my screen, and withit do a new web-request which stores the json in the same array, then the problems start

How can I clear my array to store a new json from a web-request in it?

Below the array where I store the json:

SingletonClass.h

#import <Foundation/Foundation.h>
@class Singleton;
@protocol SingletonDelegate <NSObject>
@end

@interface SingletonClass : NSObject

// ...
@property (nonatomic, strong) NSMutableArray *uData;

@end

SingletonClass.m

#import "SingletonClass.h"

@implementation SingletonClass

static SingletonClass *sharedInstance = nil;

// Get the shared instance and create it if necessary.
+ (SingletonClass *)sharedInstance {
    if (sharedInstance == nil) {
        sharedInstance = [[super allocWithZone:NULL] init];
    }

    return sharedInstance;
}

- (id)init
{
    self = [super init];
    if (self) {
        // ...
        self.uData = [[NSMutableArray alloc] init]; // store json array from web-request
    }
    return self;
}
@end

And here 3 variants where I try to re-store the json in the array. The first web-request works as desired, but if I do another web-request I get the errors...

WebApi.h

#import "AFHTTPRequestOperationManager.h"
#import "SingletonClass.h"


@interface WebApi : AFHTTPRequestOperationManager <SingletonDelegate>

@property (nonatomic, strong) SingletonClass *sshare;
-(void)getUserStream;

@end

WebApi.m - including 3 variants

#import "WebApi.h"

#define kApiHost @"http://foo.net"
#define kApiPath @"bar"

@implementation WebApi

-(id)init {
    self = [super init];
    if (self != nil) {
        self.sshare = [SingletonClass sharedInstance];
    }
    return  self;
}

-(void)getUserStream {

    NSString *URLString = [NSString stringWithFormat:@"%@/%@/pics/user", kApiHost, kApiPath];

    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    [self setAuthHeader:manager];

    // clear uData
    [self.sshare.uData removeAllObjects];  // Breakpoint set

    [manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {

        // Variant 1
        self.sshare.uData = responseObject;

        // Variant 2
        NSMutableArray *tmp = [NSMutableArray arrayWithArray:responseObject];
        self.sshare.uData = [tmp copy];

        // Variant 3
        NSMutableArray *tmp = [NSMutableArray arrayWithArray:responseObject];
        self.sshare.uData = [tmp mutableCopy];

    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        DLog(@"Error: %@, responseString: %@", error, operation.responseString);
    }];
}

@end

Error on variant 1, after breakpoint removeAllObjects

[1417:60b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray removeObjectAtIndex:]: mutating method sent to immutable object'
*** First throw call stack:
(0x2f851f03 0x39fe6ce7 0x2f851e45 0x2f7cee7b 0x2f79b769 0x3d377 0x3aeb5 0x320b66c7 0x320b6663 0x320b6633 0x320a1d7b 0x321e1eef 0x3a4cfd53 0x3a4d4817 0x3a4cfd3f 0x3a4d13ef 0x3a4d2673 0x2f81c679 0x2f81af45 0x2f7857a9 0x2f78558b 0x346f26d3 0x320e4891 0x63569 0x3a4e4ab7)
libc++abi.dylib: terminating with uncaught exception of type NSException

Error on variant 2, after breakpoint on removeAllObjects

[1425:60b] -[__NSArrayI removeAllObjects]: unrecognized selector sent to instance 0x156f5910
[1425:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI removeAllObjects]: unrecognized selector sent to instance 0x156f5910'
*** First throw call stack:
(0x2f851f03 0x39fe6ce7 0x2f855837 0x2f85412f 0x2f7a30d8 0x2c2ff 0x29e3d 0x320b66c7 0x320b6663 0x320b6633 0x320a1d7b 0x321e1eef 0x3a4cfd53 0x3a4d4817 0x3a4cfd3f 0x3a4d13ef 0x3a4d2673 0x2f81c679 0x2f81af45 0x2f7857a9 0x2f78558b 0x346f26d3 0x320e4891 0x52569 0x3a4e4ab7)
libc++abi.dylib: terminating with uncaught exception of type NSException

Error on variant 3, after breakpoint on removeAllObjects

[1392:60b] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 3 beyond bounds for empty array'
*** First throw call stack:
(0x2f851f03 0x39fe6ce7 0x2f7880cd 0xefbef 0x321b4199 0x3215b3fb 0x3215ac51 0x32081305 0x31cfd31b 0x31cf8b3f 0x31cf89d1 0x31cf83e5 0x31cf81f7 0x31d4bd45 0x34b1b75d 0x3053c5c9 0x2f811c4d 0x2f81c83f 0x2f81c7db 0x2f81afa7 0x2f7857a9 0x2f78558b 0x346f26d3 0x320e4891 0x119569 0x3a4e4ab7)
libc++abi.dylib: terminating with uncaught exception of type NSException
Was it helpful?

Solution

Your first variant is because somewhere you are getting a NSArray, not an NSMutableArray (probably when you first get the JSON). You probably want to use mutableCopy instead of arrayWithArray.

But all this is rather moot: You don't need to fuss with this. When you convert your first JSON into an array leave it as an array (unless you need to change its contents). Then when you get your second json array, just assign your array pointer to point to it, rather than the original. Something like this:

@property(nonatomic, strong) NSArray * myCurrentJson;
self.myCurrentJson = [mydownloadedJSonString JSONvalue];

Then later in your code when you get a fresh JSON array, just set:

self.myCurrentJson = [myNewDownloadedJsonString JSONvalue];

This way myCurrentJson always points to a valid json block, and you don't have to fuss with mutable because those arrays don't change. When you replace the array, the old one doesn't have any references anymore so ARC will free it in memory. If you need to mutate the value, just do a mutableCopy on the original array, and make your changes in that one.

OTHER TIPS

responseObject is an NSArray (in your case, it could also be an NSDictionary, depending on the JSON response).

self.sshare.uData = responseObject;

assigns this (immutable) NSArray to self.sshare.uData, and it does not matter that the property was declared as NSMutableArray. Therefore calling removeAllObjects later on this objects crashes.

The same happens with

self.sshare.uData = [tmp copy];

because copy also returns an NSArray, even if called on an NSMutableArray.

Finally,

self.sshare.uData = [tmp mutableCopy];

actually stores a mutable array in the property. In this case it crashes because you don't call

[self.tableView reloadData]

to inform the table view that the data source has changed.


Actually you don't have to empty the array before starting the fetch. Just assign the new values and reload the table view when the completion block is called:

[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {

    self.sshare.uData = responseObject;
    // or, if you need a mutable array:
    // self.sshare.uData = [responseObject mutableCopy];
    [self.tableView reloadData];

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    DLog(@"Error: %@, responseString: %@", error, operation.responseString);
}];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top