Не работает с NSThread: executeSelector:withObject:afterDelay:?
Возможно ли, что performSelector:withObject:afterDelay:
не работает в подпотоках?
Я все еще плохо знаком с целью c и Xcode, так что, возможно, я упустил что-то очевидное...:/ Я бы очень признателен за помощь.
Все, что я хочу сделать, это показать информационную метку в течение 3 секунд, после чего она будет скрыта. В случае установки новой информации нить, скрывающая метку через 3 секунды, должна быть отменена. (Я не хочу, чтобы новая информация скрывалась через старые темы.)
Исходный код:
- (void) setInfoLabel: (NSString*) labelText
{
// ... update label with text ...
infoLabel.hidden = NO;
if(appDelegate.infoThread != nil) [appDelegate.infoThread cancel]; // cancel last hide-thread, if it exists
NSThread *newThread = [[NSThread alloc] initWithTarget: self selector:@selector(setInfoLabelTimer) object: nil];// create new thread
appDelegate.infoThread = newThread; // save reference
[newThread start]; // start thread
[self performSelector:@selector(testY) withObject: nil afterDelay:1.0];
}
-(void) setInfoLabelTimer
{
NSLog(@"setInfoLabelTimer");
[self performSelector:@selector(testX) withObject: nil afterDelay:1.0];
[self performSelector:@selector(hideInfoLabel) withObject: nil afterDelay:3.0];
NSLog(@"Done?");
}
-(void) testX
{
NSLog(@"testX testX testX testX testX");
}
-(void) testY
{
NSLog(@"testY testY testY testY testY");
}
-(void) hideInfoLabel
{
NSLog(@"f hideInfoLabel");
if(!([[NSThread currentThread] isCancelled])) {
AppDelegate *appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
appDelegate.infoThread = nil;
appDelegate.infoLabel.hidden = YES;
[NSThread exit];
}
}
Консоль-выход:
- setInfoLabelTimer
- Готово?
- тестовый тестовый тестовый тестовый тестовый тест
Как вы видете performSelector:withObject:afterDelay:
РАБОТАЕТ (--->"testY testY testY testY testY"), но не в подпотоке (который запускается (--->"setInfoLabelTimer" и "Done?"))
Кто-нибудь знает почему performSelector:withObject:afterDelay
: не работает в подпотоках? (Или в чем моя вина?:()
С уважением, Чайник
4 ответа
Если вы хотите вызвать executeSelector:withObject:afterDelay для потока, у этого потока должен быть запущен RunLoop. Ознакомьтесь с Руководством по программированию потоков от Apple. Вот также пример для RunLoop и NSThread.
Вы можете добавить следующий код в setInfoLabelTimer:
while (!self.isCancelled)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
Если вы работаете с 'sub' потоком (потоком, который не является основным потоком), он может выполняться одним из двух способов:
- Он запускает один метод, а затем завершается
- Он запускает цикл выполнения и обрабатывает элементы из очереди
Если поток запускается в форме 1, ваше использование performSelector
помещает элемент в очередь (или пытается хотя бы), но он никогда не будет обработан, поток просто прекратит работу.
Если вы хотите использовать performSelector
на нити вам нужно сделать дополнительную работу. Или вы можете поместить элемент в основной поток, в котором выполняется цикл выполнения.
Кроме того, вы можете рассмотреть возможность работы с Grand Central Dispatch, GCD, вместо этого. Если вы хотите сделать что-то за три секунды, вы можете:
double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// do stuff here, and because it's in the main queue, you can do UI stuff, too
});
Я также отсылаю вас к разделу Миграция вдали от потоков в Руководстве по программированию параллелизма.
В качестве альтернативы, вместо использования GCD, вы можете использовать блок анимации, в котором вы можете указать, что вы хотите, чтобы происходило через 3, 0 секунды. Вы также можете анимировать этот переход (в моем примере, 0,25 секунды), так что удаление элемента управления будет немного более плавным:
[UIView animateWithDuration:0.25
delay:3.0
options:0
animations:^{
// you can, for example, visually hide in gracefully over a 0.25 second span of time
infoLabel.alpha = 0.0;
}
completion:^(BOOL finished) {
// if you wanted to actually remove the view when the animation was done, you could do that here
[infoLabel removeFromSuperview];
}];
Нет нужды ни в потоках, ни в GCD, чтобы делать то, что вы хотите.
Просто использовать performSelector:withObject:afterDelay:
непосредственно в главном потоке, используя анимацию, как указано @Rob, используйте dispatch_after
в основной очереди или NSTimer
,