2014-05-03 12 views
8

Stavo cercando di chiamare un metodo esistente della mia ViewController.m dal AppDelegate.m all'interno del metodo applicationDidEnterBackground, così ho trovato questo link: Calling UIViewController method from app delegate, che mi ha detto di implementare questo codice:chiamare un metodo da AppDelegate - Objective-C

nel mio ViewController.m

-(void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; 
    appDelegate.myViewController = self; 
} 

nel mio AppDelegate:

@class MyViewController; 

@interface AppDelegate : UIResponder <UIApplicationDelegate> 

@property (weak, nonatomic) MyViewController *myViewController; 

@end 

E nel AppDeleg L'implementazione di ate:

- (void)applicationDidEnterBackground:(UIApplication *)application 
{ 
    [self.myViewController method]; 
} 

Così ho messo questo codice nel mio progetto e ha funzionato bene, ma non ho capito come funziona il codice, riga per riga. Cosa fa il sharedApplication? Perché devo impostare un delegato invece di creazione di un'istanza di ViewController, come:

ViewController * instance = [[ViewController alloc] init]; 
[instance method]; 

risposta

16

Informazioni di base (definizione di classe contro un'istanza di classe)

Il concetto importante è la differenza tra una definizione di classe e un'istanza di classe.

La definizione di classe è il codice sorgente per la classe. Ad esempio ViewController.m contiene la definizione per la classe myViewController e AppDelegate.m contiene la definizione per la classe AppDelegate. L'altra classe menzionata nella tua domanda è UIApplication. Questa è una classe definita dal sistema, cioè non hai il codice sorgente per quella classe.

A un'istanza di classe è un blocco di memoria sull'heap e un puntatore a tale memoria. Un'istanza di classe è in genere creato con una riga di codice come questo

myClass *foo = [[myClass alloc] init]; 

noti che alloc riserve spazio sul mucchio per la classe, e quindi init imposta i valori iniziali per le variabili/proprietà della classe. Un puntatore all'istanza viene quindi archiviato in foo.

Quando l'applicazione viene avviata, la seguente sequenza di eventi si verifica (grosso modo):

  • il sistema crea un'istanza della classe UIApplication
  • il puntatore all'istanza UIApplication è memorizzato da qualche parte in un variabile di sistema
  • il sistema crea un'istanza della classe AppDelegate
  • il puntatore alla AppDelegate viene memorizzato in una variabile denominata delegate nel caso UIApplication
  • il sistema crea un'istanza della classe MyViewController
  • il puntatore alla classe MyViewController è memorizzato da qualche parte

Lo stoccaggio del puntatore per MyViewController è dove le cose si fanno disordinato. La classe AppDelegate ha una proprietà UIWindow chiamata window. (Puoi vederlo in AppDelegate.h.) Se l'app ha solo un controller di visualizzazione, il puntatore a quel controller di visualizzazione viene archiviato nella proprietà window.rootViewController. Ma se l'app ha più controller di vista (sotto un UINavigationController o un UITabBarController) le cose si complicano.

Il codice di spaghetti soluzione

Quindi il problema che si faccia è questa: quando il sistema chiama il metodo applicationDidEnterBackground, come si fa a ottenere il puntatore al controller della vista? Bene, tecnicamente, il delegato dell'app ha un puntatore al controller della vista da qualche parte sotto la proprietà window, ma non esiste un modo semplice per ottenere quel puntatore (supponendo che l'app abbia più di un controller di visualizzazione).

L'altro thread ha suggerito un approccio al codice spaghetti al problema. (Si noti che l'approccio spaghetti code è stato suggerito solo perché l'OP in questo altro thread non ha voluto fare le cose in modo corretto con le notifiche.) Ecco come funziona il codice di spaghetti

AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; 
appDelegate.myViewController = self; 

Questo codice recupera il puntatore alla UIApplication istanza creata dal sistema, quindi esegue una query sulla proprietà delegate per ottenere un puntatore all'istanza AppDelegate. Il puntatore a self, che è un puntatore all'istanza MyViewController, viene quindi archiviato in una proprietà in AppDelegate.

Il puntatore all'istanza MyViewController può quindi essere utilizzato quando il sistema chiama applicationDidEnterBackground.

La soluzione corretta

La soluzione corretta è quella di utilizzare le notifiche (come nella risposta di kkumpavat)

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; 
} 

- (void)didEnterBackground 
{ 
    NSLog(@"Entering background now"); 
} 

-(void)dealloc 
{ 
    [[NSNotificationCenter defaultCenter] removeObserver:self]; 
} 

con le notifiche, non si memorizzare i puntatori ridondanti per i controller di vista, e si non devi capire dove il sistema ha memorizzato il puntatore al tuo controller di visualizzazione. Chiamando addObserver per il UIApplicationDidEnterBackgroundNotification stai dicendo al sistema di chiamare direttamente il metodo didEnterBackground del controller di visualizzazione.

+0

Grazie mille! Risposta molto completa! –

4

Il controller di vista è già istanziato come parte del processo/storyboard NIB, quindi se la vostra applicazione delegato fa proprio alloc/init , stai semplicemente creando un'altra istanza (che non ha alcuna relazione con quella creata dalla NIB/storyboard).

Lo scopo del costrutto disegnato è semplicemente quello di fornire all'app delegato un riferimento al controller di visualizzazione che NIB/storyboard ha istanziato per te.

6

Hai due domande qui.

1) Che cosa fa il sharedApplication?
Il [UIApplication sharedApplication] fornisce l'istanza di UIApplication appartiene all'applicazione. Questo è il punto di controllo centralizzato per te. Per ulteriori informazioni è possibile leggere UIApplication class reference sul sito degli sviluppatori iOS.

2) Perché devo impostare un delegato invece di creare un'istanza di ViewController?
La creazione del controller in AppDelegate utilizzando nuovamente alloc/init creerà una nuova istanza e questa nuova istanza non punta al controller a cui si sta facendo riferimento. Quindi non otterrai risultati che stai cercando.
Tuttavia, in questo caso d'uso particolare di applicationDidEnterBackground, non è necessario disporre del riferimento del controller in AppDelegate. ViewController può registrarsi per la notifica UIApplicationDidEnterBackgroundNotification nella funzione viewDidLoad e annullare la registrazione nella funzione dealloc.

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(yourMethod) name:UIApplicationDidEnterBackgroundNotification object:nil]; 

    //Your implementation 
} 

-(void)dealloc{ 
    [[NSNotificationCenter defaultCenter] removeObserver:self]; 
}