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

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

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

Ответ 1

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

Ваше приложение не может создавать или явно управлять NSRunLoop объекты. Каждый объект NSThread, включая основные приложения thread, имеет объект 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];

В Раздел "Руководство по программированию потоков" в циклах запуска:

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

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

Ответ 2

Как насчет этого PseudoSerialQueue? Это минимальная реализация, например, Серийная очередь отправки.

#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];

Ответ 3

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

ИЗМЕНИТЬ

- oops, следует читать более внимательно. следует решение fifo:

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

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

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

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

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

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

удача

Ответ 4

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

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

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

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

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

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

Что это!

Ответ 5

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