I have an iOS application which is using an NSOperationQueue, NSOperations and AFNetworking 2.1.0 to fire off requests to a server. The -[NSOperation main] method looks something like:

- (void)main {
  AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager sharedSessionManager];
  [sessionManager GET:@"url"
           parameters:nil
              success:^(NSURLSessionDataTask *task, id responseObject) {
                NSLog(@"Success");
              }
              failure:^(NSURLSessionDataTask *task, NSError *error) {
                NSLog(@"Failure");
              }
  ];
}

I have noticed that, from time to time, that the callbacks for a particular operation never get executed, when multiple operations are created and added to the NSOperationQueue in quick succession. I dove into AFNetworking to try to figure out why. I ended up in -[AFURLSessionManager dataTaskWithRequest:completionHandler], which looks like:

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                            completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];

    AFURLSessionManagerTaskDelegate *delegate = [AFURLSessionManagerTaskDelegate delegateForManager:self completionHandler:completionHandler];
    [self setDelegate:delegate forTask:dataTask];

    return dataTask;
}

I added a logging statement right after dataTask is created:

NSLog(@"Task with id %@ created for %@ on queue %@", @(dataTask.taskIdentifier), request.URL.path, dispatch_get_current_queue());

The log reveals the problem:

2014-02-26 14:11:25.071 App[50094:6a2f] Task with id 15 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:25.071 App[50094:460f] Task with id 16 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>

2014-02-26 14:11:26.274 App[50094:6a2f] Task with id 18 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:26.274 App[50094:6c17] Task with id 17 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>

2014-02-26 14:11:27.546 App[50094:6307] Task with id 20 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:27.546 App[50094:6b17] Task with id 19 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>

2014-02-26 14:11:28.705 App[50094:6b17] Task with id 21 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:28.705 App[50094:6307] Task with id 21 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>

2014-02-26 14:11:32.091 App[50094:6307] Task with id 22 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:32.091 App[50094:6b17] Task with id 23 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>

Notice the fourth set in the log has the same taskIdentifier which is what AFNetworking uses to associate tasks with their callbacks, via delegate.

If I force the NSOperations to run on the main queue, then I am unable to recreate the issue - the taskIdentifier is always unique.

Has anyone seen anything like this before? Do I need to ensure that -[NSURLSession dataTaskWithRequest:] runs only on the main thread in order to not get taskIdentifier collisions?

有帮助吗?

解决方案

Don't know if this is still relevant to you or not, but this exact thing had me banging my head around all night. Turns out you are partially answering the problem in your question.

As it would turn out, if

NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];

is run asynchronously, then there is the small chance that the instance of NSURLSession will assign the same taskIdentifier to different tasks.

Without forking AFNetworking and synchronizing on all of the dataTaskWithRequest: methods then there were two ways that I could go about fixing this that stood out.

If you don't need the NSURLSessionTask returning from this method then the best way would be to make your own dispatch queue and any requests you want to make with your session manager you just send it to that queue asynchronously like so:

static dispatch_queue_t my_queue;
my_queue = dispatch_queue_create("MyQueueName", DISPATCH_QUEUE_CONCURRENT);

// ... Later, that very same day

dispatch_async(my_queue, ^{
    // [sessionManager GET: ...
});

The only problem with this method for your issue however was that it all seemed to be executed on the same operation queue, so maybe this way wouldn't work in your case. The other way (which is how I did it) is actually waay simpler. Just synchronize on the AFURLSessionManager so the invocation of dataTaskWithRequest: can only ever happen synchronously like so:

@synchronized(sessionManager) {
    // [sessionManager GET: ... 
}

Or, yes, you could just do the task creations on the main thread. But for some projects its not always as simple as that.

其他提示

I would enqueue a NSURLSessionDataTask to your AFURLSessionManager instead of trying to directly make GET requests. The session manager is intended to abstract the functionality of NSURLSession.

Example:

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    NSURL *URL = [NSURL URLWithString:@"http://example.com/testapi.php"];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
        if (error) {
            NSLog(@"error!");
        } else {
            NSLog(@"task successful!");
        }
    }];
    [dataTask resume];
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top