Managing a bunch of NSOperation with dependencies

后端 未结 4 1444
面向向阳花
面向向阳花 2021-02-01 08:40

I\'m working on an application that create contents and send it to an existing backend. Content is a title, a picture and location. Nothing fancy.

The backend is a bit c

4条回答
  •  萌比男神i
    2021-02-01 08:47

    A couple of thoughts:

    1. I would be inclined to avail myself of completion blocks because you probably only want to initiate the next operation if the previous one succeeded. You want to make sure that you properly handle errors and can easily break out of your chain of operations if one fails.

    2. If I wanted to pass data from operation to another and didn't want to use some property of the caller's class, I would probably define my own completion block as a property of my custom operation that had a parameter which included the field that I wanted to pass from one operation to another. This assumes, though, that you're doing NSOperation subclassing.

      For example, I might have a FilenameOperation.h that defines an interface for my operation subclass:

      #import 
      
      typedef void (^FilenameOperationSuccessFailureBlock)(NSString *filename, NSError *error);
      
      @interface FilenameOperation : NSOperation
      
      @property (nonatomic, copy) FilenameOperationSuccessFailureBlock successFailureBlock;
      
      @end
      

      and if it wasn't a concurrent operation, the implementation might look like:

      #import "FilenameOperation.h"
      
      @implementation FilenameOperation
      
      - (void)main
      {
          if (self.isCancelled)
              return;
      
          NSString *filename = ...;
          BOOL failure = ...
      
          if (failure)
          {
              NSError *error = [NSError errorWithDomain:... code:... userInfo:...];
              if (self.successFailureBlock)
                  self.successFailureBlock(nil, error);                                                    
          }
          else
          {
              if (self.successFailureBlock)
                  self.successFailureBlock(filename, nil);
          }
      }
      
      @end
      

      Clearly, if you have a concurrent operation, you'll implement all of the standard isConcurrent, isFinished and isExecuting logic, but the idea is the same. As an aside, sometimes people will dispatch those success or failures back to the main queue, so you can do that if you want, too.

      Regardless, this illustrates the idea of a custom property with my own completion block that passes the appropriate data. You can repeat this process for each of the relevant types of operations, you can then chain them all together, with something like:

      FilenameOperation *filenameOperation = [[FilenameOperation alloc] init];
      GenerateOperation *generateOperation = [[GenerateOperation alloc] init];
      UploadOperation   *uploadOperation   = [[UploadOperation alloc] init];
      
      filenameOperation.successFailureBlock = ^(NSString *filename, NSError *error) {
          if (error)
          {
              // handle error
              NSLog(@"%s: error: %@", __FUNCTION__, error);
          }
          else
          {
              generateOperation.filename = filename;
              [queue addOperation:generateOperation];
          }
      };
      
      generateOperation.successFailureBlock = ^(NSString *filename, NSData *data, NSError *error) {
          if (error)
          {
              // handle error
              NSLog(@"%s: error: %@", __FUNCTION__, error);
          }
          else
          {
              uploadOperation.filename = filename;
              uploadOperation.data     = data;
              [queue addOperation:uploadOperation];
          }
      };
      
      uploadOperation.successFailureBlock = ^(NSString *result, NSError *error) {
          if (error)
          {
              // handle error
              NSLog(@"%s: error: %@", __FUNCTION__, error);
          }
          else
          {
              [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                  // update UI here
                  NSLog(@"%@", result);
              }];
          }
      };
      
      [queue addOperation:filenameOperation];
      
    3. Another approach in more complicated scenarios is to have your NSOperation subclass employ a technique analogous to how the standard addDependency method works, in which NSOperation sets the isReady state based upon KVO on isFinished on the other operation. This not only allows you to not only establish more complicated dependencies between operations, but also to pass database between them. This is probably beyond the scope of this question (and I'm already suffering from tl:dr), but let me know if you need more here.

    4. I wouldn't be too concerned that uploadImageToCreatedEntry is dispatching back to the main thread. In complicated designs, you might have all sorts of different queues dedicated for particular types of operations, and the fact that UI updates are added to the main queue is perfectly consistent with this mode. But instead of dispatch_async, I might be inclined to use the NSOperationQueue equivalent:

      [[NSOperationQueue mainQueue] addOperationWithBlock:^{
          // do my UI update here
      }];
      
    5. I wonder if you need all of these operations. For example, I have a hard time imagining that filename is sufficiently complicated to justify its own operation (but if you're getting the filename from some remote source, then a separate operation makes perfect sense). I'll assume that you're doing something sufficiently complicated that justifies it, but the names of those operations make me wonder, though.

    6. If you want, you might want to take a look at couchdeveloper's RXPromise class which uses promises to (a) control the logical relationship between separate operations; and (b) simplify the passing of data from one to the next. Mike Ash has a old MAFuture class which does the same thing.

      I'm not sure either of those are mature enough that I'd contemplate using them in my own code, but it's an interesting idea.

提交回复
热议问题