NSURLConnection (as part of AFNetworking) doesn't call NSURLConnectionDataDelegate delegate - ios

The Issue
I am using AFNetworking, which generates an NSURLConnection object, to upload a photo.
The NSURLConnection is not making any calls to the
- (void)connection:(NSURLConnection __unused *)connection
didSendBodyData:(NSInteger)bytesWritten
totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
method. (It's part of the NSURLConnectionDataDelegate protocol, defined in NSURLConnection.h)
The request is an HTTP POST request with a photo attachment (Content-Disposition "form-data", Mime type "image/jpeg"). This should (I think) result in periodic calls to this method as the upload progresses.
I need the method to display a progress bar to the user. However, the method is not getting called.
Note:
The delegate is set properly; other delegate methods are called
The file upload is successful
AFNetworking Code
I'm pretty sure this is an NSURLConnection issue, not an AFNetworking issue.
But just in case, here is the relevant code from my AFHTTPClient subclass:
NSMutableURLRequest *request = [self multipartFormRequestWithMethod:#"POST" path:path parameters:parameters constructingBodyWithBlock:constructor];
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
if (constructor) {
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(#"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];
}
[self enqueueHTTPRequestOperation:operation];
My AFHTTPClient subclass does NOT override ANY of the methods I am calling:
multipartFormRequestWithMethod: path: parameters: constructingBodyWithBlock:
HTTPRequestOperationWithRequest: success: failure:
setUploadProgressBlock:
enqueueHTTPRequestOperation:
constructor is a block with type (void (^)(id<AFMultipartFormData>)) and is not nil (verified by stepping through code).
When I type po operation.uploadProgress in the debugger, I see $0 = 0x1301d9f0 <__NSMallocBlock__: 0x1301d9f0> so I think the block is being set correctly.
The file always uploads successfully, but the code in the upload progress block is not run, so my progress bar never updates.
Questions
Why isn't NSURLConnection calling this method?
What are the circumstances in which NSURLConnection is supposed to? (It doesn't appear to be well-documented.)
And most importantly, how can I display the upload progress to the user?

A third-party error reporting library was overriding that delegate method on NSURLConnection. Removing the library resolved the issue. (There was no issue with either AFNetworking or NSURLConnection.)
If you can provide troubleshooting steps that would have isolated this issue faster, I will award the bounty to you. (Keep in mind that the third-party library is compiled, so a source code search wouldn't have worked.)

The library maybe implements a connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite: method in a category on NSObject. That would be very nasty but that could explain why the method of AFURLConnectionOperation is not called.
In order to check this assumption, you can run class-dump on your compiled binary. Search for didSendBodyData: in the output. If there’s a match in a category, the library is clearly responsible. The library could also be messing with the Objective-C runtime, but that would be harder to detect than just running class-dump.

Related

AWS SDK - Implementing a network queue for CloudFront downloads

I'm currently working on an iOS project that utilises the AWS SDK to download large media files to the device. I am using CloudFront to distribute the content and the downloads are working well, however I am having problems implementing a network queue for these operations. No matter what I try, all the files want to download at once.
I am using the AWSContent downloadWithDownloadType: method to initiate and monitor progress on the actual downloads.
I have tried using an NSOperationQueue and setting setMaxConcurrentOperationCount, and all the code blocks execute at once. :(
I have a feeling it might be configurable with AWSServiceConfiguration in the AppDelegate, but the documentation is extremely vague on what variables you can pass into that object... http://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSServiceConfiguration.html
Has anyone had any experience with this?
TIA
Your problem is most likely that you misunderstand an approach of asynchronous operations.
I have tried using an NSOperationQueue and setting
setMaxConcurrentOperationCount, and all the code blocks execute at
once. :(
It's difficult to say what's definitely wrong without seeing an actual code, however most likely it's tied to the following steps:
You create NSOperationQueue
You set maxConcurrentOperationsCount to 2 for example
You add 4 blocks to it with AWSContent downloadWithDownloadType:
You expect no more 2 downloads to be run simultaneously
What do you probably do wrong
The key is inside point 3. What exactly the block does? My guess is that it completes before actual download completes. So if you have something like:
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationsCount = 2;
for (AWSContent *content in contentArray) { // Assume you already do have this array
[queue addOperationWithBlock:^() {
[content downloadWithDownloadType:AWSContentDownloadTypeIfNotCached
pinOnCompletion:YES
progressBlock:nil
completionHandler:^(AWSContent *content, NSData *data, NSError *error) {
// do some stuff here on completion
}];
}];
}
Your block exits before your download is finished, allowing next blocks to run on queue and starting further downloads.
What to try
You should simply add some synchronization mechanism to your block to let operation complete only on completion block. Say:
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationsCount = 2;
for (AWSContent *content in contentArray) { // Assume you already do have this array
[queue addOperationWithBlock:^() {
dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
[content downloadWithDownloadType:AWSContentDownloadTypeIfNotCached
pinOnCompletion:YES
progressBlock:nil
completionHandler:^(AWSContent *content, NSData *data, NSError *error) {
// do some stuff here on completion
// ...
dispatch_semaphore_signal(dsema); // it's important to call this function in both error and success cases of download to free the block from queue
}];
dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER); // or another dispatch_time if you want your custom timeout instead of AWS
}];
}
Effectively your answer is https://stackoverflow.com/a/4326754/2392973
You just schedule plenty of such blocks to your operation queue.
More reading
https://developer.apple.com/reference/dispatch

Synchronous calls in an asynchronous environment

I am using AFNetworking 2.0 and Objective-C to create a class which synchronizes the client database with the database on the server.
Of course, the client shouldn't notice when this happens, so the class has to make the calls asynchronous.
However, some calls in this class are dependent on the results of others calls.
Example:
Object: Mammal, id = 15, new id = 26
Subobject: Zebra, clade_id = 15
The zebra can only update its properties once the mammals are done, because it uses some of the mammals properties (id) and if it sends the call too early it results in corrupted data (id = 15 instead of the correct 26).
My question is, how one could use AFNetworking to make these synchronous calls (zebra after mammal has finished) in a way that the user doesn't notice (asynchronous).
Thanks for your answers.
You not need to use a synchronous calls.
1) The first way
// create request with URL
NSMutableURLRequest *request = ....;
// create AFHTTPRequestOperation
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperationManager manager] HTTPRequestOperationWithRequest:request success:^{success block} failure:^{failure block}];
// Call operation
[self.operationQueue addOperation:operation];
In the success block you schedule second operation.
2) The second way
Create both operations at once and set the first operation how dependency for the second operation (but need the additional code for managing shared data).
[operation2 addDependency:operation1];
[self.operationQueue addOperation:operation1];
[self.operationQueue addOperation:operation2];

Recursive / Iterative NSURLSessionDataTask causing memory leak

I am having a problem with memory leaking in my code, I have a need to GET many URL's in quick succession, each GET is influenced by the result of the previous GET. The purpose is to look for a specific piece of content within the response.
I found the cleanest way to implement this is recursively, as I can use the same method to identify if the desired value is present in the response. Functionally it works very well, but it leaks memory as described below. I have also implemented the same functionality in an iterative fashion, and this also leaks memory.
To my mind it seems that the NSURLSession API is responsible for leaking this memory, and it only occurs when multiple calls are made in very quick succession. However, I would appreciate if anyone can point out any obvious mistakes I am making.
Update 10/09/14:
Updated to add a recursion counter, demonstrating the leak still occurs even if the code isn't executed an infinite number of times. Also tidied up the implementation slightly, re-using the NSURLSession and NSURLSessionConfiguration as properties within the view controller.
Sample Code:
- (void)performURLCallRecursive {
recursionLimiter++;
if (recursionLimiter > 10) {
[self.session finishTasksAndInvalidate];
return;
}
NSURL * checkURL = [NSURL URLWithString:#"http://www.google.com"];
__block NSMutableURLRequest * urlRequest = [[NSMutableURLRequest alloc] initWithURL:checkURL
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:0.0f];
__weak typeof(self) weakSelf = self;
NSURLSessionDataTask * task = [self.session dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError
*error) {
NSString * body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Body: %#", body);
[weakSelf performURLCallRecursive];
}];
[task resume];
}
#pragma mark - Getters
- (NSURLSessionConfiguration *)sessionConfiguration {
if (!_sessionConfiguration) {
_sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
[_sessionConfiguration setAllowsCellularAccess:NO];
[_sessionConfiguration setTimeoutIntervalForRequest:10.0f];
[_sessionConfiguration setTimeoutIntervalForResource:10.0f];
[_sessionConfiguration setURLCache:[[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil]];
}
return _sessionConfiguration;
}
- (NSURLSession *)session {
if (_session == nil) {
_session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration
delegate:[SPRSessionDelegate new]
delegateQueue:nil];
}
return _session;
}
The memory leaks as reported by instruments. (NB: These vary slightly every time, but for the most part contain the same leaks, just more or less of the same leaks):
Further Update:
So, I actually implemented the same code iteratively, and the memory leak still occurs. For this example I included a loop limiter so it doesn't execute for ever. Can anyone help me figure out what on earth is going on here?
- (void)performURLCallIterative
{
int loopLimiter = 0;
do {
NSURLSessionConfiguration * defaultSession = [NSURLSessionConfiguration defaultSessionConfiguration];
[defaultSession setAllowsCellularAccess:NO];
[defaultSession setTimeoutIntervalForRequest:10.0f];
[defaultSession setTimeoutIntervalForResource:10.0f];
NSURLSession * session = [NSURLSession sessionWithConfiguration:defaultSession
delegate:self
delegateQueue:nil];
NSURL * checkURL = [NSURL URLWithString:#"http://google.com"];
NSMutableURLRequest * urlRequest = [[NSMutableURLRequest alloc] initWithURL:checkURL
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:0.0f];
__weak NSURLSession * weakSession = session;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionDataTask * task = [session dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString * body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Body: %#", body);
dispatch_semaphore_signal(semaphore);
[weakSession invalidateAndCancel];
}];
[task resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
loopLimiter++;
} while (loopLimiter <= 6);
}
Update 10/09/14:
This is still occurring on iOS 8 for any Googlers who may have found their way here. As far as I am concerned this is a bug in iOS.
- Update 9/12/2014
Solution: wait for iOS8.
- Update 9/10/2014
Whoa, this is spiraling into some Nth dimension of complexity :P. I hope one way or another you get a break here quick.
I have a few other things for you to try.
1) Could you make sure NSZombies is turned off. In Xcode, Product->Scheme->Edit Scheme...->Enable Zombie Objects (NOT ticked).
2) Also try cachePolicy:NSURLCacheStorageNotAllowed for your NSMutableURLRequest.
3) Could you see if you are completing with an error? Just put this around your body string assignment...
if (error == nil)
{
//Enter data->string code here
}
4) Could you see if you are not getting status 200?
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
5) It is hard to picture exactly how your project is set up. I would have an NSObject type class that houses the NSURLSession methods, which is separate from the UIViewController class from which it is being called. The timer or whatever recursion method you wish to choose would then call the url session associated methods from the UIViewController.
- Update 9/9/2014
You are correct about my question (2). The data task is resumed before completion and after the data task completes the session is invalidated. I haven't seen it done this way, but it makes sense. Just tested on my end, no leaks with regards to [session invalidateAndCancel]...
Could you check that your completion handler executes? Perhaps it doesn't and the session is never cancelled before a new task is started?
I am noticing that there are a few references to HTTP Headers in the Instruments Leaks report, maybe if you are not specifying either a [urlRequest setHTTPMethod:#"GET"] the request is missing some basic headers?
(I'll edit after we find the solution, so this doesn't look like a discussion).
- Original 9/8/2014
Interesting question! I have troubleshot leaks associated with NSURLSessions. Definitely #autoreleasepool{} and others are good suggestions to try so far... But!
I am afraid the thing you asked us to look past might be the culprit here.
Just a few observations first:
1) It is not clear to me why you would need to __weak the self here. What is the retain cycle you are trying to avoid? Perhaps this is more clear in the code you are actually using aside from your "sample".
2) What is the reason for the call to invalidate the session before the data task associated with that session even has a chance to complete, let alone resume. The data task is in the suspended state until resumed.
3) If you are recursively running a method like this, then I think it is crucial to specify or at least consider what delegate queue, otherwise having it set to nil defaults it to serial operation queue. What happens when the delegate calls before the completion handler finishes, in an infinite loop - most likely a huge pile up.
--
I believe that the main issue here is that you are starting a new or canceling the NSURLSessionDataTask before it has a chance to complete. Look at +sesssionWithConfiguration:
(sorry can't include pictures yet, hopefully after this answer)
https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSession_class/Introduction/Introduction.html#//apple_ref/occ/clm/NSURLSession/sessionWithConfiguration:
The point is here...
Important
The session object keeps a strong reference to the delegate
until your app explicitly invalidates the session. If you do not
invalidate the session by calling the invalidateAndCancel or
resetWithCompletionHandler: method, your app leaks memory.
My suggestion to try is...
//Your code above...
[task resume];
[session finishTasksAndInvalidate];
}
In theory this should prevent any new sessions from starting before completion, according to the description, "...new tasks cannot be created in the session, but existing tasks continue until completion. After the last task finishes and the session makes the last delegate call, references to the delegate and callback objects are broken..."
I am still not sure about invalidating the session before resuming it.
I hope this helps. Good luck.
A developer support request to Apple reveals this to be a bug within iOS 7. There is no fault with the code sample posted above (Either recursively or iteratively) and it has reportedly been fixed in the iOS 8 GM release.
Update:
This is still occurring in iOS 8.1
I had a lot of problems with memory from NSURLSession and I finally fixed it by not using a new session for each request. Sessions are generally defined on Wikipedia as:
a semi-permanent interactive information interchange
As such, Apple's convenience class method [NSURLSession sharedSession] gives us a clue of how NSURLSession objects are intended to be used: as semi-permanent objects, not one-off objects created fresh for each request, like you are doing.
You are making a new session object per request for a ton of requests that, from the server's perspective, are all part of a single session with a single client.
I was doing the same thing until I realized this was the source of my woes. I did not find Apple's documentation on this very clear, but after I realized the error of my ways, it made certain things in the documentation suddenly make more sense, like why there is a sharedSession singleton convenience method of NSURLSession, why the word "tasks" is plural in finishTasksAndInvalidate, why they called it a "session", why it has a cache, etc. (If it was just for one request, why would it be a "session" and what good would a "cache" be?)
It helps to know how a browser like Safari looks at a session. A new session starts the first time you make a connection to a given server. Setting up the session involves creating a cache of SSL certificates, establishing authentication, handshaking, etc. It would be extraordinarily inefficient to do all of that every time some JavaScript on a page makes a new request to the same server, especially since modern web apps constantly make requests with callbacks etc. That is why a single session is established for a whole huge set of requests and responses -- a conversation, if you will, between the client and server. Eventually, a session expires, but usually this happens after several minutes, not after one request!
The point is, how you should be using NSURLSession objects is to make a singleton with a strongly referenced NSURLSession object as a property. Do this if you need to customize the session's configuration, (like turning caching off, etc.). However if you do not need to customize it, just use Apple's sharedSession.
If you use a singleton on a custom class, then, if you never need to set the session property to nil, then you never need to invalidateAndCancel or finishTasksAndInvalidate. Instead, just resetWithCompletionBlock or flushWithCompletionBlock to clear out connection caches periodically.
If you hate singletons you can still use a session as a property, just make sure to invalidateAndCancel or finishTasksAndInvalidate the session before its last owner gets deallocated by the ARC runtime.
Also note that setting your NSURLSession object's URLCache property to nil is the proper way to shut off caching. That's what Apple says they do for backgroundSessionConfiguration.
See my other answers on this topic here and here.
The only suggestions I have are perhaps using an #autoreleasepool{} and converting the __weak id self to __block id self. I don't think the __block vs. __weak will do anything differently, but give it a shot.
I'm not sure what one should expect with ARC when running something asynchronously AND recursively. Looking at other questions with asynchronous recursive calls and ARC, there isn't any consistent solution. Take a look here, for example.

Stopping an NSOperationQueue

I have an NSOperationQueue that handles importing data from a web server on a loop. It accomplishes this with the following design.
NSURLConnect is wrapped in an NSOperation and added to the Queue
On successful completion of the download (using a block), the data from the request is wrapped in another NSOperation that adds the relevant data to Core Data. This operation is added to the queue.
On successful completion (using another block), (and after a specified delay) I call the method that started it all and return to step 1. Thus, i make another server call x seconds later.
This works great. I'm able to get data from the server and handle everything on the background. And because these are just NSOperations I'm able to put everything in the background, and perform multiple requests at a time. This works really well.
The ONLY problem that I currently have is that I'm unable to successfully cancel the operations once they are going.
I've tried something like the following :
- (void)flushQueue
{
self.isFlushingQueue = YES;
[self.operationQueue cancelAllOperations];
[self.operationQueue waitUntilAllOperationsAreFinished];
self.isFlushingQueue = NO;
NSLog(#"successfully flushed Queue");
}
where self.isFlushingQueue is a BOOL that I use to check before adding any new operations to the queue. This seems like it should work, but in fact it does not. Any ideas on stopping my Frankenstein creation?
Edit (Solved problem, but from a different perspective)
I'm still baffled about why exactly I was unable to cancel these operations (i'd be happy to keep trying possible solutions), but I had a moment of insight on how to solve this problem in a slightly different way. Instead of dealing at all with canceling operations, and waiting til queue is finished, I decided to just have a data structure (NSMutableDictionary) that had a list of all active connections. Something like this :
self.activeConnections = [NSMutableDictionary dictionaryWithDictionary:#{
#"UpdateContacts": #YES,
#"UpdateGroups" : #YES}];
And then before I add any operation to the queue, I simply ask if that particular call is On or Off. I've tested this, and I successfully have finite control over each individual server request that I want to be looping. To turn everything off I can just set all connections to #NO.
There are a couple downsides to this solution (Have to manually manage an additional data structure, and every operation has to start again to see if it's on or off before it terminates).
Edit -- In pursuit of a more accurate solution
I stripped out all code that isn't relevant (notice there is no error handling). I posted two methods. The first is an example of how the request NSOperation is created, and the second is the convenience method for generating the completion block.
Note the completion block generator is called by dozens of different requests similar to the first method.
- (void)updateContactsWithOptions:(NSDictionary*)options
{
//Hard coded for ease of understanding
NSString *contactsURL = #"api/url";
NSDictionary *params = #{#"sortBy" : #"LastName"};
NSMutableURLRequest *request = [self createRequestUsingURLString:contactsURL andParameters:params];
ConnectionCompleteBlock processBlock = [self blockForImportingDataToEntity:#"Contact"
usingSelector:#selector(updateContactsWithOptions:)
withOptions:options andParsingSelector:#selector(requestUsesRowsFromData:)];
BBYConnectionOperation *op = [[BBYConnectionOperation alloc] initWithURLRequest:request
andDelegate:self
andCompletionBlock:processBlock];
//This used to check using self.isFlushingQueue
if ([[self.activeConnections objectForKey:#"UpdateContacts"] isEqualToNumber:#YES]){
[self.operationQueue addOperation:op];
}
}
- (ConnectionCompleteBlock) blockForImportingDataToEntity:(NSString*)entityName usingSelector:(SEL)loopSelector withOptions:(NSDictionary*)options andParsingSelector:(SEL)parseSelector
{
return ^(BOOL success, NSData *connectionData, NSError *error){
//Pull out variables from options
BOOL doesLoop = [[options valueForKey:#"doesLoop"] boolValue];
NSTimeInterval timeInterval = [[options valueForKey:#"interval"] integerValue];
//Data processed before importing to core data
NSData *dataToImport = [self performSelector:parseSelector withObject:connectionData];
BBYImportToCoreDataOperation *importOperation = [[BBYImportToCoreDataOperation alloc] initWithData:dataToImport
andContext:self.managedObjectContext
andNameOfEntityToImport:entityName];
[importOperation setCompletionBlock:^ (BOOL success, NSError *error){
if(success){
NSLog(#"Import %#s was successful",entityName);
if(doesLoop == YES){
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:loopSelector withObject:options afterDelay:timeInterval];
});
}
}
}];
[self.operationQueue addOperation:importOperation];
};
}
Cancellation of an NSOperation is just a request, a flag that is set in NSOperation. It's up to your NSOperation subclass to actually action that request and cancel it's work. You then need to ensure you have set the correct flags for isExecuting and isFinished etc. You will also need to do this in a KVO compliant manner. Only once these flags are set is the operation finished.
There is an example in the documentation Concurrency Programming Guide -> Configuring Operations for Concurrent Execution. Although I understand that this example may not correctly account for all multi-threaded edge cases. Another more complex example is provided in the sample code LinkedImageFetcher : QRunLoopOperation
If you think you are responding to the cancellation request correctly then you really need to post your NSOperation subclass code to examine the problem any further.
Instead of using your own flag for when it is ok to add more operations, you could try the
- (void)setSuspended:(BOOL)suspend
method on NSOperationQueue? And before adding a new operation, check if the queue is suspended with isSuspended?

AFNetworking: Access to completion handlers when retrying operation

To give some context: I'm trying to implement a global error handler for authentication errors (using token authentication, not basic), which should try to re-authenticate and then repeat the original failed request (see my previous question: AFNetworking: Handle error globally and repeat request)
The current approach is to register an observer for the AFNetworkingOperationDidFinishNotification which does the re-authentication and (if auth succeeded) repeats the original request:
- (void)operationDidFinish:(NSNotification *)notification
{
AFHTTPRequestOperation *operation = (AFHTTPRequestOperation *)[notification object];
if(![operation isKindOfClass:[AFHTTPRequestOperation class]]) {
return;
}
if(403 == [operation.response statusCode]) {
// try to re-authenticate and repeat the original request
[[UserManager sharedUserManager] authenticateWithCredentials...
success:^{
// repeat original request
// AFHTTPRequestOperation *newOperation = [operation copy]; // copies too much stuff, eg. response (although the docs suggest otherwise)
AFHTTPRequestOperation *newOperation = [[AFHTTPRequestOperation alloc] initWithRequest:operation.request];
// PROBLEM 1: newOperation has no completion blocks. How to use the original success/failure blocks here?
[self enqueueHTTPRequestOperation:newOperation];
}
failure:^(NSError *error) {
// PROBLEM 2: How to invoke failure block of original operation?
}
];
}
}
However, I stumbled upon some issues regarding completion blocks of request operations:
When repeating the original request, I obviously want its completion blocks to be executed. However, AFHTTPRequestOperation does not retain references to the passed success and failure blocks (see setCompletionBlockWithSuccess:failure:) and copying NSOperation's completionBlock is probably not a good idea, as the documentation for AFURLConnectionOperation states:
Operation copies do not include completionBlock. completionBlock often strongly captures a reference to self, which, perhaps surprisingly, would otherwise point to the original operation when copied.
In case the re-authentication fails, I want to call the original request's failure block. So, again, I'd need direct access to this.
Am I missing something here? Any ideas for alternative approaches? Should I file a feature request?
I've come up with this problem in Art.sy's portfolio app. My eventual conclusion was to create a NSOperationQueue subclass which had functions to create copies of various AFNetworking HTTP Operations once they failed (and to do this up to three times per URL before giving up.)
Did you try the following?
// set success / failure block of original operation
[newOperation setCompletionBlock:[operation.completionBlock copy]];
[operation setCompletionBlock:nil];
Note that if you capture self in the original completion/failure blocks (i.e. access any ivars) you actually access the original operation instance when executing the completion block of the newOperation. But this is what you want actually, right?
The notification handler is executed before the completion block of the operation.
So you should set the completion block of the original operation to nil, to prevent it from executing twice.
Note the completion block is set to nil after it has executed (see AFURLConnectionOperation).
In the authenticateWithCredentials failure block you should not do anything. The original operation has finished at that time and already has executed its failure block.

Resources