如何使用块处理循环代码?

时间:2013-07-01 21:52:34

标签: ios objective-c block

我有一些需要使用块的代码。该块从Web服务中获取许多数据项,然后可能需要获取更多数据项,然后再获取更多数据项,然后在完全需要后返回所有数据项。我不确定如何将其放入代码中。这是我的意思的一个例子:

NSMutableArray *array = [[NSMutableArray alloc] init];

[webService getLatestItemsWithCount:50 completion:^(NSArray *objects) {
    //Some code to deal with these items.

    if (moreItemsNeeded == YES) {
        //I now need it to loop this block until I'm done
    }
}];

我怎样才能让它发挥作用?

编辑:

好的,这就是我正在使用的 - 它是Evernote API。它应该是我需要的更好的例子:

[noteStore findNotesMetadataWithFilter:filter
                                offset:0
                              maxNotes:100
                            resultSpec:resultSpec
                               success:^(EDAMNotesMetadataList *metadataList) {
    for (EDAMNoteMetadata *metadata in metadataList.notes) {
        NSDate *timestamp = [NSDate endateFromEDAMTimestamp:metadata.updated];

        if (timestamp.timeIntervalSince1970 > date.timeIntervalSince1970) {
            [array addObject:metadata];
        }
        else {
            arrayComplete = YES;
        }
    }

    //I need it to loop this code, increasing the offset, until the array is complete.

}failure:^(NSError *error) {
    NSLog(@"Failure: %@", error);
}];

4 个答案:

答案 0 :(得分:4)

您应该创建一个引用该块的变量,以便进行递归调用。必须注意的是,在你分配块时,它仍然是 nil ,所以如果你在块本身(也就是递归)中调用它,你会在尝试执行时遇到崩溃 nil 块。所以块应该有一个* __ block * storage:

void (^__block myBlock) (NSArray*) = ^(NSArray *objects) {
    //Some code to deal with these items.

    if (moreItemsNeeded == YES) {
        //I now need it to loop this block until I'm done
        myBlock(objects);
        myBlock= nil; // Avoid retain cycle
    }
}];
[webService getLatestItemsWithCount:50 completion: myBlock];

您特定情况下的区块被“翻译”为此区域:

void (^__block handler) (EDAMNotesMetadataList)= ^(EDAMNotesMetadataList* metadataList) {
    for (EDAMNoteMetadata *metadata in metadataList.notes) {
        NSDate *timestamp = [NSDate endateFromEDAMTimestamp:metadata.updated];

        if (timestamp.timeIntervalSince1970 > date.timeIntervalSince1970) {
            [array addObject:metadata];
        }
        else {
            arrayComplete = YES;
        }
    }

    //I need it to loop this code, increasing the offset, until the array is complete.
    if(!arrayComplete)
        handler(metadataList);
    handler= nil; // Avoid retain cycle
};

然后你通常可以调用该方法传递 myBlock 作为参数。

关于保留周期

为避免保留周期,在递归完成时,应将 nil 设置为指向块的指针。

答案 1 :(得分:4)

我更喜欢使用定点组合器结构来编写块递归。这样,当我忘记在递归结束时将块设置为nil时,我不必弄乱__block变量或冒隐藏周期。所有这一切归功于Mike Ash,他分享了这个code snippet

这是我的代码版本(我将其放在全局共享文件中,以便我可以从任何地方访问此函数):

// From Mike Ash's recursive block fixed-point-combinator strategy (https://gist.github.com/1254684)
dispatch_block_t recursiveBlockVehicle(void (^block)(dispatch_block_t recurse))
{
    // assuming ARC, so no explicit copy
    return ^{ block(recursiveBlockVehicle(block)); };
}

typedef void (^OneParameterBlock)(id parameter);
OneParameterBlock recursiveOneParameterBlockVehicle(void (^block)(OneParameterBlock recurse, id parameter))
{
    return ^(id parameter){ block(recursiveOneParameterBlockVehicle(block), parameter); };
}

我知道这看起来非常奇怪和令人困惑......但是一旦理解它就不会太糟糕。这是一个简单的递归块可能是这样的:

dispatch_block_t run = recursiveBlockVehicle(^(dispatch_block_t recurse) 
{
    if (! done)
    {
        // Continue recursion
        recurse();
    }
    else
    {
        // End of recursion
    }
});
run();

当您致电recursiveBlockVehicle时,您正在传递包含您的代码的块。 recursiveBlockVehicle的工作是通过这个块,你做了三件事:

  1. 执行块
  2. 将块传回recursiveBlockVehicle并将该结果作为参数传递给块
  3. 在一个简单的块中封装步骤1和2并返回
  4. 现在,在你的块代码中,如果你要调用特殊的recurse块参数,你又会重新调用你自己的块(实现递归)。这个策略的好处是内存管理非常简单。使用参数将您自己的代码传递给自己可以降低保留周期的风险。我使用这种方法而不是定义我的代码的__block变量,因为我担心我可能忘记在递归结束时将__block变量设置为nil并导致令人讨厌的保留周期。

    考虑到这一点,我将如何实现您的功能:

    OneParameterBlock run = recursiveOneParameterBlockVehicle(^(OneParameterBlock recurse, id parameter)
    {
        NSNumber *offset = parameter;
        [noteStore
            findNotesMetadataWithFilter:filter
            offset:offset.intValue
            maxNotes:100
            resultSpec:resultSpec
            success:^(EDAMNotesMetadataList *metadataList)
            {
                for (EDAMNoteMetadata *metadata in metadataList.notes)
                {
                    NSDate *timestamp = [NSDate endateFromEDAMTimestamp:metadata.updated];
                    if (timestamp.timeIntervalSince1970 > date.timeIntervalSince1970)
                    {
                        [array addObject:metadata];
                    }
                    else
                    {
                        arrayComplete = YES;
                    }
                }
    
                //I need it to loop this code, increasing the offset, until the array is complete.
                if (! arrayComplete)
                {
                    recurse([NSNumber numberWithInt:offset.intValue + 100]);
                }
            }
            failure:^(NSError *error)
            {
                NSLog(@"Failure: %@", error);
            }];
    });
    run(@0);
    

    再次注意,您没有在块本身内部调用callback(块对象)。原因是因为块将自身作为参数recurse传递,并且执行recurse是您实现递归的方式。

    另外,(如果你真的读过这篇文章并想看到更多),这里是FPC的维基百科页面:http://en.wikipedia.org/wiki/Fixed-point_combinator

    最后,我还没有亲自测试__block变量的保留周期问题。然而,Rob Mayoff对这个问题进行了精彩的分析:https://stackoverflow.com/a/13091475/588253

答案 2 :(得分:2)

如果您使块递归,您的代码将更容易理解并且更不容易泄漏块。相反,将它包装在一个方法中,如果需要继续搜索,则使块调用该方法。

此示例基于您问题中的代码:

- (void)appendNotesMetadataToArray:(NSMutableArray *)array
    untilDate:(NSDate *)date withFilter:(EDAMNoteFilter *)filter
    offset:(int32_t)offset resultSpec:(EDAMNotesMetadataResultSpec *)resultSpec
{
    static const int32_t kBatchSize = 100;

    [noteStore findNotesMetadataWithFilter:filter
        offset:offset maxNotes:kBatchSize resultSpec:resultSpec
        success:^(EDAMNotesMetadataList *metadataList) {
            BOOL searchComplete = NO;
            for (EDAMNoteMetadata *metadata in metadataList.notes) {
                NSDate *timestamp = [NSDate endDateFromEDAMTimestamp:metadata.updated];
                if ([timestamp compare:date] == NSOrderedDescending) {
                    [array addObject:metadata];
                } else {
                    searchComplete = YES;
                }
            }

            if (!searchComplete) {
                [self appendNotesMetadataToArray:array untilDate:date
                    withFilter:filter offset:offset + kBatchSize
                    resultSpec:resultSpec];
            }
        } failure:^(NSError *error) {
            NSLog(@"Failure: %@", error);
        }];
}

使用这种设计,您不需要使用不可思议的类型签名声明对块的引用,并且您不必担心块泄漏,因为它引用自身。

在此设计中,每次调用该方法都会创建一个新块。块引用self和(我假设)self引用noteStorenoteStore引用块,因此存在保留周期。但是当块完成执行时,noteStore释放块,打破保留周期。

答案 3 :(得分:1)

这是(据我所知) - 有点令人讨厌的connundrum - 以及其中一个块的缺点......如果我真的想要,以下是我所指的首选原型确保我对此感到安全..

// declare a "recursive" prototype you will refer to "inside" the block.
id __block (^enumerateAndAdd_recurse)(NSArray*);        
// define the block's function - like normal.
id         (^enumerateAndAdd)        (NSArray*) = ^(NSArray*kids){ 
   id collection = CollectionClass.new;
   for (ArrayLike* littleDarling in kids) 
       [collection add:enumerateAndAdd_recurse(littleDarling)];
   return collection;
};      
enumerateAndAdd_recurse = enumerateAndAdd; // alias the block "to itself" before calling.
enumerateAndAdd(something); // kicks it all off,  yay.