Подтвердить что ты не робот

Передача результата NSOperation другому NSOperation

У меня есть два NSOperation, которые отвечают за скачивание и анализ. После успешной операции загрузки и получения некоторого NSData я хочу, чтобы эти данные были использованы в качестве данных, которые будут использоваться операцией синтаксического анализа:

init(context: NSManagedObjectContext, completionHandler: Void -> Void) {

    downloadOperation = DownloadActivitiesOperation() { data in
        self.parseOperation.data = data
    }
    parseOperation = ParseActivitiesOperation(context: context)

    let finishOperation = NSBlockOperation(block: completionHandler)

    parseOperation.addDependency(downloadOperation)
    finishOperation.addDependency(parseOperation)

    super.init(operations: [downloadOperation, parseOperation, finishOperation])

    name = "Get Activities"
}

Однако это не работает, поскольку я пытаюсь использовать self внутри блока завершения загрузки перед вызовом super.init.

Мой вопрос: какой лучший подход при попытке передать результат одной операции следующему в цепочке?

4b9b3361

Ответ 1

Каждый экземпляр NSOperation содержит массив зависимостей. После завершения операции из этого массива операции не удаляются. Вы можете использовать эти объекты для доступа к данным:

class DownloadActivitiesOperation: NSOperation {
   var data: NSData?
   ...
   // set self.data when it has downloaded
}

class ParseActivitiesOperation: NSOperation {
    func main() {
      if let downloadOp = self.dependencies.last as? DownloadActivitiesOperation {
          let dataToParse = downloadOp.data
          ...
      }
    }
 }

И так далее.

Ответ 2

Использовать кеш файл

Похоже, вы используете какую-то реализацию GroupOperation из беседы WWDC2015 на Advanced NSOperations. В примере кода из разговора они используют файл кэша для передачи данных между загрузчиком и парсером.

Следующий фрагмент из класса GetEarthquakesOperation:

    
        let cachesFolder = try! NSFileManager.defaultManager().URLForDirectory(.CachesDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)

        let cacheFile = cachesFolder.URLByAppendingPathComponent("earthquakes.json")

        /*
            This operation is made of three child operations:
            1. The operation to download the JSON feed
            2. The operation to parse the JSON feed and insert the elements into the Core Data store
            3. The operation to invoke the completion handler
        */
        downloadOperation = DownloadEarthquakesOperation(cacheFile: cacheFile)
        parseOperation = ParseEarthquakesOperation(cacheFile: cacheFile, context: context)

Использовать кеш памяти

Мое текущее решение в одном из моих проектов - обернуть результаты в класс и передать его в обе операции:


class OperationResultWrapper<T> {
    var result: T?
}


    let wrapper = OperationResultWrapper<NSData>()
    downloadOperation = DownloadEarthquakesOperation(resultWrapper: wrapper)
    parseOperation = ParseEarthquakesOperation(dataWrapper: wrapper, context: context)

Ответ 3

Сначала вы можете создать цепочку зависимых NSOperation без использования self, затем инициализировать свои свойства и называть super.init последним.

init(context: NSManagedObjectContext, completionHandler: () -> Void) {
    let finishOperation = NSBlockOperation(block: completionHandler)

    let parseOperation = ParseActivitiesOperation(context: context)
    finishOperation.addDependency(parseOperation)

    let downloadOperation = DownloadActivitiesOperation() { data in
        parseOperation.data = data
    }
    parseOperation.addDependency(downloadOperation)

    self.parseOperation = parseOperation
    self.downloadOperation = downloadOperation
    self.name = "Get Activities"

    super.init(operations: [downloadOperation, parseOperation, finishOperation])
}

Примечание. Мне кажется странным, что вы сохраняете свойства downloadOperation и parseOperation в свойствах и передаете их в массив суперклассу, но я не знаю остальную часть вашего кода.

Ответ 4

Я только что закончил перемещение значительной части производственного кода для использования NSOperation и NSOperationQueues. Типичные решения для обмена результатами (уведомления, делегаты) казались громоздкими и громоздкими, поэтому вот мое решение.

  • Подкласс NSOperationQueue, чтобы включить свойство экземпляра словаря с изменяемым потоком. Я адаптировал измененный нитевидный изменяемый словарь, опубликованный здесь в моем подклассе JobOperationQueue: https://www.guiguan.net/ggmutabledictionary-thread-safe-nsmutabledictionary/

  • Подкласс NSOperation включает ссылку на него, владеющую/начальным JobOperationQueue. Это гарантирует, что операция всегда может найти владельца, даже если код должен запускаться в разных очередях (что происходит больше, чем я думал!). Добавьте методы подкласса в JobOperationQueue, чтобы установить это значение всякий раз, когда операция добавляется в очередь через addOperation: или addOperations:

  • В качестве операционного процесса они могут добавлять значения в словарь очередей и получать доступ к значениям, размещенным там более ранними процессами.

Я был очень доволен этим подходом и решил много проблем.

Соблюдайте условия гонки - если одна операция ДОЛЖНА ИМЕТЬ значение из другой операции, убедитесь, что для обеспечения порядка операций явно добавлена ​​зависимость.

Вот мои два основных класса. Я также добавил двунаправленную информацию о зависимостях, которую я нашел полезной в ситуации, когда операция должна порождать дочерние операции, но хочет поддерживать сеть зависимостей. В этой ситуации вы должны знать, кто в зависимости от исходной операции, чтобы вы могли распространять зависимости на порожденные операции.

//
//  JobOperation.h
//  
//
//  Created by Terry Grossman on 9/17/15.
//

#import <Foundation/Foundation.h>
#import "JobOperationQueue.h"
#import "ThreadSafeMutableDictionary.h"
#import "ThreadSafeMutableArray.h"

@interface JobOperation : NSOperation

@property (strong, atomic) ThreadSafeMutableArray *dependents;    
@property (strong, atomic) NSDate *enqueueDate;
@property (weak, atomic) JobOperationQueue *homeJobQueue;

-(ThreadSafeMutableDictionary *)getJobDict;

@end

//
//  JobOperation.m
// 
//
//  Created by Terry Grossman on 9/17/15.
//

#import "JobOperation.h"

@implementation JobOperation


- (id)init
{
    if((self = [super init])) {
        _dependents = [[ThreadSafeMutableArray alloc] init];
    }

    return self;
}


-(ThreadSafeMutableDictionary *)getJobDict
{
    id owningQueue = self.homeJobQueue;
    if (owningQueue && [owningQueue isKindOfClass:[JobOperationQueue class]])
    {
        return ((JobOperationQueue *)owningQueue).jobDictionary;
    }


    // try to be robust -- handle weird situations
    owningQueue = [NSOperationQueue currentQueue];
    if (owningQueue && [owningQueue isKindOfClass:[JobOperationQueue class]])
    {
        return ((JobOperationQueue *)owningQueue).jobDictionary;
    }

    return nil;
}

-(void) addDependency:(NSOperation *)op
{
    [super addDependency:op];  // this adds op into our list of dependencies

    if ([op isKindOfClass:[JobOperation class]])
    {
        [((JobOperation *)op).dependents addObject:self];  // let the other job op know we are depending on them
    }
}

@end


//
//  JobOperationQueue.h
// 
//
//  Created by Terry Grossman on 9/17/15.
//

#import <Foundation/Foundation.h>
#import "ThreadSafeMutableDictionary.h"

// A subclass of NSOperationQueue
// Adds a thread-safe dictionary that queue operations can read/write
// in order to share operation results with other operations.

@interface JobOperationQueue : NSOperationQueue

// If data needs to be passed to or between job operations
@property (strong, atomic) ThreadSafeMutableDictionary *jobDictionary;


-(void)addOperation:(NSOperation *)operation;
-(void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;

+(BOOL) checkQueue:(JobOperationQueue *)queue hasOpsOlderThan:(NSInteger)secondsThreshold cancelStaleOps:(BOOL)cancelOps;



@end

//
//  JobOperationQueue.m
// 
//
//  Created by Terry Grossman on 9/17/15.
//

#import "JobOperationQueue.h"
#import "JobOperation.h"


@implementation JobOperationQueue


// if this method returns NO, should set the queue to nil and alloc a new one
+(BOOL) checkQueue:(JobOperationQueue *)queue hasOpsOlderThan:(NSInteger)secondsThreshold cancelStaleOps:(BOOL)cancelOps
{
    if (queue == nil) 
    {
        return NO;
    }

    if ([queue operationCount] > 0) 
    {
        NSLog(@"previous share still processing!");

        // recently started or stale?  Check the enqueue date of the first op.
        JobOperation *oldOp = [[queue operations] objectAtIndex:0];

        NSTimeInterval sourceSeconds = [[NSDate date] timeIntervalSinceReferenceDate];
        NSTimeInterval destinationSeconds = [oldOp.enqueueDate timeIntervalSinceReferenceDate];    
        double diff =  fabs( destinationSeconds - sourceSeconds );        

        if (diff > secondsThreshold) 
        {
            // more than three minutes old!  Let cancel them and tell caller to proceed
            [queue cancelAllOperations];
            return NO;
        }
        else
        {
            return YES;
        }

    }
    return NO;
}


-(id) init;
{
    if((self = [super init])) {
        _jobDictionary = [[ThreadSafeMutableDictionary alloc] initWithCapacity:12];
    }

    return self;
}

-(void)addOperation:(NSOperation *)operation;
{
    if (operation == nil) 
    {
        return;
    }

    if ([operation isKindOfClass:[JobOperation class]]) 
    {
        ((JobOperation *)operation).enqueueDate = [NSDate date];
        //((JobOperation *)operation).homeQueueT = self.underlyingQueue; // dispatch_get_current_queue();
        ((JobOperation *)operation).homeJobQueue = self;
    }

    [super addOperation:operation];    
}

-(void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
{
    for (NSOperation *operation  in ops) 
    {
        if ([operation isKindOfClass:[JobOperation class]]) 
        {
            ((JobOperation *)operation).enqueueDate = [NSDate date];
            //((JobOperation *)operation).homeQueueT = self.underlyingQueue; //dispatch_get_current_queue();
            ((JobOperation *)operation).homeJobQueue = self;
        }
    }

    [super addOperations:ops waitUntilFinished:wait];
}

@end  

Ответ 5

Вы можете создать слабую переменную self перед запуском блока.

Попробуйте добавить эту строку до начала вашего блока:

__weak __typeof(self) weakSelf = self;

Ответ 6

Благодаря ilya для ответа, я заметил, что я получил доступ к другой операции через мои подклассы Operation через свой массив зависимостей.

В итоге я придумал это расширение:

extension Operation{

    func getOperationFromDependancies<T:Operation>(withType operationType:T.Type) -> T?{
        for dependency in self.dependencies {
            if let operation = dependency as? T{
                return operation
            }
        }
        return nil
    }

}

Затем скажем, что вы используете две операции, одну загрузку и операцию, два процесса загружают файл, ваш main() будет содержать что-то вроде:

override func main(){
    let downloadOperation = 
    self.getOperationFromDependencies(withType:DownloadOperation.self)
    let downloadedFile = downloadedOperation?.downloadedFile
    //Process file here
}

Ответ 7

Попробуйте это

` В языке Swift не разрешается вызывать назначенный инициализатор из другого назначенного инициализатора, поэтому вы можете отметить инициализацию как удобство.

Для получения дополнительной информации, пожалуйста, проверьте инициализацию языка программирования Swift в разделе "Руководство по языку" `