Эквивалент очереди последовательной отправки GCD в iOS 3.x

Великая центральная диспетчеризация Apple (GCD) великолепна, но работает только на iOS 4.0 или выше. Документация Apple гласит: "Сериализованная очередь операций [A] не обеспечивает такое же поведение, как в очереди с последовательной отправкой в ​​Grand Central Dispatch" (поскольку очередь не FIFO, но порядок определяется зависимостями и приоритетами).

Как правильно добиться того же эффекта, что и очереди последовательной отправки GCD, при поддержке версий ОС до выпуска GCD? Или, другими словами, каков рекомендуемый способ обработки простой фоновой обработки (выполнение запросов веб-служб и т. Д.) В приложениях для iOS, которые хотят поддерживать версии менее 4.0?

5 ответов

Решение

Кажется, что люди собираются много усилий, чтобы переписать NSRunloop. Согласно документации NSRunloop:

Ваше приложение не может ни создавать, ни явно управлять объектами NSRunLoop. Каждый объект NSThread, включая основной поток приложения, имеет объект NSRunLoop, автоматически создаваемый для него при необходимости.

Так что, безусловно, тривиальным ответом будет создание очереди:

- (void)startRunLoop:(id)someObject
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    [[NSRunLoop currentRunLoop] run];

    [pool release];
}

...

NSThread *serialDispatchThread = [[NSThread alloc] 
                   initWithTarget:self 
                   selector:@selector(startRunLoop:) 
                   object:nil];
[serialDispatchThread start];

Чтобы добавить задачу в очередь:

[object
    performSelector:@selector(whatever:) 
    onThread:serialDispatchThread
    withObject:someArgument
    waitUntilDone:NO];

Согласно разделу Руководства по программированию Threading в циклах выполнения:

Какао определяет пользовательский источник ввода, который позволяет вам выполнять селектор в любом потоке.... выполнять селекторные запросы сериализуются в целевом потоке, устраняя многие проблемы синхронизации, которые могут возникнуть при использовании нескольких методов в одном потоке.

Итак, у вас есть явно последовательная очередь. Конечно, мое не написано фантастически, потому что я сказал циклу выполнения работать вечно, и вы можете предпочесть тот, который вы можете прекратить позже, но это легко сделать.

Как насчет этого PseudoSerialQueue? Это минимальная реализация, такая как Dispatch Serial Queue.

#import <Foundation/Foundation.h>

@interface PseudoTask : NSObject
{
    id target_;
    SEL selector_;
    id queue_;
}

@property (nonatomic, readonly) id target;

- (id)initWithTarget:(id)target selector:(SEL)selector queue:(id)queue;
- (void)exec;
@end

@implementation PseudoTask

@synthesize target=target_;

- (id)initWithTarget:(id)target selector:(SEL)selector queue:(id)queue;
{
    self = [super init];
    if (self) {
        target_ = [target retain];
        selector_ = selector;
        queue_ = [queue retain];
    }
    return self;
}

- (void)exec
{
    [target_ performSelector:selector_];
}

- (void)dealloc
{
    [target_ release];
    [queue_ release];
}
@end

@interface PseudoSerialQueue : NSObject
{
    NSCondition *condition_;
    NSMutableArray *array_;
    NSThread *thread_;
}
- (void)addTask:(id)target selector:(SEL)selector;
@end

@implementation PseudoSerialQueue
- (id)init
{
    self = [super init];
    if (self) {
        array_ = [[NSMutableArray alloc] init];
        condition_ = [[NSCondition alloc] init];
        thread_ = [[NSThread alloc]
            initWithTarget:self selector:@selector(execQueue) object:nil];
        [thread_ start];
    }
    return self;
}

- (void)addTask:(id)target selector:(SEL)selector
{
    [condition_ lock];
    PseudoTask *task = [[PseudoTask alloc]
        initWithTarget:target selector:selector queue:self];
    [array_ addObject:task];
    [condition_ signal];
    [condition_ unlock];
}

- (void)quit
{
    [self addTask:nil selector:nil];
}

- (void)execQueue
{
    for (;;) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        [condition_ lock];
        while (array_.count == 0)
            [condition_ wait];
        PseudoTask *task = [array_ objectAtIndex:0];
        [array_ removeObjectAtIndex:0];
        [condition_ unlock];

        if (!task.target) {
            [pool drain];
            break;
        }

        [task exec];
        [task release];

        [pool drain];
    }
}

- (void)dealloc
{
    [array_ release];
    [condition_ release];
}
@end

Как пользоваться:

PseudoSerialQueue *q = [[[PseudoSerialQueue alloc] init] autorelease];
[q addTask:self selector:@selector(test0)];
[q addTask:self selector:@selector(test1)];
[q addTask:self selector:@selector(test2)];
[q quit];

Вы можете смоделировать это с помощью NSOperationQueue, а затем просто установите количество задач на один.

РЕДАКТИРОВАТЬ

- Ой, следовало бы прочитать более внимательно. следующее решение:

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

я не боюсь писать многопоточные программы, так что вот одно из решений:

  • создайте рабочую очередь fifo, которая:
    • поддерживает блокировку
    • содержит одну NSOperationQueue
    • содержит подкласс NSOperation, разработанный для того, чтобы вытащить работников из очереди fifo при реализации main, только один может существовать одновременно.
    • содержит NSArray рабочих для запуска (определение работника зависит от вас - это NSInvocation, класс, операция, ...)

подкласс NSOperation вытягивает рабочих из рабочей очереди fifo, пока рабочая очередь fifo не будет исчерпана.

когда рабочая очередь fifo имеет рабочих и не имеет активных дочерних операций, она создает дочернюю операцию и добавляет ее в свою очередь операций.

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

удачи

Есть вещи, которые автор документации NSOperationQueue забыл упомянуть, что делает такую ​​реализацию тривиальной, хотя на самом деле это не так.

Установка максимального числа одновременных операций равным 1 гарантированно будет последовательной, только если операции NSO будут добавлены в очередь из того же потока.

Я использую другой вариант, потому что он просто работает.

Добавьте NSOperations из разных потоков, но используйте NSCondition для управления очередями. startOperations может (и не должен блокировать основной поток блокировками) вызываться с помощью executeSelectorOnBackgroundThread...

Метод startOperations представляет одно задание, состоящее из одной или нескольких операций NSOperation.

- (void)startOperations
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    [[AppDelegate condition] lock];

    while (![[[AppDelegate queue] operations] count] <= 0) 
    {
        [[AppDelegate condition] wait];
    }

    NSOperation *newOperation = [alloc, init]....;
    [[AppDelegate queue] addOperation:newOperation];
    [[AppDelegate queue] waitUntilAllOperationsAreFinished]; // Don't forget this!

    NSOperation *newOperation1 = [alloc, init]....;
    [[AppDelegate queue] addOperation:newOperation1];
    [[AppDelegate queue] waitUntilAllOperationsAreFinished]; // Don't forget this!

    NSOperation *newOperation2 = [alloc, init]....;
    [[AppDelegate queue] addOperation:newOperation2];
    [[AppDelegate queue] waitUntilAllOperationsAreFinished]; // Don't forget this!

    // Add whatever number operations you need for this single job

    [[AppDelegate queue] signal];
    [[AppDelegate queue] unlock];

    [NotifyDelegate orWhatever]

    [pool drain];
}

Это оно!

Если обработка в любом случае находится в фоновом режиме, вам действительно нужно, чтобы она была строго в порядке? Если вы это сделаете, то можете добиться того же эффекта, просто настроив свои зависимости так, чтобы 1 зависело от 0, 2 от 1, 3 от 2 и т. Д. Затем очередь операций вынуждена обрабатывать их по порядку. Установите максимальное число одновременных операций равным 1, и очередь также гарантированно будет последовательной.

Другие вопросы по тегам