2

I've been working on the same project for some time now, and my understanding of Objective-C and Cocoa has evolved a little through time. Looking back in some parts of my code, I see this:

__weak ASIFormDataRequest *serverQueueRequest = [ASIFormDataRequest requestWithURL:url2];
[serverQueueRequest setCompletionBlock:^{
    NSLog(@"%@", serverQueueRequest.responseString);
}];
[serverQueueRequest startAsynchronous]; 

And that's how I've been processing all my server requests. I think I did that to suppress a warning, namely that "capturing request in block may lead to a retain cycle". So I made it weak, and that seems to have solved all my issues. I haven't noticed any real problems with this.

However, looking at the code now, it seems a little odd that it works. When I declare the request as __weak, shouldn't it immediately zero out since no one is holding on to it? Why does this code work?

Also, although this code works, I just recently discovered a case where it doesn't: when calling the method that contains this code several times in succession, say 5 times within the span of a second, 3/5 requests will have a NULL response. This is consistently the case. Removing the __weak qualifier solves this issue. What's the explanation for that?

And so finally, what is the correct way to declare a local request like this?

Update: According to this question, the correct way to do it is like so:

ASIHTTPRequest *_request = [[ASIHTTPRequest alloc] initWithURL:...
__weak ASIHTTPRequest *request = _request;

Edit: actually the fix above does not fix the issue where calling the code 5 times results in NULL responses. That problem still exists. The only way that problem goes away is by capturing the request strongly and not using any qualifiers.

Now the question is why my original code still worked..

Community
  • 1
  • 1
Snowman
  • 29,431
  • 43
  • 165
  • 290

3 Answers3

3

Quoting from Apple's Programming with ARC Release Notes:

Take care when using __weak variables on the stack. Consider the following example:

NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]]; 
NSLog(@"string:%@", string); 

Although string is used after the initial assignment, there is no other strong reference to the string object at the time of assignment; it is therefore immediately deallocated. The log statement shows that string has a null value.

David H
  • 39,114
  • 12
  • 86
  • 125
  • Right, but what I'm saying is that doing the above actually works most of the time, except in the special case where I run the code many times very very fast. Otherwise, with typical usage, the request goes through and I get a response. – Snowman Sep 05 '12 at 21:33
  • Is ASIHTTPRequest using ARC or not? If not it may be in an autorelease pool. The fact that it doesn't seem to get released immediately in this particular case doesn't prove a rule. – David H Sep 06 '12 at 00:58
  • ASIHTTPRequest does not use ARC. Wouldn't putting it the block hold it's memory location temporarily as it executes? – Ben Coffman Sep 06 '12 at 16:50
  • My point is that the reason you don't see the weak variable go to nil immediately is that the object that method returns is autoreleased. Ah, yes, well, if you wrap that call in an autorelease pool, guess you can test if the variable goes to nil outside the pool's scope. Good idea! [Note: declare the variable OUTSIDE the scope of the pool, make the call inside the pool, log the variable below the pool.] – David H Sep 06 '12 at 17:24
  • This var exactly what happened to me after converting an app to ARC. I fixed it with the following code: `ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; __weak ASIHTTPRequest *weakRequestRef = request; [request setPostValue:data forKey:@"data"]; // Request completion - notify subcriber [request setCompletionBlock:^{ [self.delegate requestCompleted:[weakRequestRef responseString]]; }]; – Peter Boné Oct 22 '13 at 09:19
  • @PeterBoné, having a dispatch block do the retain is a little obtuse and you may have to ponder how the code works later. If you can use an ivar its much clearer what your intent is, you can test the value for nil, etc. YMMV. – David H Oct 22 '13 at 11:57
0

I believe it is because you are running the weak variable in a block. The block holds onto the state of the weak variable which In turn causes it to work. I'm betting much work on the variable once the block is complete may cause issues.

My guess in why it fails if you run it many times is because the asynchronous asi call stack gets to high and bombs. I've seen this before and if you are very patient you can catch the asi blow up in the debugger.

Ben Coffman
  • 1,710
  • 1
  • 18
  • 26
0

The obj C stack will always retain pointers in scope. _weak does not mean release right now it means release when the stack goes out of scope.

When you declare a var and then make calls on it in the same stack scope it will not be released until after (minimally) the stack is cleaned up.

Blocks extend method scope since they imply potentially asynchronous behavior and they utilize the stack that was present when they were invoked.

deleted_user
  • 3,745
  • 1
  • 16
  • 26