8

Badałem już numer NSProgress, ale znalazłem brakującą dokumentację, odniesienie do klasy i samouczki. Zastanawiam się głównie, czy mój NSProgress ma zastosowanie do mojego przypadku użycia. Dokumentacja referencyjna klasy odnosi się alternatywnie do suboperations lub subtasks, mogę się mylić, ale interpretowałem suboperations, aby określić przypadek, w którym NSOperation zarządza grupą innych NSOperations. Przykład mojego użycia jest następujący:Używanie NSProgress z zagnieżdżonymi informacjami NIperations

  • Utwórz operację Upload All Items in Group dla każdej istniejącej grupy.
  • Dodaj każdą z tych operacji do NSOperationQueue.
  • Każda operacja Upload All Items in Group utworzy operację Upload Item dla każdego elementu w swojej grupie. Wszystko to zostaje dodane do NSOperationQueue zarządzanego przez operację.

bym oczekiwać NSProgress wspierać to, i pozwoli mi propagować postępy z zagnieżdżonych operacji (Upload Item pracy) do operacji nadrzędnej, a następnie w końcu do głównego wątku i interfejsie użytkownika. Ale miałem trudności z jej implementacją, wydaje się, że jest to bardziej przeznaczone dla długich operacji, które wykonują cały swój kod na jednym wątku tła, ale mają oddzielne "sekcje", które ułatwiają określenie, kiedy postęp został dokonany, jeśli to tak jest w przypadku użycia terminu suboperation jest trochę mylące, ponieważ przywodzi na myśl użycie zagnieżdżonego NSOperations.

Dziękuję za pomoc, którą możesz podać, i daj mi znać, jeśli będą potrzebne dodatkowe informacje.

Odpowiedz

13

NSProgress nic nie wie o NSOperations - te dwie rzeczy są ortogonalne - ale to nie znaczy, że nie można z nimi korzystać. Ideą zadań "zagnieżdżania się" jest to, że wewnętrzne zadanie nie wie nic o zewnętrznym zadaniu, a zewnętrzne zadanie nie wymaga bezpośredniego dostępu do wewnętrznego zadania, aby uzyskać jego aktualizację. I gotowane mały przykład:

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

Jedną rzeczą, aby pamiętać, że jeśli NSProgress jest używany do przekazywania statusu do interfejsu użytkownika, a następnie będziemy chcieli, aby upewnić się, że za każdym razem aktualizować obiekt NSProgress, robisz więc z głównego wątku, w przeciwnym razie dostaniesz dużo dziwnych wypadków.

Alternatywnie można po prostu użyć NSURLConnection do pobierania plików, a następnie mieć delegata takiego:

@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 

nadzieję, że pomoże.

+0

Nie powinieneś wywoływać '[p staćCurrentWithPendingUnitCount: numGroups];' poza pierwszą pętlą for? – Eric

+2

@Eric To spowodowałoby, że relacja między pod-postępami (potencjalnie) była nierówna pod względem ich proporcji postępu nadrzędnego. Inaczej mówiąc, jeśli chcesz, aby każdy plik reprezentował 1 jednostkę postępu w rodzicu, musisz to zrobić w ten sposób. Jeśli jesteś * pewien *, że pod-postępy są określone w niektórych wzajemnie podzielonych jednostkach, takich jak bajty, (prawdopodobnie bezpieczne założenie tutaj, ale nie wszędzie) i chcesz wystawić tę jednostkę jako część macierzystego raportowania postępu, to tak, możesz przenieść go na zewnątrz. – ipmcc

+0

To jest niesamowita odpowiedź, dziękuję. – Sam

Powiązane problemy