Protecting my code from zombies from completion blocks

I am familiar with the delegation pattern and populating my delegates, especially when making asynchronous calls that still occur when my view controllers disappear. I passed the delegate and the callback successfully returns the nil object.

Now I'm experimenting using completion blocks to make my code a little easier to read.

I call the network service from my view controller and pass in a block that updates my UITableView. Under normal conditions, it works fine. However, if I leave the view before its completion, the completion handler block is executed, but the UITableView is now a zombie.

What is the usual pattern for this?

UPDATE WITH EXAMPLE CODE

This application is for iPad, I simultaneously have two view controllers, for example, with a split view. One of them is a detail, and the other is a grid of images. I click on the image and it reports the details to download the information. However, if I click on the images too quickly before they have a chance to make a network call - I have problems. When changing images, the code below is called, which counts the selected images ....

So, here is my dilemma, if I use the code below - it works fine, but it flows in the tools if you switch images before the network responds.

If I remove __block and go into myself, it will work with zombies.

I canโ€™t win ... Iโ€™m sure that I am missing something fundamental in using blocks.

__block UITableView *theTable = [self.table retain]; __block IndexedDictionary *tableData = [self.descriptionKeyValues retain]; FavouritesController *favourites = [Container controllerWithClass:FavouritesController.class]; [favourites countFavouritesForPhoto:self.photo completion:^(int favesCount) { [tableData insertObject:[NSString stringWithFormat:@"%i", favesCount] forKey:@"Favourites:" atIndex:1]; [theTable reloadData]; [tableData release]; [theTable release]; }]; 

Any tips? Thanks

SECOND UPDATE

I changed the way you download favorites. Instead of being single favorites, I create a copy every time I change the photo. Replacing this and killing the old one โ€” the block has nowhere to call back (I think it doesn't even exist), and now my code looks like the one shown below, and it seems to work:

 [self.favourites countFavouritesForPhoto:self.photo completion:^(int favesCount) { [self.descriptionKeyValues insertObject:[NSString stringWithFormat:@"%i", favesCount] forKey:@"Favourites:" atIndex:1]; [self.table reloadData]; }]; 

It does not flow and does not seem to crash either.

+4
source share
3 answers

I recommend that you check that the tableview is not zero at the beginning of the block. It looks like the table view is properly discarded when its parent view goes off the screen, so the table operations are not valid after this point.

Keeping a UITableView in a block is a bad idea, since updates to the datasource / tableview can lead to implicit method calls and notifications that won't matter if the table view is not on the screen.

0
source

The block will save any object that it refers to, except those that were annotated with __block . If you want to not execute completion blocks at all, just enter some property of type isCancelled and check if it is YES before calling the completion block.

0
source

So, you have a background operation that should call another object after its completion, and the object can be destroyed in the meantime. The crashes you describe occur when you have unsaved links. The problem is that the mentioned object goes away and the pointer is invalid. Usually what you do is unregister inside the dealloc method so that the background task continues, and whenever it is ready to report the results back, it says: โ€œShoot, my callback object is zeroโ€, and at least He is not crashing.

However, handling manual weak links is tedious and error prone. You may forget to delegate the delegate inside the dealloc method, and it may appear without notice for several months before you encounter a situation where the code will work.

If you are targeting iOS 5.0, I would read the ARC and the weak links it provides. If you do not want to use ARC or use pre 5.x target devices, I would recommend using zeroing weak link libraries, for example MAZeroingWeakRef , which also work for 3.x devices.

Using ARC or MAZeroingWeakRef weak links, you must complete a background job with one of these fantastic weak link objects pointing to your table. Now, if the pointed object leaves, the weak pointer will be null and the background task will not crash.

0
source

Source: https://habr.com/ru/post/1397196/


All Articles