UI блокує навіть за допомогою selectSelectorOnMainThread та waitUntilDone: NO

У моєму viewController у мене є таймер, який запускається кожні 20 секунд і викликає оновлення місцезнаходження. Тоді, коли місце оновлення, я викликаю метод через performSelectorOnMainThread . Чомусь, хоча я включив waitUntilDone: NO , мій інтерфейс користувача залишається блокуванням, поки перевірка виконується. Хто-небудь знає, як я можу покращити це, щоб екран не замерзав кожні 20 секунд? Дякую!

-(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];   
    }
}

Нарешті, мій інтерфейс оновлюється за допомогою цього методу:

-(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 Відповіді

Схоже, ви завантажуєте деякі дані з URL-адреси, використовуючи dataWithContentsOfURL: , поки в основному потоці.

Схоже, що це буде відповідальним за замороження.

Спробуйте перемістити це, щоб ви тільки дзвонили в основний потік, коли дані, які ви хочете представити в інтерфейсі користувача, готові.

4
додано

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];  
    });
}

Або додайте таймер асинхронно:

-(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
додано
На якій лінії ви отримуєте цю помилку?
додано Автор Ramy Al Zuhouri, джерело
Дякую! Я отримую помилку, яка говорить про те, що пропускає void (^) (void) до парамамера несумісного типу "unsigned long". будь-які ідеї щодо того, як це виправити?
додано Автор user2492064, джерело

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];  
    });
}

Або додайте таймер асинхронно:

-(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
додано
На якій лінії ви отримуєте цю помилку?
додано Автор Ramy Al Zuhouri, джерело
Дякую! Я отримую помилку, яка говорить про те, що пропускає void (^) (void) до парамамера несумісного типу "unsigned long". будь-які ідеї щодо того, як це виправити?
додано Автор user2492064, джерело

Ідеєю з CLLocationManager є те, що ви створюєте, конфігуруєте та запускаєте один раз, коли ваш додаток цікавиться місцем. Потім на основі встановленої вами конфігурації він зателефонує вам ( locationManager: didUpdateToLocation: fromLocation: або, краще за все, locationManager: didUpdateLocations: ) кожного разу, коли розташування змінено Повторне запуск та зупинка диспетчера місцезнаходжень є неефективним, і навряд чи ви отримаєте точну інформацію про місцезнаходження.

Після того, як ви вирішите це, ви, ймовірно, буде в порядку.

Після цього вам потрібно почистити різьблення, оскільки таймер буде запускати головний потік, тому перехід до основної нитки не змінює нічого (просто додає короткий запізнювання).

Також не пишіть так багато коду. Зокрема, якщо цей код знову і знову є тим самим. Наприклад, якщо ви хочете отримати декілька речей за замовчуванням користувача, не робіть:

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

Краще робити:

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

(за замовчуванням користувач є лише прикладом, це стосується будь-якого виклику методу, який ви робите кілька разів без причини ...)

Це невеликий прибуток кожного разу, але все це додає (як до витрат на виконання, так і для зручності читання/обслуговування).

3
додано
Відповідь від Seán Labastille також є прекрасним моментом і буде сприяти вирішенню цієї проблеми.
додано Автор Wain, джерело
@BillBurgess [NSUserDefaults standardDefaults] може бути не найкращим прикладом, але, як правило, підхід є правильним, оскільки це дозволить уникнути витрат на будь-яку обробку методу доступу.
додано Автор Wain, джерело
Використання [NSUserDefaults standardDefaults] або присвоєння йому змінної буде мати ту ж саму адресу пам'яті і не стоїть нічого на стек пам'яті, вона просто стає особистими перевагами. Вам потрібні додаткові рядки коду або трохи довші лінії.
додано Автор Bill Burgess, джерело

Ідеєю з CLLocationManager є те, що ви створюєте, конфігуруєте та запускаєте один раз, коли ваш додаток цікавиться місцем. Потім на основі встановленої вами конфігурації він зателефонує вам ( locationManager: didUpdateToLocation: fromLocation: або, краще за все, locationManager: didUpdateLocations: ) кожного разу, коли розташування змінено Повторне запуск та зупинка диспетчера місцезнаходжень є неефективним, і навряд чи ви отримаєте точну інформацію про місцезнаходження.

Після того, як ви вирішите це, ви, ймовірно, буде в порядку.

Після цього вам потрібно почистити різьблення, оскільки таймер буде запускати головний потік, тому перехід до основної нитки не змінює нічого (просто додає короткий запізнювання).

Також не пишіть так багато коду. Зокрема, якщо цей код знову і знову є тим самим. Наприклад, якщо ви хочете отримати декілька речей за замовчуванням користувача, не робіть:

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

Краще робити:

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

(за замовчуванням користувач є лише прикладом, це стосується будь-якого виклику методу, який ви робите кілька разів без причини ...)

Це невеликий прибуток кожного разу, але все це додає (як до витрат на виконання, так і для зручності читання/обслуговування).

3
додано
Відповідь від Seán Labastille також є прекрасним моментом і буде сприяти вирішенню цієї проблеми.
додано Автор Wain, джерело
@BillBurgess [NSUserDefaults standardDefaults] може бути не найкращим прикладом, але, як правило, підхід є правильним, оскільки це дозволить уникнути витрат на будь-яку обробку методу доступу.
додано Автор Wain, джерело
Використання [NSUserDefaults standardDefaults] або присвоєння йому змінної буде мати ту ж саму адресу пам'яті і не стоїть нічого на стек пам'яті, вона просто стає особистими перевагами. Вам потрібні додаткові рядки коду або трохи довші лінії.
додано Автор Bill Burgess, джерело

Коли викликається зворотний виклик таймера, ви перебуваєте в основному потоці. Потім, коли ви викликаєте метод buttonUpdate, використовуючи "performSelectorOnMainThread", ви просто чергуєте це повідомлення в основному циклі запуску та продовжуєте виконання. Тоді, коли настав час для виконання кнопкиUpdate, він блокує інтерфейс користувача, оскільки синхронний мережевий виклик "dataWithContentsOfURL" запускається всередині основного потоку. Найкращий спосіб вирішити вашу проблему - негайно викликати кнопку "buttonUpdate" (не використовуйте performSelectorOnMainThread, як і ви вже в основній темі), і зателефонуйте в initWithURL з асинхронізації GCD або NSOperationQueue. Очікуючи оновлення даних, ви можете відобразити "повідомлення про оновлення" у цій кнопці, і як тільки ваш асинхронний блок або паралельний NSOperation завершиться, ви, нарешті, можете оновити кнопку до кінцевого стану (не забувайте робити це оновлення основна тема).

2
додано

Коли викликається зворотний виклик таймера, ви перебуваєте в основному потоці. Потім, коли ви викликаєте метод buttonUpdate, використовуючи "performSelectorOnMainThread", ви просто чергуєте це повідомлення в основному циклі запуску та продовжуєте виконання. Тоді, коли настав час для виконання кнопкиUpdate, він блокує інтерфейс користувача, оскільки синхронний мережевий виклик "dataWithContentsOfURL" запускається всередині основного потоку. Найкращий спосіб вирішити вашу проблему - негайно викликати кнопку "buttonUpdate" (не використовуйте performSelectorOnMainThread, як і ви вже в основній темі), і зателефонуйте в initWithURL з асинхронізації GCD або NSOperationQueue. Очікуючи оновлення даних, ви можете відобразити "повідомлення про оновлення" у цій кнопці, і як тільки ваш асинхронний блок або паралельний NSOperation завершиться, ви, нарешті, можете оновити кнопку до кінцевого стану (не забувайте робити це оновлення основна тема).

2
додано
IT KPI iOS
IT KPI iOS
74 учасників

Чат обсуждения IOS. - Оффтоп, флуд, оскорбления и вбросы здесь не приняты. - За нарушение - предупреждение или mute на неделю. - За спам и рекламу - ban. Все чаты IT KPI: https://t.me/itkpi/602

ios_jobs_ua
ios_jobs_ua
27 учасників

Mobile Dev Jobs UA
Mobile Dev Jobs UA
20 учасників

Публикуем вакансии и запросы на поиск работы по направлению iOS, Android, Xamarin, RN и т.д.