3

我有一些需要使用块的代码。该块从 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

4 回答 4

4

您应该创建一个引用该块的变量,以使递归调用成为可能。必须注意的是,在你分配块的那一刻,它仍然是nil,所以如果你在块本身内部调用它(也就是递归),你会在尝试执行nil块时崩溃。所以块应该有一个 *__block* 存储:

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 。

于 2013-07-01T21:58:54.383 回答
4

我更喜欢使用定点组合器结构来编写块递归。这样,当我忘记在递归结束时将块设置为 nil 时,我不必弄乱 __block 变量或冒着保留周期的风险。这一切归功于分享此代码片段的 Mike Ash 。

这是我的他的代码版本(我把它放在一个全局共享文件中,这样我就可以从任何地方访问这个函数):

// 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 封装在一个简单的块中并返回

现在,在你的块代码中,如果你要调用特殊的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

于 2013-07-02T00:28:34.253 回答
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释放块,打破保留周期。

于 2013-07-02T02:33:50.453 回答
1

这是(据我所知)-有点烦人的难题-也是积木的少数缺点之一……如果我真的想确保我会参考以下是我所指的首选原型我是安全的..

// 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. 
于 2013-07-02T00:11:49.757 回答