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

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

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


person jrdioko    schedule 27.05.2011    source источник


Ответы (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];

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

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

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

person Tommy    schedule 05.07.2011

Как насчет этой 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];
person Kazuki Sakamoto    schedule 06.06.2011
comment
Вроде как сложный, доходит до уровня NSThread, но похоже, что это сработает (не пробовал). Тем не менее, похоже, что должен быть менее сложный способ сделать это ... - person jrdioko; 07.06.2011

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

ИЗМЕНИТЬ

- ой, читал внимательнее. Решение FIFO следующее:

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

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

  • создать рабочую очередь FIFO, которая:
    • supports locking
    • holds one NSOperationQueue
    • holds an NSOperation subclass, designed to pull workers from the fifo queue in its implementation of main. only one may exist at a time.
    • holds an NSArray of workers to be run (defining a worker is up to you - is it an NSInvocation, class, operation, ...)

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

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

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

удачи

person justin    schedule 27.05.2011
comment
Цитируемое мной предложение находится в абзаце, объясняющем, как вы можете сериализовать NSOperationQueue, установив максимальное количество одновременных операций равным 1. Но кажется, что, в отличие от очереди последовательной отправки, нет гарантии, что задачи будут выполнены в порядке очереди. . - person jrdioko; 28.05.2011
comment
@jrdioko, вы можете создать подкласс NSOperationQueue, который гарантирует, что ни одна из его добавленных операций не имеет никаких зависимостей ... и действительно, если вы явно не устанавливаете их сами, вы гарантируете, что это будет FIFO . - person Dave DeLong; 28.05.2011
comment
Гарантированно ли это будет FIFO, если у меня нет зависимостей / приоритетов? Я не вижу этого в документации. - person jrdioko; 28.05.2011
comment
да, но вы должны так спроектировать. клиенты добавляют воркеров в очередь FIFO worker. затем очередь рабочих процессов fifo передает рабочих подклассу NSOperation до тех пор, пока у него не останется рабочих для продажи. в это время подкласс NSOperation сообщает рабочей очереди fifo о выходе. рабочая очередь fifo гарантирует, что в любой момент времени выполняется только один (или ноль) подклассов NSOperation. спроектируйте рабочих таким образом, чтобы у них не было поддержки зависимостей или приоритета. это бесплатно, если вы напишете своего рабочего или используете NSInvocation. если вы выберете NSOperations для (продолжение) - person justin; 28.05.2011
comment
(продолжение) в качестве ваших работников, тогда им может потребоваться приоритет или зависимость. обратите внимание, что подкласс NSOperation, который извлекает рабочих из очереди рабочих процессов fifo, не является рабочим. используя NSInvocation в качестве рабочего, подкласс NSOperation говорит, что рабочая очередь fifo дает мне следующий запуск NSInvocation. вы также можете значительно уменьшить сложность этого дизайна (200 строк?), если знаете, что вам нужно только создать массив рабочих, которые должны выполняться по порядку, а не полагаться на центральную очередь рабочих, которая предназначена для вставки в произвольные моменты времени. - person justin; 28.05.2011

Есть вещи, о которых автор документации 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];
}

Вот и все!

person TheBlack    schedule 03.06.2011
comment
Выглядит хорошо, если все операции добавляются одновременно, но я думаю о случае, когда операции произвольно добавляются в разное время из разных мест. - person jrdioko; 07.06.2011
comment
Неважно, 1 или 10 операций. Три операции делают разные вещи с одним и тем же объектом, потому что фрагментация задач не логична для этого конкретного примера, я вытащил код. Если вам нужно выполнить одну конкретную задачу для каждого объекта, продолжайте ... - person TheBlack; 07.06.2011

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

person Jeremy W. Sherman    schedule 02.06.2011
comment
Я могу вспомнить случаи, когда это должно быть строго в порядке. Установка подобных зависимостей работала бы, если бы все задачи были доступны и поставлены в очередь одновременно, но я думаю о произвольном добавлении их в будущем. - person jrdioko; 07.06.2011