将 NSProgress 与嵌套的 NSOperations 结合使用



我一直在调查NSProgress但发现缺少现有的文档、类参考和教程。我主要想知道我的 NSProgress 是否适用于我的用例。类参考文档或者引用suboperationssubtasks,我可能弄错了,但我suboperations解释为NSOperation管理一组其他NSOperations的情况。我的用例示例如下:

  • 为存在的每个组创建Upload All Items in Group操作。
  • 将这些操作中的每一个添加到NSOperationQueue 中。
  • 每个Upload All Items in Group操作都将为其组中的每个项目创建一个Upload Item操作。这些都将添加到由操作管理的NSOperationQueue中。

我希望NSProgress支持这一点,并允许我将进度从嵌套操作(Upload Item操作(传播到父操作,最后传播到主线程和 UI。但是我在实现这一点时遇到了困难,似乎NSProgress更多的是在一个后台线程上执行所有代码的长操作,但有单独的"部分",可以很容易地确定何时取得进展,如果是这种情况,那么术语 suboperation 的使用有点误导,因为它让人想起嵌套NSOperations的使用。

感谢您提供的任何帮助,如果需要其他详细信息,请告诉我。

NSProgressNSOperations一无所知 - 这两件事是正交的 - 但这并不意味着它不能与它们一起使用。嵌套NSProgress"任务"背后的想法是,内部任务对外部任务一无所知,外部任务不需要直接访问内部任务的NSProgress来为其提取更新。我举了一个小例子:

// Outer grouping
NSProgress* DownloadGroupsOfFiles(NSUInteger numGroups, NSUInteger filesPerGroup)
{
    // This is the top level NSProgress object
    NSProgress* p = [NSProgress progressWithTotalUnitCount: numGroups];
    for (NSUInteger i = 0; i < numGroups; ++i)
    {
        // Whatever DownloadFiles does, it's worth "1 unit" to us.
        [p becomeCurrentWithPendingUnitCount: 1];
        DownloadFiles(filesPerGroup);
        [p resignCurrent];
    }
    return p;
}
// Inner grouping
void DownloadFiles(NSUInteger numberOfFiles)
{
    NSProgress* p = [NSProgress progressWithTotalUnitCount: numberOfFiles];
    NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
    // Make the op queue last as long as the NSProgress
    objc_setAssociatedObject(p, NULL, opQueue, OBJC_ASSOCIATION_RETAIN);
    // For each file...
    for (NSUInteger i = 0; i < numberOfFiles; ++i)
    {
        // Whatever this DownloadOperation does is worth 1 "unit" to us.
        [p becomeCurrentWithPendingUnitCount: 1];
        // Make the new operation
        MyDownloadOperation* op = [[MyDownloadOperation alloc] initWithName: [NSString stringWithFormat: @"File #%@", @(i+1)]];
        [opQueue addOperation: op];
        [p resignCurrent];
    }
}
// And then the DownloadOperation might look like this...
@interface MyDownloadOperation : NSOperation
@property (nonatomic, readonly, copy) NSString* name;
- (id)initWithName: (NSString*)name;
@end
@implementation MyDownloadOperation
{
    NSProgress* _progress;
    NSString* _name;
}
- (id)initWithName:(NSString *)name
{
    if (self = [super init])
    {
        _name = [name copy];
        // Do this in init, so that our NSProgress instance is parented to the current one in the thread that created the operation
        _progress = [NSProgress progressWithTotalUnitCount: 1];
    }
    return self;
}
- (void)dealloc
{
    _name = nil;
    _progress = nil;
}
- (void)main
{
    // Fake like we're doing something that takes some time
    // Determine fake size -- call it 768K +- 256K
    const NSUInteger size = 512 * 1024 + arc4random_uniform(512*1024);
    const NSUInteger avgBytesPerSec = 1024 * 1024;
    const NSTimeInterval updatePeriod = 1.0/60.0;
    // Make sure all the updates to the NSProgress happen on the main thread
    // in case someone is bound to it.
    dispatch_async(dispatch_get_main_queue(), ^{
        _progress.totalUnitCount = size;
        _progress.completedUnitCount = 0;
    });
    NSUInteger bytesRxd = 0;
    do
    {
        // Sleep for a bit...
        usleep(USEC_PER_SEC * updatePeriod);
        // "Receive some data"
        NSUInteger rxdThisTime = updatePeriod * avgBytesPerSec;
        // Never report more than all the bytes
        bytesRxd = MIN(bytesRxd + rxdThisTime, size);
        // Update on the main thread...
        dispatch_async(dispatch_get_main_queue(), ^{
            [_progress setCompletedUnitCount: bytesRxd];
        });
    } while (bytesRxd < size);
}
@end

需要注意的一点是,如果NSProgress用于向UI传达状态,那么您将需要确保每次更新NSProgress对象时,您都从主线程进行,否则您将获得很多奇怪的崩溃。

或者,您可以使用 NSURLConnection 下载文件,然后有一个如下所示的委托:

@interface MyURLConnectionProgressReporter : NSObject <NSURLConnectionDownloadDelegate>
@property (nonatomic, readwrite, assign) id<NSURLConnectionDownloadDelegate> delegate;
@end
NSProgress* DownloadABunchOfFiles(NSArray* arrayOfURLs)
{
    arrayOfURLs = arrayOfURLs.count ? arrayOfURLs : @[ [NSURL URLWithString: @"http://www.google.com"] ];
    NSProgress* p = [NSProgress progressWithTotalUnitCount: arrayOfURLs.count];
    for (NSURL* url in arrayOfURLs)
    {
        [p becomeCurrentWithPendingUnitCount: 1];
        MyURLConnectionProgressReporter* delegate = [[MyURLConnectionProgressReporter alloc] init];
        NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest: [NSURLRequest requestWithURL: url] delegate: delegate];
        [conn start];
        [p resignCurrent];
    }
    return p;
}
@implementation MyURLConnectionProgressReporter
{
    NSProgress* _progress;
}
static void EnsureMainThread(dispatch_block_t block);
- (id)init
{
    if (self = [super init])
    {
        _progress = [NSProgress progressWithTotalUnitCount: 1];
        EnsureMainThread(^{
            _progress.kind = NSProgressKindFile;
            [_progress setUserInfoObject:NSProgressFileOperationKindDownloading forKey:NSProgressFileOperationKindKey];
        });
    }
    return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    id retVal = [super forwardingTargetForSelector:aSelector];
    if (!retVal && [self.delegate respondsToSelector: _cmd])
    {
        retVal = self.delegate;
    }
    return retVal;
}
- (void)p_updateWithTotalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
    // Update our progress on the main thread...
    EnsureMainThread(^{
        if (!expectedTotalBytes)
            _progress.totalUnitCount = -1;
        else
            _progress.totalUnitCount = MAX(_progress.totalUnitCount, expectedTotalBytes);
        _progress.completedUnitCount = totalBytesWritten;
    });
}
- (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
    // Update our progress
    [self p_updateWithTotalBytesWritten: totalBytesWritten expectedTotalBytes: expectedTotalBytes];
    // Then call on through to the other delegate
    if ([self.delegate respondsToSelector: _cmd])
    {
        [self.delegate connection:connection didWriteData:bytesWritten totalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes];
    }
}
- (void)connectionDidResumeDownloading:(NSURLConnection *)connection totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
    // Update our progress
    [self p_updateWithTotalBytesWritten: totalBytesWritten expectedTotalBytes: expectedTotalBytes];
    // Then call on through to the other delegate
    if ([self.delegate respondsToSelector: _cmd])
    {
        [self.delegate connectionDidResumeDownloading:connection totalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes];
    }
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *) destinationURL
{
    // We're done, so we want (_progress.completedUnitCount == _progress.totalUnitCount)
    EnsureMainThread(^{
        _progress.completedUnitCount = _progress.totalUnitCount;
    });
    if ([self.delegate respondsToSelector: _cmd])
    {
        [self.delegate connectionDidFinishDownloading:connection destinationURL:destinationURL];
    }
}
static void EnsureMainThread(dispatch_block_t block)
{
    if (!block)
        return;
    else if ([NSThread isMainThread])
        block();
    else
        dispatch_async(dispatch_get_main_queue(), block);
}
@end

希望有帮助。

最新更新