2013-07-01 86 views
2

我有一些代码需要使用块。该块从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); 
}]; 
+1

你如何知道你是否需要获取更多物品?你对这些物品做什么?这个过程如何开始呢?你在运行什么线程或队列?你用什么API从服务器获取项目? –

+0

它检查每个返回项目的日期。如果它早于特定日期,则需要在返回之前收集更多项目。 – Andrew

回答

4

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

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作为论据。

关于保留周期

为了避免保留周期,你应该将指针设置为到块时递归结束。

+0

对不起,我是新来的我自己的块。我已经读过一些东西,但我仍然有点生疏。你能解释一下这个方法吗?此外,它给了我一个错误,'块属性不允许,只允许本地变量'与__block在那里。 – Andrew

+1

确定一个块类似于一个函数指针,它可能被调用就像它是一个函数(* myBlock(objects)*)。在块内引用的每个指针都被复制(所以指针对象的保留计数会增加)。如果一个变量具有* __ block *说明符,则指针不会被复制,但它将在块内保持活动状态(类似于全局变量,但它在堆栈中)。现在在分配块的时刻,块变量仍然是* nil *,因此块内部引用的块将是* nil *,这就是为什么我使它具有* __ block *存储区分符。 –

+0

至于错误取决于你如何分配块,这个错误经常发生在你试图做* __ block *的时候,你是否刚刚复制了我的代码? –

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. 
+0

我还不确定我明白这一点。这怎么能适应我的代码(上面的新例子)? – Andrew

4

我更喜欢使用定点组合器结构来写块递归。这样,当我忘记在递归结束时将块设置为零时,我不必乱用__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个简单的块中,并返回

现在,你的块的代码中,如果你调用特殊recurse块参数,你又一遍呼唤自己的块(实现递归) 。这种策略的好处在于内存管理非常简单。使用参数将自己的代码传回给自己可以降低保留周期的风险。我使用这种方法,而不是定义我的代码的__block变量,因为恐怕我可能会忘记在递归结束时将__block变量设置为零,并导致令人讨厌的保留周期。

考虑到这一点,这是我将如何实现你的函数:

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变量的保留周期问题。然而,罗布Mayoff没有在这个问题上一个梦幻般的分析:https://stackoverflow.com/a/13091475/588253

+0

这工作,谢谢。我唯一的问题是'run(@ 0)'的目的是什么?'最后? – Andrew

+1

啊,对不起,这是一个简写。 @ 0转换为[NSNumber numberWithInt:0] ;.换句话说,@ 0转换为数值为0的NSNumber。目的是为偏移量定义一个起始点(即,在0处开始偏移量并将其每增加100个增量值)。这也可以写成:run([NSNumber numberWithInt:0]); –

+0

啊,完美。谢谢。 – Andrew

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释放块,打破保留周期。