UI schließt sogar mit performSelectorOnMainThread und waitUtilDone: NO

In meinem viewController habe ich einen Timer, der alle 20 Sekunden auslöst und eine Standortaktualisierung aufruft. Wenn der Standort aktualisiert wird, rufe ich eine Methode über performSelectorOnMainThread auf. Aus irgendeinem Grund, obwohl ich waitUntilDone: NO eingeschlossen habe, bleibt meine Benutzeroberfläche während der Überprüfung geschlossen. Weiß jemand wie ich das verbessern kann damit der Bildschirm nicht alle 20 Sekunden einfriert? Vielen Dank!

-(void)viewDidLoad {

    NSTimer* myTimer = [NSTimer scheduledTimerWithTimeInterval: 20.0 target: self 
    selector: @selector(callAfterSixtySecond:) userInfo: nil repeats: YES];

    //other code
}

-(void) callAfterSixtySecond:(NSTimer*) t {

    locationManagerProfile.delegate = self;
    locationManagerProfile.desiredAccuracy = kCLLocationAccuracyBest; 
    [locationManagerProfile startUpdatingLocation];  
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation 
*)newLocation fromLocation:(CLLocation *)oldLocation {

    CLLocation *currentLocation = newLocation;

    if (currentLocation != nil) {

        [self performSelectorOnMainThread:@selector(buttonUpdate) withObject:nil 
        waitUntilDone:NO];   
    }
}

Zum Schluss wird meine Oberfläche mit dieser Methode aktualisiert:

-(void) buttonUpdate {

    [locationManagerProfile stopUpdatingLocation];
    NSString *userLatitude =[(PDCAppDelegate *)[UIApplication sharedApplication].delegate 
    getUserLatitude];
    NSString *userLongitude =[(PDCAppDelegate *)[UIApplication sharedApplication].delegate 
    getUserLongitude];

    NSString *placeLatitude = [[NSUserDefaults standardUserDefaults]
    stringForKey:@"savedLatitude"];

    NSString *placeLongitude = [[NSUserDefaults standardUserDefaults]
    stringForKey:@"savedLongitude"];

    NSString *distanceURL = [NSString stringWithFormat:@"http://www.website.com/page.php?
    lat1=%@&lon1=%@&lat2=%@&lon2=%@",userLatitude, userLongitude, placeLatitude, 
    placeLongitude];

    NSData *distanceURLResult = [NSData dataWithContentsOfURL:[NSURL 
    URLWithString:distanceURL]];

    NSString *distanceInFeet = [[NSString alloc] initWithData:distanceURLResult 
    encoding:NSUTF8StringEncoding];

    if ([distanceInFeet isEqualToString:@"1"]) {

        UIBarButtonItem *btnGo = [[UIBarButtonItem alloc] initWithTitle:@"Button A" 
        style:UIBarButtonItemStyleBordered target:self action:@selector(actionA)];
        self.navigationItem.rightBarButtonItem = btnGo;
        [self.navigationItem.rightBarButtonItem setTintColor:[UIColor 
        colorWithRed:44.0/255.0 green:160.0/255.0 blue:65.0/255.0 alpha:1.0]];  
        UIBarButtonItem *btnGoTwo = [[UIBarButtonItem alloc] initWithTitle:@"Button B" 
        style:UIBarButtonItemStyleBordered target:self action:@selector(actionB)];
        self.navigationItem.rightBarButtonItem = btnGoTwo; 
        self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:btnGo, 
        btnGoTwo, nil];
    }
    if ([distanceInFeet isEqualToString:@"0"]) {
        UIBarButtonItem *btnGo = [[UIBarButtonItem alloc] initWithTitle:@"Button C" 
        style:UIBarButtonItemStyleBordered target:self action:@selector(actionC)];
        self.navigationItem.rightBarButtonItem = btnGo;
        [self.navigationItem.rightBarButtonItem setTintColor:[UIColor 
        colorWithRed:44.0/255.0 green:160.0/255.0 blue:65.0/255.0 alpha:1.0]];        
        UIBarButtonItem *btnGoTwo = [[UIBarButtonItem alloc] initWithTitle:@"Button B" 
        style:UIBarButtonItemStyleBordered target:self action:@selector(actionB)];
        self.navigationItem.rightBarButtonItem = btnGoTwo;   
        self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:btnGo, 
        btnGoTwo, nil];
    }       
}
0

7 Antworten

Es scheint, dass Sie einige Daten von einer URL mit dataWithContentsOfURL: während des Hauptthreads abrufen.

Das sieht so aus, als wäre es für das Einfrieren verantwortlich.

Versuchen Sie, diese Option zu entfernen, sodass Sie nur dann zum Hauptthread aufrufen, wenn die Daten, die Sie auf der Benutzeroberfläche präsentieren möchten, bereit sind.

4
hinzugefügt

viewDidLoad gets called on the main thread, you're adding the timer here so the selector is called on the main thread. You aren't in background. There are many ways to make the method execute concurrently, for example you can call dispatch_async inside it:

-(void) callAfterSixtySecond:(NSTimer*) t {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ^{
        locationManagerProfile.delegate = self;
        locationManagerProfile.desiredAccuracy = kCLLocationAccuracyBest; 
        [locationManagerProfile startUpdatingLocation];  
    });
}

Oder füge den Timer asynchron hinzu:

-(void)viewDidLoad {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ^{
        NSTimer* myTimer = [NSTimer scheduledTimerWithTimeInterval: 20.0 target: self 
        selector: @selector(callAfterSixtySecond:) userInfo: nil repeats: YES];
    });

    //other code
}
3
hinzugefügt
Bei welcher Zeile erhalten Sie diesen Fehler?
hinzugefügt der Autor Ramy Al Zuhouri, Quelle
Vielen Dank! Ich erhalte einen Fehler, der besagt, dass void (^) (void) an Parameter des inkompatiblen Typs 'unsigned long' übergeben wurde. irgendwelche Ideen, wie das zu beheben ist?
hinzugefügt der Autor user2492064, Quelle

viewDidLoad gets called on the main thread, you're adding the timer here so the selector is called on the main thread. You aren't in background. There are many ways to make the method execute concurrently, for example you can call dispatch_async inside it:

-(void) callAfterSixtySecond:(NSTimer*) t {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ^{
        locationManagerProfile.delegate = self;
        locationManagerProfile.desiredAccuracy = kCLLocationAccuracyBest; 
        [locationManagerProfile startUpdatingLocation];  
    });
}

Oder füge den Timer asynchron hinzu:

-(void)viewDidLoad {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ^{
        NSTimer* myTimer = [NSTimer scheduledTimerWithTimeInterval: 20.0 target: self 
        selector: @selector(callAfterSixtySecond:) userInfo: nil repeats: YES];
    });

    //other code
}
3
hinzugefügt
Bei welcher Zeile erhalten Sie diesen Fehler?
hinzugefügt der Autor Ramy Al Zuhouri, Quelle
Vielen Dank! Ich erhalte einen Fehler, der besagt, dass void (^) (void) an Parameter des inkompatiblen Typs 'unsigned long' übergeben wurde. irgendwelche Ideen, wie das zu beheben ist?
hinzugefügt der Autor user2492064, Quelle

Die Idee mit CLLocationManager ist, dass Sie es einmal erstellen, konfigurieren und starten, sobald Ihre App an dem Standort interessiert ist. Basierend auf der von Ihnen festgelegten Konfiguration ruft es Sie dann zurück ( locationManager: didUpdateToLocation: fromLocation: oder, besser noch, wenn Sie können, locationManager: didUpdateLocations: ) ist geändert. Das wiederholte Starten und Stoppen des Standortmanagers ist ineffizient und es ist unwahrscheinlich, dass Sie genaue Ortsinformationen erhalten.

Sobald Sie das gelöst haben, werden Sie wahrscheinlich in Ordnung sein.

Danach müssen Sie das Threading auffrischen, da der Timer auf den Hauptthread feuert, sodass ein Wechsel zum Hauptthread nichts wirklich ändert (fügt nur eine kurze Verzögerung hinzu).

Schreiben Sie auch nicht so viel Code. Vor allem wenn es immer wieder den gleichen Code gibt. Zum Beispiel, wenn Sie mehrere Dinge von Benutzer Standardeinstellungen erhalten möchten, tun Sie nicht:

[[NSUserDefaults standardUserDefaults] stringForKey:@"savedLatitude"]
[[NSUserDefaults standardUserDefaults] stringForKey:@"savedLongitude"]

Besser zu tun:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]
[defaults stringForKey:@"savedLatitude"]
[defaults stringForKey:@"savedLongitude"]

(Benutzer-Standardeinstellungen sind nur ein Beispiel, dies gilt für jeden Methodenaufruf, den Sie ohne Grund mehrfach durchführen.)

Es ist jedes Mal ein kleiner Gewinn, aber alles addiert sich (sowohl zu Laufzeitkosten als auch zu Lesbarkeit/Wartung).

3
hinzugefügt
Die Antwort von Seán Labastille ist auch ein ausgezeichneter Punkt und wird zu dem Thema beitragen.
hinzugefügt der Autor Wain, Quelle
@BillBurges [NSUserDefaults standardDefaults] ist möglicherweise nicht das beste Beispiel, aber als allgemeine Regel ist der Ansatz gültig, da er die Kosten für jede Verarbeitung der Accessor-Methode vermeidet.
hinzugefügt der Autor Wain, Quelle
Die Verwendung von [NSUserDefaults standardDefaults] oder das Zuweisen zu einer Variablen hat die gleiche Speicheradresse und kostet nichts zum Speicherstack, es wird nur zur persönlichen Präferenz. Wünschen Sie zusätzliche Codezeilen oder etwas längere Zeilen?
hinzugefügt der Autor Bill Burgess, Quelle

Die Idee mit CLLocationManager ist, dass Sie es einmal erstellen, konfigurieren und starten, sobald Ihre App an dem Standort interessiert ist. Basierend auf der von Ihnen festgelegten Konfiguration ruft es Sie dann zurück ( locationManager: didUpdateToLocation: fromLocation: oder, besser noch, wenn Sie können, locationManager: didUpdateLocations: ) ist geändert. Das wiederholte Starten und Stoppen des Standortmanagers ist ineffizient und es ist unwahrscheinlich, dass Sie genaue Ortsinformationen erhalten.

Sobald Sie das gelöst haben, werden Sie wahrscheinlich in Ordnung sein.

Danach müssen Sie das Threading auffrischen, da der Timer auf den Hauptthread feuert, sodass ein Wechsel zum Hauptthread nichts wirklich ändert (fügt nur eine kurze Verzögerung hinzu).

Schreiben Sie auch nicht so viel Code. Vor allem wenn es immer wieder den gleichen Code gibt. Zum Beispiel, wenn Sie mehrere Dinge von Benutzer Standardeinstellungen erhalten möchten, tun Sie nicht:

[[NSUserDefaults standardUserDefaults] stringForKey:@"savedLatitude"]
[[NSUserDefaults standardUserDefaults] stringForKey:@"savedLongitude"]

Besser zu tun:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]
[defaults stringForKey:@"savedLatitude"]
[defaults stringForKey:@"savedLongitude"]

(Benutzer-Standardeinstellungen sind nur ein Beispiel, dies gilt für jeden Methodenaufruf, den Sie ohne Grund mehrfach durchführen.)

Es ist jedes Mal ein kleiner Gewinn, aber alles addiert sich (sowohl zu Laufzeitkosten als auch zu Lesbarkeit/Wartung).

3
hinzugefügt
Die Antwort von Seán Labastille ist auch ein ausgezeichneter Punkt und wird zu dem Thema beitragen.
hinzugefügt der Autor Wain, Quelle
@BillBurges [NSUserDefaults standardDefaults] ist möglicherweise nicht das beste Beispiel, aber als allgemeine Regel ist der Ansatz gültig, da er die Kosten für jede Verarbeitung der Accessor-Methode vermeidet.
hinzugefügt der Autor Wain, Quelle
Die Verwendung von [NSUserDefaults standardDefaults] oder das Zuweisen zu einer Variablen hat die gleiche Speicheradresse und kostet nichts zum Speicherstack, es wird nur zur persönlichen Präferenz. Wünschen Sie zusätzliche Codezeilen oder etwas längere Zeilen?
hinzugefügt der Autor Bill Burgess, Quelle

Wenn der Timer-Rückruf aufgerufen wird, befinden Sie sich im Hauptthread. Wenn Sie dann die Methode 'buttonUpdate' mit 'performSelectorOnMainThread' aufrufen, wird diese Nachricht einfach in die Hauptlaufschleife eingereiht und die Ausführung fortgesetzt. Wenn es dann an der Zeit ist, dass buttonUpdate ausgeführt wird, blockiert es die Benutzeroberfläche, da der synchrone Netzwerkaufruf "dataWithContentsOfURL" im Hauptthread ausgeführt wird. Der beste Weg, um Ihr Problem zu beheben, besteht darin, "buttonUpdate" sofort aufzurufen (verwenden Sie nicht performSelectorOnMainThread, da Sie bereits im Hauptthread sind) und rufen Sie Ihre initWithURL innerhalb einer GCD async oder einer NSOperationQueue auf. Während Sie darauf warten, dass die Daten aktualisiert werden, können Sie eine "Aktualisierungsnachricht" in der Schaltfläche anzeigen und sobald Ihre asynchrone Blockierung oder gleichzeitige NSOperation beendet ist, können Sie die Schaltfläche endgültig aktualisieren (vergessen Sie nicht, das Update durchzuführen) der Hauptfaden).

2
hinzugefügt

Wenn der Timer-Rückruf aufgerufen wird, befinden Sie sich im Hauptthread. Wenn Sie dann die Methode 'buttonUpdate' mit 'performSelectorOnMainThread' aufrufen, wird diese Nachricht einfach in die Hauptlaufschleife eingereiht und die Ausführung fortgesetzt. Wenn es dann an der Zeit ist, dass buttonUpdate ausgeführt wird, blockiert es die Benutzeroberfläche, da der synchrone Netzwerkaufruf "dataWithContentsOfURL" im Hauptthread ausgeführt wird. Der beste Weg, um Ihr Problem zu beheben, besteht darin, "buttonUpdate" sofort aufzurufen (verwenden Sie nicht performSelectorOnMainThread, da Sie bereits im Hauptthread sind) und rufen Sie Ihre initWithURL innerhalb einer GCD async oder einer NSOperationQueue auf. Während Sie darauf warten, dass die Daten aktualisiert werden, können Sie eine "Aktualisierungsnachricht" in der Schaltfläche anzeigen und sobald Ihre asynchrone Blockierung oder gleichzeitige NSOperation beendet ist, können Sie die Schaltfläche endgültig aktualisieren (vergessen Sie nicht, das Update durchzuführen) der Hauptfaden).

2
hinzugefügt