ARC:向对象发送nil不会立即调用其dealloc

时间:2013-04-13 07:59:05

标签: iphone ios objective-c automatic-ref-counting

我正在从手动内存管理过渡到ARC并遇到问题。大多数时候,我通过调用模型类中的performSelectorInBackground来异步执行数据加载。问题是我需要在模型收到nil(发布)时停止任何模型代码执行。在非弧形中,一切都很简单 - 只要用户关闭窗口,其控制器就会开始释放自己并释放其模型[_myModel release],因此模型停止其代码执行(数据加载)并调用其dealloc方法。

这似乎与ARC有所不同。即使从控制器接收到nil消息,模型仍然执行代码。它的dealloc方法仅在其代码执行(数据加载)后被调用。这是一个问题,因为当用户关闭窗口(控制器)时,代码执行应该尽快停止。这是对代码缺乏控制的某种控制 - 控制器告诉模型 - “走开,我不再需要你的工作”,但模型仍然“正在努力完成它的工作”:)。

想象一下,模型执行一些非常繁重的数据处理,持续时间为10秒。当用户打开窗口(控制器)时,模型开始进行处理。但是图像用户改变主意并在打开窗口后关闭窗口。该模型仍然执行浪费的处理。任何想法如何解决或解决?我不喜欢在我的模型中有一个特殊的BOOL“shouldDealloc”属性,并在控制器dealloc方法中设置为YES,并在我的模型类条件中使用。有更优雅的解决方案吗?

我做了一些演示项目来展示问题。对于测试,只需创建单个视图应用程序并粘贴代码。在ViewController.xib文件中创建按钮 - “开始计算”“停止计算”,并将其IBActions与 startCalculationPressed 相关联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

将新的MyModel类添加到项目中:

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是对的,performSelectorOnMainThread总是保留self,因此无法停止在其他线程上执行代码(在ARC和非ARC中),因此在释放模型时不会立即调用dealloc 。因此,应该使用带有条件检查的适当属性(例如委托)显式地完成。

1 个答案:

答案 0 :(得分:6)

如果对象的释放次数降至零,则取消分配对象,如果是,则以ARC语言取消分配 对该对象的最后一个强引用已经消失。

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

添加了对self的强引用,这解释了为什么对象未被释放 在后台线程完成之前。

没有办法(我知道)让后台线程“自动”停止。 对于以dispatch_async()NSOperation开头的块,情况也是如此。 一旦启动,线程/块/操作必须在点处监视某些属性 保存停止的地方。

在您的示例中,您可以监控self.delegate。如果那变为nil,则没有人 对结果感兴趣,所以后台线程可以返回。在这种情况下, 将delegate属性声明为atomic

是有意义的

请注意,如果视图控制器,self.delegate也会自动设置为nil 被释放(因为它是一个弱属性),即使stopCalculationPressed没有 被叫了。