我有一些需要使用块的代码。该块从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);
}];
答案 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
的工作是通过这个块,你做了三件事:
recursiveBlockVehicle
并将该结果作为参数传递给块现在,在你的块代码中,如果你要调用特殊的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
引用noteStore
,noteStore
引用块,因此存在保留周期。但是当块完成执行时,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.