Przechodzę z ręcznego zarządzania pamięcią do ARC i mam problem. W większości przypadków ładuję dane asynchronicznie, wywołując metodę performSelectorInBackground w moich klasach modeli. Chodzi o to, że muszę zatrzymać wykonywanie dowolnego kodu modelu, gdy model otrzymuje zero (wydanie). W trybie nie-łukowym wszystko było proste - gdy tylko użytkownik zamknie okno, jego kontroler zaczyna deallocować się i zwalnia swój model [_myModel release], a więc model zatrzymuje wykonywanie kodu (ładowanie danych) i jest nazywany jego metodą dealloc .ARC: wysyłanie zera do obiektu nie wywołuje natychmiast jego dealloc
Wygląda inaczej w ARC. Model nadal wykonuje kod nawet po otrzymaniu zerowej wiadomości od kontrolera. Metoda dealloc zostaje wywołana tylko po wykonaniu kodu (ładowaniu danych). Jest to problem, ponieważ wykonanie kodu powinno zostać zatrzymane, gdy użytkownik zamknie okno (kontroler). To jakiś rodzaj braku kontroli nad kodem - kontroler mówi do modelu - "odejdź, już nie potrzebuję twojej pracy", ale model nadal "pracuje, by skończyć swoją pracę" :).
Wyobraź sobie model wykonujący bardzo ciężkie przetwarzanie danych z czasem trwania 10 sekund. Model rozpoczyna przetwarzanie, gdy użytkownik otworzy okno (kontroler). Ale obraz zmienia zdanie i zamyka okno tuż po otwarciu. Model nadal wykonuje nieefektywne przetwarzanie. Wszelkie pomysły, jak je rozwiązać lub obejść? Nie podoba mi się pomysł posiadania specjalnej właściwości BOOL "shouldDealloc" w moim modelu i ustawionej na YES w metodzie dealloc kontrolera i używam jej w warunkach mojej klasy modelu. Czy istnieje bardziej eleganckie rozwiązanie?
Zrobiłem projekt demo, aby pokazać problem. Do testowania wystarczy utworzyć aplikację z pojedynczym widokiem i wkleić kod. Tworzenie się buttons- "Start obliczyć" i "Stop obliczyć" w pliku ViewController.xib i połączyć swoje IBActions z startCalculationPressed i 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
Dodaj nową klasę MyModel do projektu:
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 ma rację, zawsze zachowuje performSelectorOnMainThread jaźń, więc nie ma w jak zatrzymać wykonywanie kodu na innym wątku (zarówno w ARC, jak i poza ARC), więc dealloc nie jest wywoływany natychmiast po zwolnieniu modelu. Tak więc, należy to zrobić jawnie, używając odpowiedniej właściwości (na przykład delegata) z funkcją sprawdzania warunkowego.
dlaczego nie wystarczy zachować bool, który jest sprawdzany w pętli 'performCalculateAsync' for i po prostu zakończy działanie funkcji, gdy jest ustawiony przez' stopCalculationPressed' (będzie potrzebował jakiegoś mutex prob) może twój aktualny program nie ma pętli lub gdzieś możesz umieścić tę kontrolę we właściwy sposób, ale jeśli ... – Fonix
@Fonix Widzisz, że to tylko prosty kod demo. W rzeczywistych klasach modeli (przetwarzanie, wywołania zwrotne itp.) Jest więcej rzeczy. Posiadanie takiej surogatycznej właściwości zmusiłoby mnie do dodania warunkowych sprawdzeń w całym kodzie, czyniąc ją brzydką ... – Centurion
Nie powinieneś używać zarządzania pamięcią do kontrolowania zachowania twojego programu. Zarządzanie pamięcią służy do zarządzania pamięcią, a nie do niczego innego. Jeśli potrzebujesz wątku lub innej operacji do zakończenia, musisz zaaranżować wyraźny sygnał, który będzie niezależny od zarządzania pamięcią. Twój poprzedni projekt był zepsuty. –