【发布时间】:2013-04-05 19:44:15
【问题描述】:
我正在从手动内存管理过渡到 ARC,但遇到了问题。大多数时候,我通过在模型类中调用 performSelectorInBackground 来异步执行数据加载。问题是当模型收到 nil(发布)时,我需要停止任何模型代码执行。在非 arc 中,一切都很简单——一旦用户关闭窗口,它的控制器就会开始释放自己并释放它的模型 [_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 .因此,这应该通过条件检查使用适当的属性(例如委托)显式完成。
【问题讨论】:
-
为什么不只保留一个在
performCalculateAsyncfor 循环中检查的布尔值,并且在stopCalculationPressed设置时结束函数(将需要某种互斥体概率)也许你的实际程序没有循环或某个地方你可以适当地放置这个检查,但如果它有...... -
@Fonix 你看它只是一个简单的演示代码。真实模型类中有更多东西(处理、回调等)。拥有这样的代理属性将迫使我在整个代码中添加条件检查,使其变得丑陋......
-
您不应使用内存管理来控制程序的行为。内存管理是用于管理内存而不是其他任何东西。如果您需要终止线程或其他操作,则必须安排一个独立于内存管理的显式信号来实现该效果。你之前的设计被破坏了。
-
@Centurion 你可能想看看
NSOperation。它内置了对取消异步操作的支持。 -
@jlehr:运行中的 NSOperation 还必须检查它是否已被取消(文档明确指出 “您应该始终在您编写的任何自定义代码中支持取消语义。特别是,您的主要任务代码应该定期检查 isCancelled 方法的值。”),因此 performSelectorInBackground/dispatch_async/NSOperation 在取消方面没有根本区别。
标签: iphone ios objective-c automatic-ref-counting