Как выполнить структурированное программирование с использованием блоков в Objective-C

При использовании методов, которые возвращают блоки, они могут быть очень удобными. Тем не менее, когда вам нужно объединить несколько из них, это быстро становится беспорядочным.

например, вы должны вызывать 4 URL-адреса подряд:

[remoteAPIWithURL:url1 success:^(int status){
    [remoteAPIWithURL:url2 success:^(int status){
        [remoteAPIWithURL:url3 success:^(int status){
            [remoteAPIWithURL:url2 success:^(int status){
            //succes!!!
            }];
        }];
    }];
}];

Итак, для каждой итерации я иду на один уровень глубже, и я даже не обрабатываю ошибки во вложенных блоках.

Ухудшается, когда есть реальный цикл. Например, скажем, я хочу загрузить файл в 100 кусков:

- (void) continueUploadWithBlockNr:(int)blockNr
{
    if(blocknr>=100) 
    {
    //success!!!
    }
    [remoteAPIUploadFile:file withBlockNr:blockNr success:^(int status)
    {
        [self continueUploadWithBlockNr:blockNr];
    }];
}

Это кажется очень неинтуитивным и очень быстро читается.

В .Net они решили все это, используя ключевое слово async и wait, в основном разворачивая эти продолжения в кажущийся синхронный поток.

Какова наилучшая практика в Objective C?

Ответ 1

Ваш вопрос сразу заставил меня подумать о рекурсии. Оказывается, Objective-c блоки могут использоваться в рекурсии. Поэтому я придумал следующее решение, которое легко понять и может масштабировать до N задач довольно красиво.

// __block declaration of the block makes it possible to call the block from within itself
__block void (^urlFetchBlock)();

// Neatly aggregate all the urls you wish to fetch
NSArray *urlArray = @[
    [NSURL URLWithString:@"http://www.google.com"],
    [NSURL URLWithString:@"http://www.stackoverflow.com"],
    [NSURL URLWithString:@"http://www.bing.com"],
    [NSURL URLWithString:@"http://www.apple.com"]
];
__block int urlIndex = 0;

// the 'recursive' block 
urlFetchBlock = [^void () {
    if (urlIndex < (int)[urlArray count]){
        [self remoteAPIWithURL:[urlArray objectAtIndex:index] 
            success:^(int theStatus){
                urlIndex++;
                urlFetchBlock();
            }

            failure:^(){
                // handle error. 
            }];
    }
} copy];

// initiate the url requests
urlFetchBlock();

Ответ 2

Одним из способов уменьшения вложенности является определение методов, возвращающих отдельные блоки. Чтобы облегчить обмен данными, который делается "автоматически-магически" компилятором Objective C через закрытие, вам нужно будет определить отдельный класс для хранения общего состояния.

Вот приблизительный эскиз того, как это можно сделать:

typedef void (^WithStatus)(int);

@interface AsyncHandler : NSObject {
    NSString *_sharedString;
    NSURL *_innerUrl;
    NSURL *_middleUrl;
    WithStatus _innermostBlock;
}
+(void)handleRequest:(WithStatus)innermostBlock
            outerUrl:(NSURL*)outerUrl
            middleUrl:(NSURL*)middleUrl
            innerUrl:(NSURL*)innerUrl;

-(WithStatus)outerBlock;

-(WithStatus)middleBlock;

@end

@implementation AsyncHandler

+(void)handleRequest:(WithStatus)innermostBlock
            outerUrl:(NSURL*)outerUrl
            middleUrl:(NSURL*)middleUrl
            innerUrl:(NSURL*)innerUrl {
    AsyncHandler *h = [[AsyncHandler alloc] init];
    h->_innermostBlock = innermostBlock;
    h->_innerUrl = innerUrl;
    h->_middleUrl = middleUrl;
    [remoteAPIWithURL:outerUrl success:[self outerBlock]];
}

-(WithStatus)outerBlock {
    return ^(int success) {
        _sharedString = [NSString stringWithFormat:@"Outer: %i", success];
        [remoteAPIWithURL:_middleUrl success:[self middleBlock]];
    };
}

-(WithStatus)middleBlock {
    return ^(int success) {
        NSLog("Shared string: %@", _sharedString);
        [remoteAPIWithURL:_innerUrl success:_innermostBlock];
    };
}

@end

Примечание. Все это предполагает ARC; если вы компилируете без него, вам нужно использовать Block_copy в методах, возвращающих блоки. Вам также нужно будет сделать копию в вызывающем коде ниже.

Теперь ваша оригинальная функция может быть переписана без "Русская кукла" , как это:

[AsyncHandler
    handleRequest:^(int status){
        //succes!!!
    }
    outerUrl:[NSURL @"http://my.first.url.com"]
    middleUrl:[NSURL @"http://my.second.url.com"]
    innerUrl:[NSURL @"http://my.third.url.com"]
];

Ответ 3

Итеративный алгоритм:

  • Создайте переменную __block (int urlNum), чтобы отслеживать текущий URL-адрес (внутри NSArray).
  • Если блок onUrlComplete отключит следующий запрос до тех пор, пока не будут загружены все URL-адреса.
  • Оставьте первый запрос.
  • Когда все URL-адреса загружены, выполните "//success!". танец.

Код, написанный без помощи XCode (что означает ошибки компилятора, при необходимости исправит):

- (void)loadUrlsAsynchronouslyIterative:(NSArray *)urls {
  __block int urlNum = 0;
  void(^onUrlComplete)(int) = nil; //I don't remember if you can call a block from inside itself.
  onUrlComplete = ^(int status) {
    if (urlNum < urls.count) {
      id nextUrl = urls[urlNum++];
      [remoteAPIWithURL:nextUrl success:onUrlComplete];
    } else {
      //success!
    }
  }
  onUrlComplete(0); //fire first request
}

Рекурсивный алгоритм:

  • Создайте способ загрузки всех остальных URL-адресов.
  • Если оставшиеся URL-адреса пустые, запустите "onSuccess".
  • В противном случае, запрос на пожар для следующего URL-адреса и предоставить блок завершения, который рекурсивно вызывает метод со всеми, кроме первых оставшихся URL-адресов.
  • Осложнения: мы объявили блок "onSuccess" для принятия параметра int status, поэтому мы передаем последнюю переменную статуса вниз (включая значение по умолчанию).

Код, написанный без помощи XCode (здесь указывается отказ от ответственности):

- (void)loadUrlsAsynchronouslyRecursive:(NSArray *)remainingUrls onSuccess:(void(^)(int status))onSuccess lastStatus:(int)lastStatus {
  if (remainingUrls.count == 0) {
    onSuccess(lastStatus);
    return;
  }
  id nextUrl = remainingUrls[0];
  remainingUrls = [remainingUrls subarrayWithRange:NSMakeRange(1, remainingUrls.count-1)];
  [remoteAPIWithUrl:nextUrl onSuccess:^(int status) {
    [self loadUrlsAsynchronouslyRecursive:remainingUrls onSuccess:onSuccess lastStatus:status];
  }];
}

//fire first request:
[self loadUrlsAsynchronouslyRecursive:urls onSuccess:^(int status) {
  //success here!
} lastStatus:0];

Что лучше?

  • Итеративный алгоритм прост и краток - если вам удобнее играть в игры с переменными и областями __block.
  • В качестве альтернативы рекурсивный алгоритм не требует переменных __block и довольно прост, так как рекурсивные алгоритмы идут.
  • Рекурсивная реализация более пригодна для повторного использования, итеративная (как реализовано).
  • Рекурсивный алгоритм может протекать (требуется ссылка на self), но есть несколько способов исправить это: сделать его функцией, использовать __weak id weakSelf = self; и т.д.

Насколько легко было бы добавить обработку ошибок?

  • Итеративную реализацию можно легко расширить, чтобы проверить значение status, причем стоимость блока onUrlComplete становится более сложной.
  • Рекурсивная реализация, возможно, не так прост в распространении - в первую очередь потому, что она повторно используется. Вы хотите отменить загрузку большего количества URL-адресов, когда статус такой-то? Затем передайте блок проверки состояния/обработки ошибок, который принимает int status и возвращает BOOL (например, YES для продолжения, NO для отмены). Или, возможно, измените onSuccess, чтобы принять как int status, так и NSArray *remainingUrls - но вам нужно вызвать loadUrlsAsynchronouslyRecursive... в реализации блока onSuccess.

Ответ 4

Вы сказали (в комментарии): "Асинхронные методы предлагают легкую асинхронизацию без использования явных потоков". Но, похоже, ваша жалоба заключается в том, что вы пытаетесь сделать что-то с асинхронными методами, и это нелегко. Вы видите противоречие здесь?

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

Поэтому я предлагаю вам прекратить использовать обратный вызов. Grand Central Dispatch (GCD) упрощает (это слово снова!), Чтобы выполнить работу "в фоновом режиме", а затем перезвонить в основной поток, чтобы обновить пользовательский интерфейс. Поэтому, если у вас есть синхронная версия вашего API, просто используйте ее в фоновом режиме:

- (void)interactWithRemoteAPI:(id<RemoteAPI>)remoteAPI {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // This block runs on a background queue, so it doesn't block the main thread.
        // But it can't touch the user interface.

        for (NSURL *url in @[url1, url2, url3, url4]) {
            int status = [remoteAPI syncRequestWithURL:url];
            if (status != 0) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    // This block runs on the main thread, so it can update the
                    // user interface.
                    [self remoteRequestFailedWithURL:url status:status];
                });
                return;
            }
        }
    });
}

Поскольку мы просто используем обычный поток управления, просто сделать более сложные вещи. Скажем, нам нужно выпустить два запроса, затем загрузить файл в кусках не более 100k, а затем выдать еще один запрос:

#define AsyncToMain(Block) dispatch_async(dispatch_get_main_queue(), Block)

- (void)uploadFile:(NSFileHandle *)fileHandle withRemoteAPI:(id<RemoteAPI>)remoteAPI {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        int status = [remoteAPI syncRequestWithURL:url1];
        if (status != 0) {
            AsyncToMain(^{ [self remoteRequestFailedWithURL:url1 status:status]; });
            return;
        }

        status = [remoteAPI syncRequestWithURL:url2];
        if (status != 0) {
            AsyncToMain(^{ [self remoteRequestFailedWithURL:url2 status:status]; });
            return;
        }

        while (1) {
            // Manage an autorelease pool to avoid accumulating all of the
            // 100k chunks in memory simultaneously.
            @autoreleasepool {
                NSData *chunk = [fileHandle readDataOfLength:100 * 1024];
                if (chunk.length == 0)
                    break;
                status = [remoteAPI syncUploadChunk:chunk];
                if (status != 0) {
                    AsyncToMain(^{ [self sendChunkFailedWithStatus:status]; });
                    return;
                }
            }
        }

        status = [remoteAPI syncRequestWithURL:url4];
        if (status != 0) {
            AsyncToMain(^{ [self remoteRequestFailedWithURL:url4 status:status]; });
            return;
        }

        AsyncToMain(^{ [self uploadFileSucceeded]; });
    });
}

Теперь я уверен, что вы говорите "О да, это отлично выглядит".; ^) Но вы также можете сказать: "Что, если RemoteAPI имеет только асинхронные методы, а не синхронные методы?"

Мы можем использовать GCD для создания синхронной обертки для асинхронного метода. Нам нужно заставить оболочку вызывать метод async, а затем блокировать до тех пор, пока метод async не вызовет обратный вызов. Сложный бит заключается в том, что, возможно, мы не знаем, в какой очереди используется метод async для вызова обратного вызова, и мы не знаем, использует ли он dispatch_sync для вызова обратного вызова. Поэтому позвольте быть безопасным, вызывая метод async из параллельной очереди.

- (int)syncRequestWithRemoteAPI:(id<RemoteAPI>)remoteAPI url:(NSURL *)url {
    __block int outerStatus;
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    [remoteAPI asyncRequestWithURL:url completion:^(int status) {
        outerStatus = status;
        dispatch_semaphore_signal(sem);
    }];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    dispatch_release(sem);
    return outerStatus;
}

UPDATE

Сначала я отвечу на ваш третий комментарий, а второй второй комментарий.

Третий комментарий

Ваш третий комментарий:

Последнее, но не менее важное: ваше решение посвятить отдельный поток для обертывания синхронной версии вызова более дорогостоящей, чем использование альтернативных асинхронных версий. Thread - дорогостоящий ресурс, и когда он блокирует вас, вы потеряли один поток. Асинхронные вызовы (по крайней мере, в библиотеках ОС) обычно обрабатываются гораздо более эффективным способом. (Например, если вы будете запрашивать 10 URL-адресов одновременно, скорее всего, он не будет разворачивать 10 потоков (или помещать их в threadpool))

Да, использование потока дороже, чем просто использование асинхронного вызова. И что? Вопрос в том, слишком ли это дорого. Objective-C сообщения слишком дороги в некоторых сценариях на текущем оборудовании iOS (например, внутренние петли распознавания лиц в реальном времени или алгоритм распознавания речи), но я не испытываю никаких проблем с их использованием в большинстве случаев.

Действительно ли поток является "дорогостоящим ресурсом", зависит от контекста. Рассмотрим ваш пример: "Например, если вы будете запрашивать 10 URL-адресов одновременно, скорее всего, он не будет разворачивать 10 потоков (или помещать их в threadpool)". Давайте узнаем.

NSURL *url = [NSURL URLWithString:@"http://1.1.1.1/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
for (int i = 0; i < 10; ++i) {
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        NSLog(@"response=%@ error=%@", response, error);
    }];
}

Итак, я использую собственный рекомендованный Apple метод +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:] для асинхронного отправки 10 запросов. Я выбрал, что URL-адрес не реагирует, поэтому я могу точно определить, какую стратегию потоков/очередей Apple использует для реализации этого метода. Я запустил приложение на своем iPhone 4S под управлением iOS 6.0.1, остановился в отладчике и сделал снимок экрана "Навигатор потоков":

10 NSURLConnection sendAsynchronousRequest: threads

Вы можете видеть, что существует 10 потоков с меткой com.apple.root.default-priority. Я открыл три из них, так что вы можете видеть, что это обычные потоки очереди GCD. Каждый из них вызывает блок, определенный в +[NSURLConnection sendAsynchronousRequest:…], который просто поворачивается и вызывает +[NSURLConnection sendSynchronousRequest:…]. Я проверил все 10, и все они имеют одну и ту же трассировку стека. Итак, на самом деле, в OS-библиотеке имеется 10 потоков.

Я столкнулся с числом циклов от 10 до 100 и обнаружил, что GCD закрывает количество потоков com.apple.root.default-priority на 64. Поэтому я предполагаю, что остальные 36 запросов, которые я выдал, помещены в очередь в очереди с приоритетом по умолчанию и выиграли 't даже начать выполнение до тех пор, пока не завершится выполнение некоторых из 64 "запущенных" запросов.

Итак, слишком ли дорого использовать поток для превращения асинхронной функции в синхронную функцию? Я бы сказал, это зависит от того, сколько из них вы планируете делать одновременно. Я бы не сомневался, если число до 10 или даже 20.

Второй комментарий

Что приводит меня ко второму комментарию:

Однако, когда у вас есть: делайте эти 3 вещи одновременно, и когда "все" из них закончены, игнорируйте остальных и выполняйте эти 3 вызова одновременно и когда "все" из них заканчиваются,.

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

Сначала мы сделаем typedef для блока завершения удаленного API, просто чтобы сохранить ввод позже:

typedef void (^RemoteAPICompletionBlock)(int status);

Я запустил поток управления так же, как и раньше, переместив его из основного потока в параллельную очередь:

- (void)complexFlowWithRemoteAPI:(id<RemoteAPI>)remoteAPI {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

Сначала мы хотим выпустить три запроса одновременно и дождаться, пока один из них преуспеет (или, предположительно, для всех трех, чтобы сбой).

Итак, скажем, у нас есть функция statusOfFirstRequestToSucceed, которая выдает любое количество асинхронных запросов удаленного API и ждет, когда первое будет успешным. Эта функция предоставит блок завершения для каждого асинхронного запроса. Но разные запросы могут принимать разные аргументы... как мы можем передавать запросы API к функции?

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

        int status = statusOfFirstRequestToSucceed(@[
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI requestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI anotherRequestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI thirdRequestWithCompletion:completion];
            }
        ]);
        if (status != 0) {
            AsyncToMain(^{ [self complexFlowFailedOnFirstRoundWithStatus:status]; });
            return;
        }

ОК, теперь мы выпустили три первых параллельных запроса и ждали, когда это удастся, или для всех из них сбой. Теперь мы хотим выпустить еще три параллельных запроса и ждать, пока все удастся, или для одного из них не удастся. Таким образом, он почти идентичен, за исключением того, что я собираюсь принять функцию statusOfFirstRequestToFail:

        status = statusOfFirstRequestToFail(@[
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI requestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI anotherRequestWithCompletion:completion];
            },
            ^(RemoteAPICompletionBlock completion) {
                [remoteAPI thirdRequestWithCompletion:completion];
            }
        ]);
        if (status != 0) {
            AsyncToMain(^{ [self complexFlowFailedOnSecondRoundWithStatus:status]; });
            return;
        }

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

        [self complexFlowSucceeded];
    });
}

В целом, это похоже на довольно простой поток контроля для меня, и нам просто нужно реализовать statusOfFirstRequestToSucceed и statusOfFirstRequestToFail. Мы можем реализовать их без дополнительных потоков. Поскольку они настолько схожи, мы сделаем так, чтобы они оба вызывали вспомогательную функцию, которая выполняет реальную работу:

static int statusOfFirstRequestToSucceed(NSArray *requestBlocks) {
    return statusOfFirstRequestWithStatusPassingTest(requestBlocks, ^BOOL (int status) {
        return status == 0;
    });
}

static int statusOfFirstRequestToFail(NSArray *requestBlocks) {
    return statusOfFirstRequestWithStatusPassingTest(requestBlocks, ^BOOL (int status) {
        return status != 0;
    });
}

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

static int statusOfFirstRequestWithStatusPassingTest(NSArray *requestBlocks,
    BOOL (^statusTest)(int status))
{
    dispatch_queue_t completionQueue = dispatch_queue_create("remote API completion", 0);

Обратите внимание, что я буду класть только блоки на completionQueue с помощью dispatch_sync, а dispatch_sync всегда запускает блок в текущем потоке, если очередь не является основной очередью.

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

    dispatch_semaphore_t enoughJobsCompleteSemaphore = dispatch_semaphore_create(0);

Я буду отслеживать количество заданий, которые еще не закончены, и статус последнего задания:

    __block int jobsLeft = requestBlocks.count;
    __block int outerStatus = 0;

Когда jobsLeft становится 0, это означает, что либо я установил outerStatus в состояние, которое проходит тест, либо что все задания завершены. Здесь блок завершения, где я буду работать, отслеживая, буду ли я ждать. Я делаю все это на completionQueue для сериализации доступа к jobsLeft и outerStatus, если удаленный API отправляет несколько блоков завершения параллельно (в отдельных потоках или в параллельной очереди):

    RemoteAPICompletionBlock completionBlock = ^(int status) {
        dispatch_sync(completionQueue, ^{

Я проверяю, будет ли внешняя функция все еще ждать завершения текущего задания:

            if (jobsLeft == 0) {
                // The outer function has already returned.
                return;
            }

Затем я уменьшаю количество оставшихся заданий и делаю статус завершенного задания доступным для внешней функции:

            --jobsLeft;
            outerStatus = status;

Если заполненный статус задания проходит тест, я устанавливаю jobsLeft на ноль, чтобы другие задания не перезаписывали мой статус или не выделяли внешнюю функцию:

            if (statusTest(status)) {
                // We have a winner.  Prevent other jobs from overwriting my status.
                jobsLeft = 0;
            }

Если нет заданий, оставшихся ждать (потому что они все закончили или потому что этот статус задания прошел тест), я разбужу внешнюю функцию:

            if (jobsLeft == 0) {
                dispatch_semaphore_signal(enoughJobsCompleteSemaphore);
            }

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

            dispatch_release(completionQueue);
            dispatch_release(enoughJobsCompleteSemaphore);
        });
    };

Это конец блока завершения. Остальная функция тривиальна. Сначала я выполняю каждый блок запроса, и я сохраняю очередь и семафор, чтобы предотвратить оборванные ссылки:

    for (void (^requestBlock)(RemoteAPICompletionBlock) in requestBlocks) {
        dispatch_retain(completionQueue); // balanced in completionBlock
        dispatch_retain(enoughJobsCompleteSemaphore); // balanced in completionBlock
        requestBlock(completionBlock);
    }

Обратите внимание, что сохранение не обязательно, если вы используете ARC, а целью развертывания является iOS 6.0 или новее.

Затем я просто жду, когда одно из заданий разбудит меня, отпустит очередь и семафор и вернет статус заданий, которые разбудили меня:

    dispatch_semaphore_wait(enoughJobsCompleteSemaphore, DISPATCH_TIME_FOREVER);
    dispatch_release(completionQueue);
    dispatch_release(enoughJobsCompleteSemaphore);
    return outerStatus;
}

Обратите внимание, что структура statusOfFirstRequestWithStatusPassingTest является довольно общей: вы можете передать любые блоки запросов, которые вы хотите, до тех пор, пока каждый вызывает блок завершения и переходит в статус int. Вы можете изменить функцию для обработки более сложного результата из каждого блока запроса или отменить невыполненные запросы (если у вас есть API отмены).

Ответ 5

При этом я сам столкнулся с портом Reactive Extensions до Objective-C. Реактивные расширения похожи на возможность запросить набор событий или асинхронные операции. Я знаю, что он имел большое понимание в .Net и JavaScript, и теперь, по-видимому, есть порт для Objective-C, а также

https://github.com/blog/1107-reactivecocoa-for-a-better-world

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

Ответ 6

Я склонен обертывать большой вложенный блок-кластер f ****, как вы описываете в подклассах NSOperation, которые описывают, какое поведение в целом делает ваш большой блок-кластер f *** (вместо того, чтобы оставлять их замусоренными во всех других код).

Например, если ваш следующий код:

[remoteAPIWithURL:url1 success:^(int status){
    [remoteAPIWithURL:url2 success:^(int status){
        [remoteAPIWithURL:url3 success:^(int status){
            [remoteAPIWithURL:url2 success:^(int status){
            //succes!!!
            }];
        }];
    }];
}];

предназначен для получения токена авторизации, а затем синхронизации чего-то, возможно, будет NSAuthorizedSyncOperation... Я уверен, что вы получите суть. Преимущества этого - хорошие аккуратные пакеты поведения, завернутые в класс, с одним местом для их редактирования, если все изменится по строке. Мои 2 ¢.

Ответ 7

Не уверен, хотите ли вы, где вы ищете? Хотя все объекты в массиве нуждаются в разном времени, чтобы завершить все, отображаются в порядке, где они отправлены в очередь.

typedef int(^SumUpTill)(int);
SumUpTill sum = ^(int max){
    int i = 0;
    int result = 0;
    while (i < max) {
        result += i++;
    }
    return result;
};

dispatch_queue_t queue = dispatch_queue_create("com.dispatch.barrier.async", DISPATCH_QUEUE_CONCURRENT);
NSArray *urlArray = @[  [NSURL URLWithString:@"http://www.google.com"],
                        @"Test",
                        [sum copy],
                        [NSURL URLWithString:@"http://www.apple.com"]
];

[urlArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    dispatch_barrier_async(queue, ^{
        if ([obj isKindOfClass:[NSURL class]]) {
            NSURLRequest *request = [NSURLRequest requestWithURL:obj];
            NSURLResponse *response = nil;
            NSError *error = nil;
            [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
            NSLog(@"index = %d, response=%@ error=%@", idx, response, error);
        }
        else if ([obj isKindOfClass:[NSString class]]) {
            NSLog(@"index = %d, string %@", idx, obj);
        }
        else {
            NSInteger result = ((SumUpTill)obj)(1000000);
            NSLog(@"index = %d, result = %d", idx, result);
        }
    });
}];

Ответ 8

В NSDocument для сериализации доступны следующие методы:

Serialization
– continueActivityUsingBlock:
– continueAsynchronousWorkOnMainThreadUsingBlock:
– performActivityWithSynchronousWaiting:usingBlock:
– performAsynchronousFileAccessUsingBlock:
– performSynchronousFileAccessUsingBlock:

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