问题
So I have:
@interface testAppControl : NSObject
{
NSString *s;
}
and then in my block I want to do
[SendAPI setGroupWithName:groupName completionHandler:^(NSArray *errors) {
s = @"something";
}];
NSLog(@"%@", s); //<--- s is null
So I want to keep the value "something"
after I leave my block. Is this possible with ARC?
回答1:
Declare it as a __block
variable, then you can use it outside the block and alter it inside the block.
__block NSString *s = nil;
void(^block)(void) = ^ {
s = @"something";
};
block();
NSLog(@"%@", s);
s = @"blah";
NSLog(@"%@", s);
block();
NSLog(@"%@", s);
回答2:
The very presence of completionHandler
might lead one to logically infer that setGroupWithName
is an asynchronous method. This a common programming pattern in iOS development: Rather than perform some potentially time consuming process in the foreground (during which the user interface would be frozen), perform it asynchronously in the background, but pass it a block, the completionHandler
in this case, so you can identify what should take place when the asynchronous process is complete.
In this case, it looks like you're invoking setGroupWithName
, on some background thread/queue with the understanding that while that's happening, you'll continue on the main thread (in your case, at that NSLog
statement), ensuring that your app stays responsive while that slow operation is being completed. But when that asynchronous background operation of setGroupWithName
is done, it will perform the block of code represented by the completionHandler
(in your case, the setting of s
to @"something"
).
If you put a NSLog
statement inside the completionHandler
block, where you're setting s
to @"something"
, and you'll probably see that it is happening well after the existing NSLog
statement you have after the invocation of setGroupWithName
.
To illustrate the example, here is an example of a standard Cocoa method that has a completionHandler
parameter, namely the NSURLConnection
class method, sendAsynchronousRequest.
The question is how to perform a slow operation, but still have a user interface that is responsive without waiting for the slow Internet operation.
So, consider the following code:
- (void)performSearch:(NSString *)term
{
NSLog(@"%s start: array has %d items", __FUNCTION__, [self.array count]);
// prepare to do search (the details here are not relevant)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://search.twitter.com/search.json?q=%@", term]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// submit an Internet search that will be performed in the background
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// in the background, when the Twitter search is done,
// and when it's done, we'll make an array of the results
self.array = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%s: after background query, array now has %d items", __FUNCTION__, [self.array count]);
}];
// in the meantime, let's immediately carry on while that search is taking place
NSLog(@"%s end: array still has %d items", __FUNCTION__, [self.array count]);
}
The details of this code aren't very important, but the basic idea is that it's doing something slow (performing a Twitter search on the Internet), but it will immediately continue running code in the main thread, letting the background thread carry on at its own pace.
The timestamps of the various NSLog
statements is illustrate the timing of all of this:
2013-05-02 20:42:58.922 myapp[81642:c07] -[ViewController performSearch:] start: array has 0 items 2013-05-02 20:42:58.923 myapp[81642:c07] -[ViewController performSearch:] end: array still has 0 items 2013-05-02 20:42:59.798 myapp[81642:1303] __32-[ViewController performSearch:]_block_invoke: after background query, array now has 11 items
Note that the time elapsed between the "start" and the "end" is roughly 1 millisecond, but the completion of the Twitter search in the background took almost a full second to complete. The design principle is that if we didn't do this asynchronously, using the completionHandler
in this case, the app's user interface would have been frozen for that one second. But by using asynchronous programming techniques, the app remained nice and responsive, but rather performed the slow operation in the background.
I'm not familiar with your setGroupWithName
method, but it is presumably performing the same sort of asynchronous operation. Thus your attempt to look at the value immediately afterword will not reflect the value that will change once that method is done (kind of like the fact that the "end" NSLog statement in my example doesn't reflect the change that will take place one second later).
回答3:
Is not totally clear why and what you want to do so I give you 3 options:
- Declare the variable
s
outside the block, it will be copied inside automatically inside the block - If you need to change its value and read it declare it outside the block with the
__block
specifier, pay attention that if you are changing that var in an async process, the value after the block will be most probably useless - If you want to keep and change its value during each call to the
block decalre it inside the block as
static
put it will be "visible" only inside the block
回答4:
It seem as you are performing an asynchronous operation and when the completion block is called you have already left the function/scope where s
was defined.
consider declaring s
in a "global" scope (like a class variable or property) then as long as your class instance is alive, you will be able to read the results set by the block.
I would recommend using a property, and capturing a "weak self" pointer in the block, in order to avoid bad memory access in case that the instance holding s
was deallocated.
来源:https://stackoverflow.com/questions/16347482/how-can-i-retain-a-local-variable-that-is-set-in-a-block