【问题标题】:Asynchronous Recursive Calls using Box sdk - How to iterate all files in using iOS BOX sdk api使用 Box sdk 进行异步递归调用 - 如何使用 iOS BOX sdk api 迭代所有文件
【发布时间】:2023-03-09 07:39:01
【问题描述】:

我正在尝试迭代存储在 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];
}

【问题讨论】:

  • 请注意,您将并行启动大量请求。根据 Box API 的底层实现,这可能会或可能不会成为问题。如果 Box API 不限制同时网络请求的数量(例如
  • 想对我自己的评论发表评论:正如您在代码中实现的那样,使用dispatch_group_wait,我上面的陈述不正确。但是,上面的代码无论如何都不会那样工作。我已经给出了答案,这有点挑战性和乐趣。希望它有效;)
  • 我是 Box iOS SDK 的作者。我想提一下,在 API 回调中阻塞并不是一个好主意。 Box iOS SDK 在单个线程上执行所有完成/失败回调和 NSURLConnection 回调。阻塞此线程会阻止任何其他 API 调用进行。
  • 那么我如何枚举所有文件(某种类型的)

标签: ios objective-c recursion box-api


【解决方案1】:

可行办法的关键是做内循环,也就是

for (int i = 0; i &lt; numberOfItems; i++) {..}

顺序调用许多异步任务并且本身异步的构造。异步任务应该是一个块 (^),它将调用外部方法并捕获状态,以便可以以递归方式调用其外部方法 recursiveEnumerateFilesAtPath:

请注意,这不是我们从函数中知道的递归,其中函数在返回之前调用自身,并且状态保存在程序堆栈中。在这里,我们宁愿有一个迭代(异步循环)——堆栈上没有状态,相反,“递归”所需的状态保存在块中捕获的变量中,这些变量驻留在堆。

也就是说,你需要将“for循环”(for (int i = 0; i &lt; numberOfItems; i++) {..})转化为一些异步方法,例如:

- (void) forEach:(NSArray*)objects applyTask:(task_t) completion:(completion_t)completionHandler;

其中 task 是异步的,具有完成处理程序,并且(有条件地)调用外部方法 recursiveEnumerateFilesAtPath:

这个forEach 构造可以使用NSOperationQueue 来实现,其最大并发操作数设置为1,其中任务是带有完成处理程序的NSOperation。或者 - 在这种情况下更简单,它可以使用调度队列和调度组来实现。或者 - 更简单的是,它可以使用“异步循环”来实现。我将展示一个可能的实现,请参阅下面的“NSArray Category forEachApplyTask:completion:”。

现在,假设 NSArray 有一个具有异步 forEachApplyTask:completion: 方法的类别:

@interface NSArray (AsyncExtension)
- (void) forEachApplyTask:(unary_async_t)task completion:(completion_t) completion;
@end

此方法将unary_async_t 类型的块作为第一个参数:

typedef void (^unary_async_t)(id input, completion_t completion);

和一个完成处理程序作为第二个参数:

typedef void (^completion_t)(id result);

完成处理程序的这种 typedef 是通用,可用于所有完成处理程序变体。

假设,块 task 将按顺序应用于接收器(数组)中的每个元素。假设,这个数组是原始代码中的一个“模型”数组——你只需要从BoxCollection 对象中创建一个“模型”数组。

完成后,完成处理程序传递一个结果,该结果是一个数组,其中包含每个任务的结果,顺序相同。

此任务执行您以前的 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");
            }
        }];
    }];
}

注意:未经测试,您可能会遇到问题!

请注意,一切都是异步运行的,并且没有将其状态放入堆栈的递归。递归迭代文件夹所需的状态放在堆上,包含在块中捕获的变量中。


NSArray Category 方法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

另请参阅:https://gist.github.com/couchdeveloper/6155227

【讨论】:

  • 感谢 CouchDeveloper 的解决方案。我怎么知道所有的项目都被枚举了?
  • @niks 如果您使用async_forEachApplyTask::当接收器(数组)中的所有项目都已通过应用转换函数(或块)进行处理时,将调用完成处理程序。完成处理程序将获得一个参数,该参数是一个数组,其中包含接收器中每个元素的转换函数的结果。例如,让接收者是一个 URL 数组。让转换函数成为异步加载,其结果是来自 URL 请求的 NSData - 如果出现问题,则为 NSError。在完成处理程序中,您可以遍历结果列表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-29
  • 1970-01-01
  • 2014-08-21
  • 2020-08-29
  • 1970-01-01
  • 2014-02-07
相关资源
最近更新 更多