8

Tôi đã điều tra NSProgress nhưng đã tìm thấy tài liệu hiện có, tham chiếu lớp học và hướng dẫn bị thiếu. Tôi chủ yếu tự hỏi nếu NSProgress của tôi có thể áp dụng cho trường hợp sử dụng của tôi hay không. Tài liệu tham chiếu lớp học có liên quan đến suboperations hoặc subtasks, tôi có thể bị nhầm lẫn nhưng tôi đã giải thích suboperations có nghĩa là trường hợp NSOperation quản lý một nhóm khác NSOperations. Ví dụ về trường hợp sử dụng của tôi như sau:Sử dụng NSProgress với NSOperations lồng nhau

  • Tạo một hoạt động Upload All Items in Group cho mỗi nhóm tồn tại.
  • Thêm từng hoạt động này vào NSOperationQueue.
  • Mỗi hoạt động Upload All Items in Group sẽ tạo một hoạt động Upload Item cho từng mục trong nhóm của họ. Tất cả những thứ này được thêm vào một số NSOperationQueue do thao tác quản lý.

tôi dự kiến ​​sẽ có NSProgress để hỗ trợ này, và cho phép tôi để tuyên truyền tiến bộ từ các hoạt động lồng nhau (Upload Item hoạt động) đến hoạt động phụ huynh, và cuối cùng là các chủ đề chính và giao diện người dùng. Nhưng tôi đã gặp khó khăn khi thực hiện điều này, có vẻ như mặc dù NSProgress có nghĩa là nhiều hơn cho các hoạt động dài thực thi tất cả mã của chúng trên một chuỗi nền, nhưng có "phần" riêng biệt giúp dễ dàng xác định khi nào tiến trình đã được thực hiện. là trường hợp đó, việc sử dụng thuật ngữ suboperation là một chút gây hiểu lầm vì nó mang đến cho tâm trí việc sử dụng lồng nhau NSOperations.

Cảm ơn bạn đã được trợ giúp bạn có thể cung cấp và cho tôi biết nếu cần thêm chi tiết.

Trả lời

13

NSProgress không biết gì về NSOperations - hai điều là trực giao - nhưng điều đó không có nghĩa là nó không thể được sử dụng với chúng. Ý tưởng đằng sau việc làm tổ NSProgress "nhiệm vụ" là nhiệm vụ bên trong không biết gì về nhiệm vụ bên ngoài, và nhiệm vụ bên ngoài không cần truy cập trực tiếp vào nhiệm vụ bên trong của NSProgress để lấy các cập nhật cho nó. Tôi nấu chín lên một chút ví dụ:

// 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 

Một điều cần lưu ý là nếu NSProgress đang được sử dụng để truyền đạt trạng thái để giao diện người dùng, sau đó bạn sẽ muốn chắc chắn rằng mỗi khi bạn cập nhật các đối tượng NSProgress, bạn làm vì vậy từ chủ đề chính, nếu không bạn sẽ nhận được rất nhiều sự cố lạ.

Hoặc bạn chỉ có thể sử dụng NSURLConnection tải tập tin, và sau đó có một đại biểu như thế này:

@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 

Hy vọng rằng sẽ giúp.

+0

Bạn không nên gọi '[p trở thànhCurrentWithPendingUnitCount: numGroups]; 'bên ngoài vòng lặp đầu tiên cho vòng lặp? – Eric

+2

@Eric Điều đó sẽ làm cho mối quan hệ giữa các tiến trình phụ (có khả năng) bất bình đẳng về tỷ lệ tiến bộ của phụ huynh. Đặt một cách khác nhau, nếu bạn muốn mỗi tập tin đại diện cho 1 đơn vị tiến bộ trong phụ huynh, sau đó bạn cần phải làm theo cách này. Nếu bạn chắc chắn rằng các tiến trình con được chỉ định trong một số đơn vị được chia sẻ chung như byte, (có thể là giả định an toàn ở đây, nhưng không phải ở mọi nơi) và bạn muốn hiển thị đơn vị đó như một phần của báo cáo tiến độ gốc, thì có, bạn có thể di chuyển nó ra ngoài. – ipmcc

+0

Đây là một câu trả lời tuyệt vời, cảm ơn bạn. – Sam

Các vấn đề liên quan