ARC: sending null to an object does not call its dealloc immediately

I am switching from manual memory management to ARC and have a problem. In most cases, I load data asynchronously by calling the performSelectorInBackground function in my model classes. The thing is, I need to stop executing the model code when the model receives nil (release). In the non-emergency mode, everything was simple - as soon as the user closes the window, his controller starts to free himself and frees his model [_myModel release], and therefore the model stops the code execution (data loading) and receives a call to its dealloc method,

In ARC, this seems different. The model still executes the code even after receiving a nil message from the controller. Its dealloc method is called after code execution (data loading). This is a problem because code execution must stop when the user closes the window (controller). This kind of lack of control over the code controller tells the model - “go away, I don’t need your work anymore”, but the model still “works to finish its work” :).

Imagine a model doing very heavy data processing for 10 seconds. The model begins to perform its processing when the user opens a window (controller). But the image of the user changes his mind and closes the window immediately after it is opened. The model is still doing wasteful processing. Any ideas how to solve or a workaround? I don’t like the idea of ​​having a special BOOL property “shouldDealloc” in my model and setting YES in the controller dealloc method and using the model class in my conditions. Is there a more elegant solution?

I did some demo projects to show the problem. For testing, just create an application with one view and paste the code. Create for the buttons - “Start calculation” and “Stop calculation” in the ViewController.xib file and connect their IBActions using startCalculationPressed and stopCalculationPressed

ViewController.h

#import "MyModel.h" @interface ViewController : UIViewController <MyModelDelegate> - (IBAction)startCalculationPressed:(id)sender; - (IBAction)stopCalculationPressed:(id)sender; @end 

ViewController.m

 @interface ViewController (){ __strong MyModel *_myModel; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)didCalculated { NSLog(@"Did calculated..."); } - (IBAction)startCalculationPressed:(id)sender { NSLog(@"Starting to calculate..."); _myModel = nil; _myModel = [[MyModel alloc] init]; _myModel.delegate = self; [_myModel calculate]; } - (IBAction)stopCalculationPressed:(id)sender { NSLog(@"Stopping calculation..."); _myModel.delegate = nil; _myModel = nil; } @end 

Add a new MyModel class to the project:

MyModel.h

 @protocol MyModelDelegate <NSObject> - (void)didCalculated; @end @interface MyModel : NSObject @property (nonatomic, weak) id<MyModelDelegate> delegate; - (void)calculate; @end 

MyModel.m

 @implementation MyModel - (void)dealloc { NSLog(@"MyModel dealloc..."); } - (void)calculate { [self performSelectorInBackground:@selector(performCalculateAsync) withObject:nil]; } - (void)performCalculateAsync { // Performing some longer running task int i; int limit = 1000000; NSMutableArray *myList = [[NSMutableArray alloc] initWithCapacity:limit]; for (i = 0; i < limit; i++) { [myList addObject:[NSString stringWithFormat:@"Object%d", i]]; } [self performSelectorOnMainThread:@selector(calculateCallback) withObject:nil waitUntilDone:NO]; } - (void)calculateCallback { [self.delegate didCalculated]; } @end 

UPDATE Martin is right, executing SelectorOnMainThread always saves itself, so there is no way to stop code execution in another thread (both in ARC and non-ARC), so dealloc is not called immediately when the model is released, therefore, this should be done explicitly using corresponding property (for example, a delegate) with conditional validation.

+8
ios objective-c iphone automatic-ref-counting
source share
1 answer

An object is freed if its release count is reduced to zero or in ARC if the last strong reference to that object has disappeared.

 [self performSelectorInBackground:@selector(performCalculateAsync) withObject:nil]; 

adds a strong reference to self , which explains why the object is not freed before the background thread ends.

There is no way (what I know) to stop the background thread "automatically". The same applies to blocks starting with dispatch_async() or for NSOperation . After starting, the thread / block / operation must control some property at the points where it is stored for stop.

In your example, you can track self.delegate . If it becomes nil , no one else is interested in the result, so the background thread may return. In that case, it would be wise to declare the delegate property as atomic .

Note that self.delegate also automatically set to nil if the view controller (since this is a weak property), even if stopCalculationPressed has not been called.

+6
source share

All Articles