How can I retain a local variable that is set in a block?

故事扮演 提交于 2020-01-05 10:09:39

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!