8

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

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

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

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

4

1 回答 1

13

NSProgress什么都不知道NSOperations——这两个东西是正交的——但这并不意味着它不能和它们一起使用。嵌套“任务”背后的想法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

希望有帮助。

于 2013-10-06T21:01:01.497 回答