-2

I created a simple HttpClient with AFNetworking.

HttpClient *client = [[HttpClient alloc] initWithTarget:(id)target
                                                 before:(SEL)before
                                                success:(SEL)success
                                                failure:(SEL)failure
                                                timeout:(SEL)timeout]

So the controller can register callback function when it perfroms the HTTP request. And here is how I wrote the callback functions:

- (void)successMethod:(id)response {
  //  LogDebug(@"Success: %@", response);
  self.lock = false;
  if (self.target == nil || self.successCallback == nil) {
    return;
  }
  if ([self.target respondsToSelector:self.successCallback]) {
    [self.target performSelector:self.successCallback withObject:response];
  }
}

But here I found a problem. When the request comes back fast, it works fine. But if the request comes back after a very longtime and meanwhile, user changes the view. Then it crashes the app and throw an exception like, selector can not be performed on a nil object.

So what I want know whether I did it in the right way and is there any way to solve this problem? What is the best practices to implement this?

=====

Update:

Sorry I didn't put the error log. But actually there is no much information I can get. Sometime there is even no log. I put a screenshot here and hope it can help.

I only get this and there is no exception log.

enter image description here

==== Update

And hope this can help

#import "HttpClient.h"
#import "AFNetworking.h"
#import "Logging.h"

@interface HttpClient ()

@property int retryCounter;
@property(nonatomic) NSString *action;
@property(nonatomic) NSString *url;
@property(nonatomic) NSDictionary *param;
@property(nonatomic) AFHTTPRequestOperationManager *manager;

@end

@implementation HttpClient

- (id)initWithTarget:(id)target
              before:(SEL)before
             success:(SEL)success
             failure:(SEL)failure
             timeout:(SEL)timeout {
  self = [super init];
  if (self) {
    self.target = target;
    self.before = before;
    self.success = success;
    self.failure = failure;
    self.timeout = timeout;
    self.lock = false;
    self.retryMaxCounter = 2;
    self.retryCounter = 0;

    self.manager = [AFHTTPRequestOperationManager manager];
    self.manager.requestSerializer = [AFJSONRequestSerializer serializer];
    self.manager.responseSerializer = [AFJSONResponseSerializer serializer];
  }
  return self;
}

- (void)request:(NSString *)action
            url:(NSString *)url
          param:(NSDictionary *)param {
  self.action = action;
  self.url = url;
  self.param = param;
  [self beforeMethod];
  [self request];
}

- (void)request {
  if (self.lock) {
    return;
  }
  if ([[self.action lowercaseString] isEqual:@"get"]) {
    // Get
    LogDebug(@"Send GET request.");
    self.lock = true;
    LogInfo(@"%@\n%@", self.url, self.param);
    [self.manager GET:self.url
        parameters:self.param
        success:^(AFHTTPRequestOperation *operation, id responseObject) {
          [self successMethod:responseObject];
        }
        failure:^(AFHTTPRequestOperation operation, NSError error) {
          if ([operation.response statusCode] == 500) {
            [self failureMethod:operation.responseObject];
          } else {
            [self timeoutMethod];
          }
        }];
  } else if ([[self.action lowercaseString] isEqual:@"post"]) {
    // POST
    LogDebug(@"Send POST request.");
    self.lock = true;
    LogInfo(@"%@\n%@", self.url, self.param);
    [self.manager POST:self.url
        parameters:self.param
        success:^(AFHTTPRequestOperation *operation, id responseObject) {
          [self successMethod:responseObject];
        }
        failure:^(AFHTTPRequestOperation operation, NSError error) {
          if ([operation.response statusCode] == 500) {
            [self failureMethod:operation.responseObject];
          } else {
            [self timeoutMethod];
          }
        }];
  } else {
    LogError(@"Not supported request method.");
  }
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

- (void)beforeMethod {
  LogDebug(@"Before requesting.");
  if (self.target == nil || self.before == nil) {
    return;
  }
  if ([self.target respondsToSelector:self.before]) {
    [self.target performSelector:self.before];
  }
}

- (void)successMethod:(id)success {
  //  LogDebug(@"Success: %@", success);
  self.lock = false;
  if (self.target == nil || self.success == nil) {
    return;
  }
  if ([self.target respondsToSelector:self.success]) {
    [self.target performSelector:self.success withObject:success];
  }
}

- (void)failureMethod:(id)failure {
  LogDebug(@"Failure: %@", failure);
  self.lock = false;
  if (self.target == nil || self.failure == nil) {
    return;
  }
  if ([self.target respondsToSelector:self.failure]) {
    [self.target performSelector:self.failure withObject:failure];
  }
}

- (void)timeoutMethod {
  LogError(@"Request timeout.");
  self.lock = false;
  self.retryCounter++;
  if (self.retryCounter < self.retryMaxCounter) {
    [self request];
  } else {
    if (self.target == nil || self.timeout == nil) {
      return;
    }
    if ([self.target respondsToSelector:self.timeout]) {
      [self.target performSelector:self.timeout];
    }
  }
}

#pragma clang diagnostic pop

@end

Thank you all very much!

==== Update

I found the problem. It was because I set the targe to assign instead of weak. For the difference between assign and weak, please refer to stackoverflow question

Community
  • 1
  • 1
Ciel
  • 5,551
  • 5
  • 17
  • 24
  • I'm guessing you are running an asynchronous request? – gikygik Apr 29 '15 at 17:16
  • @gikygik Yes sure. Some view requires the response come back first. So all view drawing functions are put into the success callback. And others are for error handling. But if I change view before the request come back, it will crash. – Ciel Apr 29 '15 at 17:18
  • I assume successCallback property is assign am I right? – Julian Apr 29 '15 at 17:24
  • ok i'm just trying to understand some other things so I can help. What happens if you just let it sit forever and it times out with out switching screens? Do you still get a crash or does it say timed out? – gikygik Apr 29 '15 at 17:25
  • What is the exact error being thrown? – Brian Nickel Apr 29 '15 at 17:25
  • And can you show use the property definitions for target and successCallback? – Brian Nickel Apr 29 '15 at 17:26
  • Brian is right can you give us the exact error? – gikygik Apr 29 '15 at 17:30
  • @BrianNickel Hi, I put the screenshot and the code. It doesn't give me very useful logs so I don't know, I just screenshot the screen when the app crashes. Is there anything wrong with my code? Thank you very much! – Ciel Apr 30 '15 at 03:38

2 Answers2

0

There is not enough information about the problem. But I can guess about the possible reason.

I guess you add a target for the first request, it works okay, but when you make the second request, there are two targets to perform the selector. But first object is already released so it can't perform selector.

You have to remove target that already not in use.

Alexander Perechnev
  • 2,797
  • 3
  • 21
  • 35
  • But I actually add if statement to check whether the target and selector are nil or not. Also I checked whether the target can perfrom the selector or not. Then why will this happen? – Ciel Apr 30 '15 at 03:32
0

You need to use a weak self reference for your target. I'm not sure what your implementation looks like but this might give you a hint:

__weak typeof(self) weakSelf = self;
HttpClient *client = [[HttpClient alloc] initWithTarget:weakSelf
                                                 before:(SEL)before
                                                success:(SEL)success
                                                failure:(SEL)failure
                                                timeout:(SEL)timeout]

Another, better IMO, way is to use blocks for the AFNetworking completions.

Nicolas S
  • 5,325
  • 3
  • 29
  • 36