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.