8

調査NSProgressしてきましたが、既存のドキュメント、クラス リファレンス、およびチュートリアルが不足していることがわかりました。私の NSProgress が私のユースケースに適用できるかどうかは主に疑問です。クラス リファレンス ドキュメントではsuboperationsorsubtasksを代わりに参照していますが、間違っているかもしれませんがsuboperations、NSOperation が other のグループを管理する場合を意味すると解釈しましたNSOperations。私のユースケースの例は次のとおりです。

  • Upload All Items in Group存在するグループごとに操作を作成します。
  • これらの各操作を に追加しNSOperationQueueます。
  • 各操作は、グループ内の各アイテムUpload All Items in Groupの操作を作成します。Upload ItemこれらはすべてNSOperationQueue、オペレーションによって管理される に追加されます。

NSProgress私はこれをサポートし、ネストされた操作 (Upload Item操作) から親操作に、そして最後にメイン スレッドと UI に進行状況を伝達できるようにすることを期待していました。しかし、私はこれを実装するのに苦労しましNSProgressた.1つのバックグラウンドスレッドですべてのコードを実行する長い操作を意図しているようですが、進行状況がいつ行われたかを簡単に判断できる個別の「セクション」があります。この場合、suboperationネストされた の使用を思い起こさせるため、この用語の使用は少し誤解を招きますNSOperations

ご協力いただきありがとうございます。さらに詳しい情報が必要な場合はお知らせください。

4

1 に答える 1

13

NSProgressは何も知りませんNSOperations-- 2 つのことは直交しています -- しかし、それはそれらと一緒に使用できないという意味ではありません。ネストされ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

注意すべきことの 1 つは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 に答える