ios swift ios9 nsoperation

ios - Pasando el resultado de NSOperation a otra NSOperation



swift ios9 (7)

Usar un archivo de caché

Parece que está utilizando algún tipo de implementación de GroupOperation en la charla de WWDC2015 sobre Advanced NSOperations . En el código de muestra de la conversación, usan un archivo de caché para pasar datos entre el descargador y el analizador.

Siguiendo un fragmento de la clase 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)

Usa un caché de memoria

Mi solución actual en uno de mis proyectos es envolver los resultados en una clase y pasarlos a ambas operaciones:

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

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

Tengo dos NSOperation que se encargan de la descarga y el análisis. Después de que la operación de descarga se haya realizado correctamente y recibo algunos datos NSData , deseo configurar esos datos como los datos que utilizará la operación de análisis:

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" }

Sin embargo, esto no funciona, ya que estoy intentando usar self dentro de mi bloque de finalización de descarga antes de llamar a super.init .

Mi pregunta es ¿cuál sería el mejor enfoque al intentar pasar el resultado de una operación a la siguiente en la cadena?


Acabo de terminar de mover una gran parte del código de producción para usar NSOperation y NSOperationQueues. Las soluciones típicas para compartir resultados (notificaciones, delegados) parecían engorrosas y difíciles de manejar, así que aquí está mi solución.

  1. Subclase NSOperationQueue para incluir una propiedad de instancia de diccionario mutable segura para subprocesos. Adapté el código de diccionario mutable seguro para subprocesos publicado aquí en mi subclase JobOperationQueue: https://www.guiguan.net/ggmutabledictionary-thread-safe-nsmutabledictionary/

  2. Subclase NSOperation para incluir una referencia a su propiedad / initial JobOperationQueue. Esto garantiza que la operación siempre pueda encontrar a su propietario, incluso cuando el código debe ejecutarse en colas diferentes (¡lo que sucede más de lo que pensé!). Agregue métodos de subclase a JobOperationQueue para establecer este valor cada vez que se agregue una operación a la cola mediante addOperation: o addOperations:

  3. A medida que las operaciones se procesan, pueden agregar valores al diccionario de la cola y acceder a los valores colocados allí por procesos anteriores.

Estoy muy contento con este enfoque y ha resuelto muchos problemas.

Tenga cuidado con las condiciones de la carrera: si una operación DEBE TENER un valor de otra operación, asegúrese de que se haya agregado explícitamente una dependencia para garantizar el orden de las operaciones.

Aquí están mis dos clases principales. También agregué información de dependencia bidireccional, que encontré útil en la situación en la que una operación tiene que generar operaciones secundarias, pero desea mantener la red de dependencia. En esa situación, debe saber quién depende de la operación original, de modo que pueda propagar dependencias en las operaciones generadas.

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


Cada instancia de NSOperation contiene una matriz de dependencias. Las operaciones no se eliminan de esta matriz después de terminar. Puedes usar esos objetos para acceder a los datos:

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 ... } } }

Y así.


Gracias a ilya por la respuesta, noté que accedía a otra operación a través de mis subclases de Operación a través de su matriz de dependencias.

Al final se me ocurrió esta extensión:

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

Entonces digamos que usa dos operaciones, la operación una descarga y la operación dos procesos el archivo descargado, su main () contendría algo como:

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


Primero puede crear la cadena de NSOperation dependientes sin usar self , luego inicializar sus propiedades y llamar a super.init último lugar.

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]) }

Nota: Me parece extraño que almacene downloadOperation y parseOperation en propiedades y las pase en una matriz a la súper clase, pero no conozco el resto de su código.


Prueba esto

`En el lenguaje Swift no está permitido llamar al inicializador designado desde otro inicializador designado, por lo que puede marcar el inicializador como conveniente.

Para obtener más información, consulte la Inicialización del lenguaje de programación Swift en la Sección de la Guía de idiomas `


Puedes crear una variable débil de self antes de comenzar el bloque.

Intenta agregar esta línea antes de que comience tu bloqueo:

__weak __typeof(self) weakSelf = self;