2013-07-01 84 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釋放塊,打破保留週期。