我在使用大批量数据创建CKQuery操作时遇到问题。我的查询使用100个结果,但是在更多结果查询失败之后,因为一个线程被调度错误或者某些东西(libdispatch.dylib`dispatch_group_leave: 我迷路了......任何想法?
+ (void) fetchAnswersWithRecordId:(CKRecordID *)recordId completionHandler:(CloudKitCompletionHandler)handler {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANSrecordID == %@", recordId];
CKQuery *query = [[CKQuery alloc] initWithRecordType:ckAnswers predicate:predicate];
CKQueryOperation *operation = [[CKQueryOperation alloc] initWithQuery:query];
CKQueryOperation * __weak weakSelf = operation;
operation.resultsLimit = 300;
NSMutableArray *tmp = [[NSMutableArray alloc] init];
operation.recordFetchedBlock = ^(CKRecord *record) {
[tmp addObject:record];
};
operation.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error) {
if (!handler)
return;
NSArray *array = [NSArray arrayWithArray:tmp];
if(cursor != nil) {
CKQueryOperation *newOperation = [[CKQueryOperation alloc] initWithCursor:cursor];
newOperation.recordFetchedBlock = weakSelf.recordFetchedBlock;
newOperation.completionBlock = weakSelf.completionBlock;
[[self publicCloudDatabase] addOperation:newOperation];
} else {
NSLog(@"Results: %lu", [array count]);
dispatch_async(dispatch_get_main_queue(), ^{
handler(array, error);
});
}
};
[[self publicCloudDatabase] addOperation:operation];}
答案 0 :(得分:2)
我认为你的问题在于__weak操作以及在另一个操作中创建操作的方式。下面是一个示例(在swift中)我如何做类似的事情,即获得额外的结果,但在获取而不是查询。注意使用实例变量初始化第一次和通过GCD dispatch_aync使用半递归:
public TestViewModel(Test test)
{
var foo = test;
calculated = foo.price * 2;
}
答案 1 :(得分:2)
仅供参考,dispatch_async()
没有必要,这是一个内存管理问题。以下适用于多批次提取:
//
__block CKQueryOperation* enumerateOperationActive = nil;
//
NSPredicate* predicate = [NSPredicate predicateWithValue:TRUE];
CKQuery* query = [[[CKQuery alloc] initWithRecordType:@"myType" predicate:predicate] autorelease];
CKQueryOperation* enumerateOperation = [[[CKQueryOperation alloc] initWithQuery:query] autorelease];
// DEBUG: fetch only 1 record in order to "force" a nested CKQueryOperation cycle
enumerateOperation.resultsLimit = 1;
enumerateOperation.recordFetchedBlock = ^(CKRecord* recordFetched)
{
// ...
};
enumerateOperation.queryCompletionBlock = ^(CKQueryCursor* cursor, NSError* error)
{
if (error)
{
// ...
}
else
{
if (cursor)
{
CKQueryOperation* enumerateOperationNested = [[[CKQueryOperation alloc] initWithCursor:cursor] autorelease];
// DEBUG: fetch only 1 record in order to "force" a doubly-nested CKQueryOperation cycle
enumerateOperationNested.resultsLimit = 1;
enumerateOperationNested.recordFetchedBlock = /* re-used */ enumerateOperationActive.recordFetchedBlock;
enumerateOperationNested.queryCompletionBlock = /* re-used */ enumerateOperationActive.queryCompletionBlock;
// CRITICAL: keep track of the very last (active) operation
enumerateOperationActive = enumerateOperationNested;
[database addOperation:enumerateOperationNested];
}
}
};
//
// CRITICAL: keep track of the very last (active) operation
enumerateOperationActive = enumerateOperation;
[database addOperation:enumerateOperation];
注意:如果您尝试访问(原始)enumerateOperation.queryCompletionBlock
而不是(最后一个)enumerateOperationActive.queryCompletionBlock
,则操作永远不会完成。
答案 2 :(得分:0)
我的解决方案是一个使用两个操作但都使用相同块的类别,您可以为每个请求提供多少结果。
@interface CKDatabase (MH)
/* Convenience method for performing a query receiving the results in batches using multiple network calls. Best use max 400 for cursorResultsLimit otherwise server sometimes exceptions telling you to use max 400. Even using CKQueryOperationMaximumResults can cause this exception. */
- (void)mh_performCursoredQuery:(CKQuery *)query cursorResultsLimit:(int)cursorResultsLimit inZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(NSArray /* CKRecord */ *results, NSError *error))completionHandler;
@end
@implementation CKDatabase(MH)
- (void)mh_performCursoredQuery:(CKQuery *)query cursorResultsLimit:(int)cursorResultsLimit inZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(NSArray /* CKRecord */ *results, NSError *error))completionHandler{
//holds all the records received.
NSMutableArray* records = [NSMutableArray array];
//this block adds records to the result array
void (^recordFetchedBlock)(CKRecord *record) = ^void(CKRecord *record) {
[records addObject:record];
};
//this is used to allow the block to call itself recurively without causing a retain cycle.
void (^queryCompletionBlock)(CKQueryCursor *cursor, NSError *error)
__block __weak typeof(queryCompletionBlock) weakQueryCompletionBlock;
weakQueryCompletionBlock = queryCompletionBlock = ^void(CKQueryCursor *cursor, NSError *error) {
//if any error at all then end with no results. Note its possible that we got some results,
// and even might have got a cursor. However if there is an error then the cursor doesn't work properly so will just return with no results.
if(error){
completionHandler(nil,error);
}
else if(cursor){
CKQueryOperation* cursorOperation = [[CKQueryOperation alloc] initWithCursor:cursor];
cursorOperation.zoneID = zoneID;
cursorOperation.resultsLimit = cursorResultsLimit;
cursorOperation.recordFetchedBlock = recordFetchedBlock;
cursorOperation.queryCompletionBlock = weakQueryCompletionBlock; // use the weak pointer to prevent retain cycle
//start the recursive operation and return.
[self addOperation:cursorOperation];
}
else{
completionHandler(records,nil);
}
};
//start the initial operation.
CKQueryOperation* queryOperation = [[CKQueryOperation alloc] initWithQuery:query];
queryOperation.zoneID = zoneID;
queryOperation.resultsLimit = cursorResultsLimit;
queryOperation.recordFetchedBlock = recordFetchedBlock;
queryOperation.queryCompletionBlock = queryCompletionBlock;
[self addOperation:queryOperation];
}
@end