I'm transitioning from manual memory management to ARC and have an issue. Most of the time, I'm performing data load asynchronously by calling performSelectorInBackground in my model classes. The thing is I need to stop any model code execution when model receives nil (release). In non-arc, everything was straightforward - as soon as a user closes the window, its controller starts to deallocate itself and deallocates its model [_myModel release], and so model stops its code execution (data loading) and gets called its dealloc method.
This seems to be different in ARC. Model still executes the code even after receiving nil message from controller. Its dealloc method gets called after its code execution (data load) only. This is an issue because the code execution should stop ASAP when a user closes the window (controller). It's some sort of a lack of control over the code - controller tells to model - "go away, I don't need your work anymore" but the model still "is working to finish its job" :).
Imagine a model performs some very heavy data processing with duration of 10 seconds. A model starts to do its processing when a user opens the window (controller). But image a user changes his mind and closes the window, just after the opening it. The model still perform wasteful processing. Any ideas how to solve or workaround that? I don't like an idea to have a special BOOL "shouldDealloc" property in my model and set to YES in controller dealloc method, and use in my model class conditions. Is there more elegant solution?
I have made some demo project to show the problem. For testing just create single view application and paste the code. Create to buttons- "Start calculate" and "Stop calculate" in ViewController.xib file, and connect their IBActions with 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 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, performSelectorOnMainThread always retains self, so there's no way how to stop code execution on other thread (both in ARC and non-ARC) so dealloc is not called immediately when releasing model. So, that should be done explicitly using appropriate property (for example delegate) with conditional checking.
performCalculateAsyncfor loop and will just end the function when it is set bystopCalculationPressed(will need some sort of mutex prob) maybe your actual program doesnt have a loop or somewhere you can place this check appropriately but if it does... - FonixNSOperation. It has built-in support for canceling asynchronous operations. - jlehr