我正在尝试迭代存储在 Box 帐户中的所有图像。
以下代码不正确,似乎找不到错误。
问题似乎是在等待所有异步递归调用完成,以便知道何时没有更多图像可获取以发出完成信号
-(void)enumerateFiles
{
[self recursiveEnumerateFilesAtPath:@"0" completion:nil];
}
-(void)recursiveEnumerateFilesAtPath:(NSString *)folderID completion:(void(^)())block
{
static NSArray *imageExtensions;
imageExtensions = @[@"jpg",@"jpeg",@"png"];
[self enumerateFilesAtPath:folderID completion:^(BoxCollection *collection) {
NSUInteger numberOfItems = collection.numberOfEntries;
for (int i = 0; i < numberOfItems; i++) {
id model = [collection modelAtIndex:i];
if ([model isKindOfClass:[BoxFolder class]])
{
BoxFolder *folder = (BoxFolder *)model;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self recursiveEnumerateFilesAtPath:folder.modelID completion:^{
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
} else if ([model isKindOfClass:[BoxItem class]])
{
BoxItem *item = (BoxItem *)model;
NSString *extension = [[item.name pathExtension] lowercaseString];
if ([imageExtensions containsObject:extension])
{
[self.items addObject:model];
}
}
}
if (block)
{
block();
}
}];
}
-(void)enumerateFilesAtPath:(NSString *)folderID completion:(void(^)(BoxCollection *collection))block
{
BoxCollectionBlock success = ^(BoxCollection *collection)
{
block(collection);
};
BoxAPIJSONFailureBlock failure = ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSDictionary *JSONDictionary)
{
block(nil);
};
[[BoxSDK sharedSDK].foldersManager folderItemsWithID:folderID requestBuilder:nil success:success failure:failure];
}
最佳答案
可行方法的关键是制作内循环,即
for (int i = 0; i < numberOfItems; i++) {..}
顺序调用多个异步任务并且本身是异步的。异步任务应该是一个 block ( ^
),它将调用外部方法并捕获状态,以便其外部方法 recursiveEnumerateFilesAtPath:
可以以递归方式调用。
请注意,这不是我们从函数中了解到的递归,其中函数在返回之前调用自身,并且状态保存在程序堆栈中。在这里,我们宁愿有一个迭代(异步循环)——堆栈上没有状态,相反,“递归”所需的状态保存在 block 中捕获的变量中,这些变量驻留在堆。
也就是说,您需要将“for 循环”(for (int i = 0; i < numberOfItems; i++) {..}
)转换为某种异步方法,例如:
- (void) forEach:(NSArray*)objects applyTask:(task_t) completion:(completion_t)completionHandler;
其中 task 是异步的,有一个完成处理程序,并且(有条件地)调用外部方法 recursiveEnumerateFilesAtPath:
.
这forEach
构造可以使用 NSOperationQueue
来实现,其最大并发操作设置为 1,任务是 NSOperation
带有完成处理程序。或者 - 在这种情况下更简单,它可以使用调度队列和调度组来实现。或者 - 甚至更简单,它可以使用“异步循环”来实现。我将展示一个可能的实现,请参阅下面的“NSArray 类别 forEachApplyTask:completion:
”。
现在,假设有一个 NSArray
的类别使用异步 forEachApplyTask:completion:
方法:
@interface NSArray (AsyncExtension)
- (void) forEachApplyTask:(unary_async_t)task completion:(completion_t) completion;
@end
此方法采用类型为 unary_async_t
的 block 作为第一个参数:
typedef void (^unary_async_t)(id input, completion_t completion);
和一个完成处理程序作为第二个参数:
typedef void (^completion_t)(id result);
这个完成处理程序的 typedef 是通用的,可用于所有完成处理程序变体。
假设 block 任务 将按顺序应用于接收器(数组)中的每个元素。假设这个数组是原始代码中的“模型”数组——您只需要从 BoxCollection
中创建一个“模型”数组。对象。
完成后,完成处理程序会传递一个结果,该结果是一个数组,其中包含按相同顺序排列的每个任务的结果。
此任务执行您之前的 {..}
中的 block ( for loop
) .
你的方法 recursiveEnumerateFilesAtPath:
将不可避免地异步(因为在其实现中调用异步方法),因此也获得完成处理程序参数。
根据上面给出的假设,您可以按如下所示实现您的问题:
-(void)recursiveEnumerateFilesAtPath:(NSString *)folderID
completion:(completion_t)finalCompletionHandler
{
[self enumerateFilesAtPath:folderID completion:^(BoxCollection *collection) {
NSArray* models = ...; // create an NSArray from the collection
[models forEachApplyTask:^(id model, completion_t taskCompletionHandler) {
if ([model isKindOfClass:[BoxFolder class]]) {
BoxFolder *folder = (BoxFolder *)model;
[self recursiveEnumerateFilesAtPath:folder.modelID completion:^(id result){
// result should be @"finished folder"
taskCompletionHandler(@"folder"); // taskCompletionHandler will never be nil
}];
}
else if ([model isKindOfClass:[BoxItem class]]) {
BoxItem *item = (BoxItem *)model;
NSString *extension = [[item.name pathExtension] lowercaseString];
if ([imageExtensions containsObject:extension]) {
[self.items addObject:model];
}
taskCompletionHandler(@"item");
}
}
completion:^(id array){
// this folder is finished. array may be for example @[@"folder", @"item", @"item"]
if (finalCompletionHandler) {
finalCompletionHandler(@"finished folder");
}
}];
}];
}
注意:未经测试,您可能会遇到问题!
请注意,一切都是异步运行的,并且没有将状态放在堆栈上的递归。递归迭代文件夹所需的状态放在堆上,包含在 block 中捕获的变量中。
NSArray 分类方法 forEachApplyTask:completion:
是一个可重用的组件,可以在很多情况下使用。可以按如下方式实现它:
类别NSArray的实现
/**
Objective:
Asynchronously transform or process an array of items - one after the
other and return the result of each transform in an array.
Synopsis:
void transform_each(NSArray* inArray, unary_async_t task, completion_t completion);
*/
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
/**
Typedef of a generic completion handler for an asynchronous task.
The parameter _result_ is the eventual result of the asynchronous
task. In case the task has been failed _result_ SHALL be an
NSError object.
*/
typedef void (^completion_t)(id result);
/**
Typedef for an asynchronous "transform" function. It's an
unary block taking an input as parameter and signals
the eventual result via a completion handler.
*/
typedef void (^unary_async_t)(id input, completion_t completion);
/**
`transform_each` sequentially applies an asynchronous transform function
to each object in the input array _inArray_ and signals the result as an
array containing the result of each transform applied to the input object.
Function `transform_each` is itself a asynchronous function, that is,
its eventual result will be signaled to the client through a completion
handler.
The result array contains the transformed objects in order of the
corresponding input array.
*/
void transform_each(NSArray* inArray, unary_async_t task, completion_t completion);
// implementation
static void do_each(NSEnumerator* iter, unary_async_t task, NSMutableArray* outArray, completion_t completion)
{
id obj = [iter nextObject];
if (obj == nil) {
if (completion)
completion([outArray copy]);
return;
}
task(obj, ^(id result){
[outArray addObject:result];
do_each(iter, task, outArray, completion);
});
}
void transform_each(NSArray* inArray, unary_async_t task, completion_t completion) {
NSMutableArray* outArray = [[NSMutableArray alloc] initWithCapacity:[inArray count]];
NSEnumerator* iter = [inArray objectEnumerator];
do_each(iter, task, outArray, completion);
}
/*******************************************************************************
Example
*******************************************************************************/
// A Category for NSArray
@interface NSArray (AsyncExtension)
- (void) async_forEachApplyTask:(unary_async_t) task completion:(completion_t) completion;
@end
@implementation NSArray (AsyncExtension)
- (void) async_forEachApplyTask:(unary_async_t) task completion:(completion_t) completion {
transform_each(self, task, completion);
}
@end
关于ios - 使用 Box sdk 的异步递归调用 - 如何使用 iOS BOX sdk api 迭代所有文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20495920/