Каков наилучший способ справиться с UIActivityIndicator и несколькими потоками? - PullRequest
2 голосов
/ 22 ноября 2010

Я долго пытался поиграть с этим, и я не могу найти лучший подход. Я запутался, потому что, кажется, есть разные ответы / мнения о том, как выполнить эту, казалось бы, простую задачу.

Я хочу иметь многократно используемый класс ActivityIndicatorController. Этот контроллер имеет два основных метода: activIndicator и deactivateIndicator. Он принимает UIView в качестве аргумента / свойства, а также NSString для метки. После активации он отключит взаимодействие с пользователем в UIView и добавит подпредставление прямоугольника (с альфа-каналом и закругленными углами), элемент управления UIActivityIndicator и UILabel для текста состояния. Это желательно, потому что таким образом мне не нужно иметь пользовательский код UIActivityIndicatorView в каждом контроллере представления или настраивать ActivityIndicator в каждом NIB.

Основная проблема, с которой я столкнулся, заключается в том, как запустить этот процесс добавления и анимации ActivityIndicator. Некоторые методы, которые я пробовал, вообще не отображают новый вид. Другие работают, но ActivityIndicator не анимирует.

Я пытался использовать [NSThread detachNewThreadSelector: @selector (startAnimating) toTarget: activityIndicator withObject: nil] внутри метода activIndicator, но это не отображает новый UIView.

Я пытался использовать [NSThread detachNewThreadSelector: @selector (activIndicator) toTarget: activityIndicatorController withObject: nil] из вызывающего метода, но это поместило бы все создание нового UIView в отдельный поток.

Теперь к вопросу:

Часть 1: Я понимаю, что все интерфейсы должны обрабатываться в главном потоке, это правильно?

Часть 2: В чем разница / преимущество / недостаток использования [NSThread detachThreadSelector] по сравнению с NSOperation?

Часть 3: лучше ли:

(a) отправить длительную операцию в новый фоновый поток с обратным вызовом в основной поток ИЛИ

(b) отправить метод startAnimating UIActivityIndicatorView в отдельный поток и запустить длительный процесс в главном потоке

И почему?

Вот мой текущий код:

ActivityViewController класс:

-(void)activateIndicator {
NSLog(@"activateIndicator called");
if (isActivated || !delegateView)
    return;
NSLog(@"activateIndicator started");

[delegateView.view setUserInteractionEnabled:NO];
[delegateView.navigationController.view setUserInteractionEnabled:NO];
[delegateView.tabBarController.view setUserInteractionEnabled:NO];

float w = [[UIScreen mainScreen] bounds].size.width;
float h = [[UIScreen mainScreen] bounds].size.height;

NSLog(@"Width = %f\nHeight = %f", w, h);

if (!disabledView) {
    disabledView = [[[UIView alloc] initWithFrame:CGRectMake((w - kNormalWidth) / 2.0, (h - kNormalHeight) / 2.0, kNormalWidth, kNormalHeight)] autorelease];
    disabledView.center = [[[delegateView.view superview] superview] center];
    [disabledView setBackgroundColor:[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.85]];

CALayer *layer = [disabledView layer];
NSLog(@"layer=%@",layer);
NSLog(@"delegate=%@",[layer delegate]);
layer.cornerRadius = 12.0f;
}

if (!activityIndicator) {
    activityIndicator = [[[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(kNormalWidth / 2, 10.0f, 40.0f, 40.0f)] autorelease];
    [activityIndicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhiteLarge];
    activityIndicator.center = disabledView.center;
}

if (!activityLabel) {
    activityLabel = [[[UILabel alloc] initWithFrame:CGRectMake(10.0f, 100.0f, kNormalWidth - 20, 38)] autorelease];
    activityLabel.text = labelText;
    activityLabel.textAlignment = UITextAlignmentCenter;
    activityLabel.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.0f];
    activityLabel.textColor = [UIColor colorWithWhite:1.0f alpha:1.0f];
    activityLabel.center = disabledView.center;
}

[[[delegateView.view superview] superview] addSubview:disabledView];

[[[delegateView.view superview] superview] addSubview:activityIndicator];
[[[delegateView.view superview] superview] addSubview:activityLabel];


[NSThread detachNewThreadSelector:@selector(startAnimating) toTarget:activityIndicator withObject:nil];
}

Телефонный код из нескольких мест в приложении:

    ActivityIndicatorController *aic = [[ActivityIndicatorController alloc] init];
aic.delegateView = self;
aic.labelText = @"Test...";
[aic activateIndicator];

//DO LENGTHY WORK ON MAIN THREAD

[aic deactivateIndicator];
[aic release], aic = nil;

Ответы [ 3 ]

2 голосов
/ 22 ноября 2010

Часть 1. Я понимаю, что все интерфейсы пользователя должны обрабатываться в главном потоке, верно?

Правильно.

Часть 2. Чточем отличается / преимущество / недостаток использования [NSThread detachThreadSelector] по сравнению с NSOperation?

NSOperation - это интерфейс более высокого уровня, который позволяет ставить операции в очередь, создавать несколько операций, которые зависят друг от другаи т. д. Другие варианты работы с заданиями в фоновом режиме: performSelectorOnMainThread:... / performSelectorInBackground:... и Grand Central Dispatch.

Часть 3: лучше:

(a)отправить длинную операцию в новый фоновый поток с обратным вызовом в основной поток ИЛИ

(b) отправить метод startAnimating UIActivityIndicatorView в отдельный поток и запустить длинный процесс в основном потоке

Из-за ответа на вопрос 1 (а) - ваш единственный вариант.

1 голос
/ 22 ноября 2010

Поместите вашу длинную работу в отдельный поток, чтобы он не полностью блокировал пользовательский интерфейс на случай, если вам понадобится какое-то взаимодействие (скажем, для отмены операции).Затем ваш ActivityIndicatorController должен обратиться к основному потоку, чтобы выполнить весь материал пользовательского интерфейса, например:

@implementation ActivityIndicatorController

    - (void)activateIndicator
    {
        [self performSelectorOnMainThread:@selector(activateOnMainThread)
                               withObject:nil
                            waitUntilDone:YES];
    }

    - (void)activateOnMainThread
    {
        // Do your actual UI stuff here.
    }

    // And similarly for the deactivate method.
0 голосов
/ 22 ноября 2010

После отображения и анимации индикатор активности будет продолжать анимацию, даже если основной поток заблокирован.

Однако представление не имеет шансов на запуск, поскольку цикл выполнения еще не выполнен из-за отложенной длительной операции.

Так что я думаю, что вам нужно просто performSelector:withObject:afterDelay с задержкой 0, чтобы ваша длительная операция была поставлена ​​в очередь и выполнена после того, как ваш индикатор станет видимым.

...