Retaining self with nested blocks? - ios

With the following code:
#interface MyClass()
{
NSMutableArray * dataArray;
}
#end
#implementation MyClass
- (void) doSomething
{
__typeof__(self) __weak wself = self;
dispatch_async(dispatch_get_global_queue(0,0), ^{
__typeof__(self) sself = wself;
[sself->dataArray addObject: #"Hello World"];
dispatch_async(dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval: 30];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: sself->dataArray[0]
message: #"Message"
delegate: nil
cancelButtonTitle: #"OK"
otherButtonTitles: nil];
[alert show];
});
});
}
#end
Is this the proper way to access sself from within the main queue block?
Would sself go out of scope once the initial queue finished?
Should I add a second __typeof__(self) sself = wself; inside the main queue block?

Yes, sure it is.
No, you'll be able to use in main_queue block.
No, you don't have to. You don't have to add __typeof__(self) sself = wself; even in global_queue block; it's not necessary, you already have a weakened self object wself (moreover, you will retain self in __typeof__(self) part inside the block).
You don't have to use __typeof__. Use simply typeof(self)

You ask:
Is this the proper way to access sself from within the main queue block?
Almost. You also should be checking to make sure sself is not nil, too. You do this because dereferencing a ivar for an nil pointer may crash your app. Thus, check to make sure it's not nil:
- (void) doSomething
{
typeof(self) __weak wself = self;
dispatch_async(dispatch_get_global_queue(0,0), ^{
typeof(self) sself = wself;
if (sself) {
[sself->dataArray addObject: #"Hello World"];
dispatch_async(dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval: 30];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: sself->dataArray[0]
message: #"Message"
delegate: nil
cancelButtonTitle: #"OK"
otherButtonTitles: nil];
[alert show];
});
}
});
}
You then ask:
Would sself go out of scope once the initial queue finished?
It goes out of scope, but is retained by the inner block and will be retained until that inner block finishes.
Should I add a second __typeof__(self) sself = wself; inside the main queue block?
You can if you want to, but it's not necessary. It depends upon the desired behavior. If, for example, you want to see this alert even if the current object (presumably a view controller) is dismissed, then use sself pattern (because you presumably want to hang on to it so you have access to dataArray). If you don't want to show the alert anymore, then you'd repeat this weakSelf/strongSelf dance.

Related

break retain cycle in a block nested in another block

Sometimes I use a block nested in another block, here is my code
- (void)test {
__weak typeof(self) weakSelf = self;
[self.viewSource fetchData:^(BOOL succeed, NSError * _Nonnull error, id _Nonnull data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.dataSource disposalData:^{
// here is the strongSelf ok? do I have to do something to avoid retain cycle?
[strongSelf updateUI];
}];
}];
}
- (void)updateUI {
}
I doubt the inner block still has a retain cycle?
[strongSelf.dataSource disposalData:^{
[strongSelf updateUI];
}];
my question is what is the correct way to break the retain cycle in such situation?
here is the additional discussion, as many friend mentioned about this, if I remove __strong typeof(weakSelf) strongSelf = weakSelf;, the inner block has no retain cycle? Is it perfectly correct?
- (void)test {
__weak typeof(self) weakSelf = self;
[self.viewSource fetchData:^(BOOL succeed, NSError * _Nonnull error, id _Nonnull data) {
[weakSelf.dataSource disposalData:^{
[weakSelf updateUI];
}];
}];
}
- (void)updateUI {
}
I think you can just create new strong reference inside nested block, like this:
- (void)test {
__weak typeof(self) weakSelf = self;
[self.viewSource fetchData:^(BOOL succeed, NSError * _Nonnull error, id _Nonnull data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.dataSource disposalData:^{
__strong typeof(weakSelf) strongSelf = weakSelf; // <- new strong ref
[strongSelf updateUI];
}];
}];
}
It will override the first strongSelf in the nested block scope. And it will be only alive during the execution of the nested block without strong reference cycle created. I think so =)

dispatch_async Nested Block

I am using dispatch_async method to execute task in main queue. But it causing retain cycle:
Following is the code snippet:
self.test = ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", self);
});
};
I am not able to get properly why it is creating retain cycle. As My controller does not have ownership of dispatch_async block.
Try using weakSelf:
__weak typeof(self) weakSelf = self;
self.test = ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", weakSelf);
});
};
If you don't want the outer block to retain self, but want the inner block to be able to keep self alive once dispatched, maybe something like this:
typeof(self) __weak weakSelf = self;
self.test = ^{
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", strongSelf);
});
}
};

NSoperation - string not visible outside the block

I am currently working on a project and using tesseract API .
The code is following :
UIImage *bwImage = [image g8_blackAndWhite];
[self.activityIndicator startAnimating];
// Display the preprocessed image to be recognized in the view
self.imageView.image = bwImage;
G8RecognitionOperation *operation = [[G8RecognitionOperation alloc] init];
operation.tesseract.engineMode = G8OCREngineModeTesseractOnly;
operation.tesseract.pageSegmentationMode = G8PageSegmentationModeAutoOnly;
operation.delegate = self;
operation.recognitionCompleteBlock = ^(G8Tesseract *tesseract) {
// Fetch the recognized text
NSString *recognizedText = tesseract.recognizedText;
NSLog(#"%#", recognizedText);
[self.activityIndicator stopAnimating];
// Spawn an alert with the recognized text
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"OCR Result"
message:recognizedText
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
};
//NSLog(#"%#",);
// Finally, add the recognition operation to the queue
[self.operationQueue addOperation:operation];
}
I want to pass recognizedText string to second View controller but it is not visible outside the block.
How can I achieve this, any advice?
Declare recognizedText outside block with __block keyword to make it visible outside block.
Like below code:
......
__block NSString *recognizedText;//declared outside to make it visible outside block
operation.recognitionCompleteBlock = ^(G8Tesseract *tesseract) {
// Fetch the recognized text
recognizedText = tesseract.recognizedText;
NSLog(#"%#", recognizedText);
[self.activityIndicator stopAnimating];
// Spawn an alert with the recognized text
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"OCR Result"
message:recognizedText
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
};
......

ios recursive call with block leaks memory

I am calling a function recursively from completion block which increases the memory foot print, this most probably is leading to a block retain cycle,following is the code:
- (void)tick {
if (counter > counterLimit) {
[self finish];
return;
}
counter++;
context = [[Context alloc] init];
//this is executed in another thread
[self.executer computeWithContext:(Context*)context completion:^(NSDictionary *dictionary, Context *context_)
{
[self handleResponse];
[self tick];
}];
}
self is owning executer, executer is owning the block, the block captures self strongly (same effect as owning). you have a retain cycle.
create a weak reference for self and use that in the block exclusively.
- (void)tick {
// ...
__weak typeof(self) weakSelf = self;
[self.executer computeWithContext:(Context*)context completion:^(NSDictionary *dictionary, Context *context_)
{
typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf) { // ensure the receiver is still there to receive
[strongSelf handleResponse];
[strongSelf tick];
}
}];
}

Cant dismiss UIAlertView, buttons not clickable

I have the following code where i show a MBProgress view and then run code in a separate thread. I then get a handle to the main thread and dismiss the spinner which works and then i show a UIAlertView. The UIAlertView loads fine however i can not click any of the buttons. If the alert view is outside of the dispatch block it works fine. Any ideas?
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
GamePlayManager *gameManager = [GamePlayManager alloc];
Session *sess = [Session sharedInstance];
//Add the last actor to the end of the list
NSMutableDictionary *connections = sess.connections;
[connections setObject:sess.secondActor forKey:[NSString stringWithFormat:#"%d",kLastFieldtag]];
BOOL result = [gameManager areAnswersCorrect:sess.connections startingActor:sess.firstActor endingActor:sess.secondActor];
NSString *display = #"Sorry incorrect. Please recheck your answers.";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Result"
message:display
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK", nil];
if (result)
{
display = #"You are correct! You Won!";
if (sess.isMutiplayerGame)
{
[_gameCenterController endGame];
[self showGameOverScreen:YES isMultiplayer:YES];
}
else
{
[self showGameOverScreen:YES isMultiplayer:NO];
}
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
[alert show];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
[alert show];
});
}
});
This is likely an issue caused by a clash between the MBProgressHUD's animation and the UIAlertView's animation.
I've never used MBProgressHUD, but looking at the code on GitHub it seems they've already solved your problem. MBProgressHUD has a completionBlock property.
Code like this should work: (Warning: Untested)
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD HUDForView:self.view].completionBlock = ^{
[alert show];
};
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
MBProgressHUD fires its completionBlock after the view has finished its animation, so there should no longer be a conflict.
As a side note the MBProgressHUD method:
- (void)showAnimated:(BOOL)animated
whileExecutingBlock:(dispatch_block_t)block
onQueue:(dispatch_queue_t)queue
completionBlock:(MBProgressHUDCompletionBlock)completion;
seems like it would be a better fit for your code.
Declare the alert view outside the threads with block:
__block UIAlertView *alert;

Resources