Kyle LeNeau
Kyle LeNeau

Reputation: 1038

AFNetworking getting response JSON from the wrong endpoint

I am seeing a really weird and random issue in my code that I can't track down. I am getting crashes in my data model init methods when returning from AFNetworking JSON request methods. When the app does crash I am able to step back in the call stack to debug the what the JSON request/response was. The weird part is when I check the URL, request, and resonseJSON. The responseJSON does not match the expected result of the URL/request. It's like I am getting some other API methods call and data. Because the data/JSON is not what I expect the app will crash on model init.

The data I get back is usually different and not always the same. Sometimes the data is from endpoint A and sometimes it is from B, it's never consistent. It does however seem to crash consistently in the same model object.

Request endpoint A data but I get back endpoint B data. When I debug the AFHttpOperation when it crashes I see this is the result. It's almost like 2 calls are getting crossed and is some type of race condition. Below is a sample of my model object, Rest client, and model access layer.

Model Object

@implementation Applications

- (id)initWithData:(NSDictionary *)appData forLocation:(Location *)location inCategory:(Category *)category {
    // appData is the JSON returned by The Rest Client and AFNetworking
    self = [super init];
    DDLogVerbose(@"appData = %@", appData);
    if (self) {
        _location = location;
        _listeners = [NSMutableArray mutableArrayUsingWeakReferences];
        _devices = [[NSMutableDictionary alloc] init];
        _category = category;
        _subscriptions = [Utility sanitizeArray:appData[@"Subscriptions"]];
    }
    return self;
}

@end

@implementation Location

- (void)refreshApplications {
    [[Model shared] appsForLocation:self 
                            success:^(NSObject *obj) {
        self.apps = nil; //we have to get our apps again
        self.apps = [NSMutableArray array];

        NSArray *newApps = (NSArray *) obj;
        for (NSDictionary *app in newApps) {
            **// This is where it's crashing!**
            Applications *newApp = [[Applications alloc] initWithData:app
                                                          forLocation:self
                                                           inCategory:[[SmartAppCategory alloc] init]];
            [self.apps addObject:newApp];
        }

        [self notifyListeners];
    }
                            error:nil];
}

@end

Rest Client

@interface Rest

+ (Rest *)sharedClient;
- (void)GET:(NSString *)path parameters:(NSDictionary *)params success:(SuccessCallback)sCallback error:(ErrorCallback)eCallback;

@end

@implementation Rest

+ (Rest *)sharedClient {
    static dispatch_once_t token;
    static Rest *shared = nil;
    dispatch_once(&token, ^{
        shared = [[Rest alloc] init];
    });
    return shared;
}

- (id)init {
    self = [super init];
    if (self) {
        [self createClients];
    }
    return self;
}

- (void)createClients {
    // Setup the Secure Client
    // Private implementation properties
    self.secureClient = [[AFOAuth2Client alloc] initWithBaseURL:baseUrl clientID:OAUTH2_CLIENT_ID secret:OAUTH2_CLIENT_SECRET];
    [self.secureClient setParameterEncoding:AFJSONParameterEncoding];
    AFOAuthCredential *credential = (AFOAuthCredential *) [NSKeyedUnarchiver unarchiveObjectWithData:[KeyChainStore dataForKey:KEYCHAIN_SETTINGS_AFOAuthCredential]];
    if (credential) {
        [self.secureClient setAuthorizationHeaderWithToken:credential.accessToken];
    }

    // Setup the Anonymous Client
    self.anonymousClient = [[AFHTTPClient alloc] initWithBaseURL:baseUrl];
    [self.anonymousClient setParameterEncoding:AFJSONParameterEncoding];
    [self.anonymousClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
}

- (void)GET:(NSString *)path parameters:(NSDictionary *)params success:(SuccessCallback)sCallback error:(ErrorCallback)eCallback {

    [_secureClient getPath:path
                parameters:params
                   success:^(AFHTTPRequestOperation *operation, id responseObject) {
                       DDLogVerbose(@"Success Path: %@ JSON: %@", path, responseObject);
                       if (sCallback) sCallback(responseObject);
                   }
                   failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                       [Rest callErrorBlock:eCallback withOperation:operation];
                   }];
}

@end

Model Access Layer

@interface Model

+ (Model *)shared;
- (void)appsForLocation:(Location *)location success:(SuccessCallback)success error:(ErrorCallback)error;

@end

@implementation Model

- (void)appsForLocation:(Location *)location success:(SuccessCallback)success error:(ErrorCallback)error {
    NSString *path = [NSString stringWithFormat:@"/api/locations/%@/apps/", location.locationId];

    [[Rest sharedClient] GET:path parameters:nil success:success error:error];
}

@end

A Location is a root object in the application and it will be told to refresh often. Either through UI interaction, events, or data Deserialization the the refreshApplications will execute to get more data from the server. Meanwhile other requests and events are going on in the application to get and send data to the API is JSON. Some of these GET calls to other endpoints seem to be messing with the response data.

Questions

  1. How could this be happening with AFNetworking?
  2. Am I being too quick to blame AFNetowrking and should I be looking for other places in my system that could be crossing the responses? I do have a load balanced backend hosted at Amazon.
  3. Is this an endpoint issue?
  4. How can I better debug and reproduce this issue? It only comes up at random times and is very hard to replicate. I have to continually run and re-run the application in hopes that it is crash.
  5. Are there any advanced debugging techniques that I can use to back trace this call/crash using xcode?

Upvotes: 2

Views: 800

Answers (2)

lukaswelte
lukaswelte

Reputation: 3001

To debug non-HTTPS as well as HTTPS Traffic use the mitmproxy

It allows you to inspect all packages and also resend them and much more. With this you can check what really happens and if the backend is the problem or if AFNetworking has a Bug.

And as a cool side effect mitmproxy is totally free and Open-Sourced under the MIT Licensed. On their website you will find some handy tutorials specific for iOS.

Upvotes: 1

Idles
Idles

Reputation: 1131

I recommend that you use Charles proxy to double-check that the data you're receiving is correct. There's a trial version available that works identically to the registered version for 30 days. My first guess is that there's either some sort of buggy cache layer between you and your server, or your server is buggy. An HTTP proxy like Charles will allow you to confirm or reject this hypothesis.

This page explains how to set up Charles to proxy non-HTTPS connections from iOS devices.

Upvotes: 4

Related Questions