2013-04-13 14 views
8

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.

+1

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

+0

@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

+1

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. –

Odpowiedz

6

Obiekt zostanie zwolniony, jeśli jego liczba zwolnień spadnie do zera, lub w języku ARC, jeśli zniknie ostatnie silne odwołanie do tego obiektu.

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

dodaje wyraźne odniesienie do self, co wyjaśnia, dlaczego obiekt nie jest zwalniane przed nić tło zostało zakończone.

Nie ma sposobu (o którym wiem), aby wątek tła zatrzymał się "automatycznie". To samo dotyczy bloków zaczynających się od dispatch_async() lub. Po uruchomieniu wątek/blok/operacja musi monitorować niektóre właściwości w punktach , gdzie jest zapisywany, aby zatrzymać.

W twoim przykładzie możesz monitorować self.delegate. Jeśli stanie się to nil, nikt już nie jest zainteresowany wynikiem, więc wątek tła może powrócić. W takim przypadku byłoby sensowne zadeklarowanie właściwości delegate jako atomic.

Zauważ, że self.delegate jest również automatyczne przełączenie do nil jeśli kontroler widok jest zwalniane (bo jest słaby właściwość) nawet jeśli stopCalculationPressed nie została wywołana.

+0

Chciałbym wiedzieć, jak działa przykład OP z ręcznym zarządzaniem pamięcią. Zarówno w ARC, jak i ręcznym zarządzaniu pamięcią 'performSelectorInBackground' potrąca liczbę zatrzymań, więc w obu przypadkach wydaje się, że' MójModel' zostanie zwolniony dopiero po zakończeniu "performCalculateAsync". – Barjavel

+0

@Barjavel: Tak, to jest dobra uwaga. –

Powiązane problemy