2016-07-03 11 views
1

私のネットワークリクエストは、FIFO形式で行う必要があります。ネットワークの問題(オフライン、タイムアウトなど)が原因で障害が発生した場合は、キューを続行する前に再試行が失敗した要求が必要です。どうすればこれを達成できますか?NSOperationをリセットする

操作が失敗すると、私は実行を設定して、両方を終了します。これがNSOperationの「リセット」として機能すると仮定します。しかし、キューは決して再開しません。

紛失しているものがありますか?

+0

の再試行のすべてが完了するまでの動作は 'isFinished'を設定しない場合、最も簡単な方法です。したがって、操作を「リセット」するのではなく、操作に「再試行」ロジックを組み込む必要があります。 – Rob

+0

オペレーション間に依存関係を作成して、FIFOを維持することができます。そして再試行のために、NSOperationのisCancelledブール値フラグをチェックし、そこでリトライロジックを書きます。 –

+0

@Robありがとうございます!オペレーションは、成功または失敗をキューに報告する前に再試行を処理する必要があります。完璧な意味合いを持つ。私は喜んで答えをあなたが作成する場合は、このコメントを受け入れるだろう。 – ChickensDontClap

答えて

2

あなたは再試行を実行するネットワークオペレーションを構築し、ネットワーク完了ブロックが呼び出されたときにのみisFinishedを設定し、それには、再試行が必要とされていないと判断できます。

class NetworkOperationWithRetry: AsynchronousOperation { 
    var session: NSURLSession 
    var request: NSURLRequest 
    var task: NSURLSessionTask? 
    var networkCompletionHandler: ((NSData?, NSURLResponse?, NSError?) ->())? 

    init(session: NSURLSession = NSURLSession.sharedSession(), request: NSURLRequest, networkCompletionHandler: (NSData?, NSURLResponse?, NSError?) ->()) { 
     self.session = session 
     self.request = request 
     self.networkCompletionHandler = networkCompletionHandler 
    } 

    override func main() { 
     attemptRequest() 
    } 

    func attemptRequest() { 
     print("attempting \(request.URL!.lastPathComponent)") 

     task = session.dataTaskWithRequest(request) { data, response, error in 
      if error?.domain == NSURLErrorDomain && error?.code == NSURLErrorNotConnectedToInternet { 
       print("will retry \(self.request.URL!.lastPathComponent)") 
       dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * Int64(NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) { 
        self.attemptRequest() 
       } 
       return 
      } 

      print("finished \(self.request.URL!.lastPathComponent)") 

      self.networkCompletionHandler?(data, response, error) 
      self.networkCompletionHandler = nil 
      self.completeOperation() 
     } 
     task?.resume() 
    } 

    override func cancel() { 
     task?.cancel() 
     super.cancel() 
    } 
} 

そして、あなたはそうのようにそれを呼び出すことができます。

let queue = NSOperationQueue() 
queue.maxConcurrentOperationCount = 1  // I wouldn't generally do this, but just to illustrate that it's honoring operation queue dependencies/concurrency settings 

let requests = urlStrings.map { NSURLRequest(URL: NSURL(string: $0)!) } 
requests.forEach { request in 
    queue.addOperation(NetworkOperationWithRetry(request: request) { data, response, error in 
     // do something with the `data`, `response`, and `error` 
    }) 
} 

ここでは、NSURLErrorNotConnectedToInternetで再試行していますが、そのロジックを自由に変更することができます。同様に、私はおそらくいくつかの "最大再試行"ロジックを追加する傾向があります。また、インターネット接続が不足している場合は、インターネット接続が達成されるまで再試行するのではなく、操作のisReady通知をReachabilityに設定する傾向があります。ところで

は、上記には、以下のAsynchronousOperationを使用しています:

/// Asynchronous Operation base class 
/// 
/// This class performs all of the necessary KVN of `isFinished` and 
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer 
/// a concurrent NSOperation subclass, you instead subclass this class which: 
/// 
/// - must override `main()` with the tasks that initiate the asynchronous task; 
/// 
/// - must call `completeOperation()` function when the asynchronous task is done; 
/// 
/// - optionally, periodically check `self.cancelled` status, performing any clean-up 
/// necessary and then ensuring that `completeOperation()` is called; or 
/// override `cancel` method, calling `super.cancel()` and then cleaning-up 
/// and ensuring `completeOperation()` is called. 

public class AsynchronousOperation : NSOperation { 

    override public var asynchronous: Bool { return true } 

    private let stateLock = NSLock() 

    private var _executing: Bool = false 
    override private(set) public var executing: Bool { 
     get { 
      return stateLock.withCriticalScope { _executing } 
     } 
     set { 
      willChangeValueForKey("isExecuting") 
      stateLock.withCriticalScope { _executing = newValue } 
      didChangeValueForKey("isExecuting") 
     } 
    } 

    private var _finished: Bool = false 
    override private(set) public var finished: Bool { 
     get { 
      return stateLock.withCriticalScope { _finished } 
     } 
     set { 
      willChangeValueForKey("isFinished") 
      stateLock.withCriticalScope { _finished = newValue } 
      didChangeValueForKey("isFinished") 
     } 
    } 

    /// Complete the operation 
    /// 
    /// This will result in the appropriate KVN of isFinished and isExecuting 

    public func completeOperation() { 
     if executing { 
      executing = false 
     } 

     if !finished { 
      finished = true 
     } 
    } 

    override public func start() { 
     if cancelled { 
      finished = true 
      return 
     } 

     executing = true 

     main() 
    } 

    override public func main() { 
     fatalError("subclasses must override `main`") 
    } 
} 

extension NSLock { 

    /// Perform closure within lock. 
    /// 
    /// An extension to `NSLock` to simplify executing critical code. 
    /// 
    /// - parameter block: The closure to be performed. 

    func withCriticalScope<T>(@noescape block: Void -> T) -> T { 
     lock() 
     let value = block() 
     unlock() 
     return value 
    } 
} 
-1

このカスタムクラスを使用してNSOperationを管理しました。私はGCDWebServerのコードをちょっと試してこのスタブを出した。そのほとんどは自明です。

@interface SROperation : NSOperation <NSCopying> 


///------------------------------- 
/// @name error reporting 
///------------------------------- 

/** 
The error, if any, that occurred in the lifecycle of the request. 
*/ 
@property (nonatomic, strong) NSError *error; 


///----------------------------------------- 
/// @name the operation properties 
///----------------------------------------- 

/** 
a dictionary. 
*/ 
@property (nonatomic, strong) NSDictionary*aDictionary; 



///---------------------------------- 
/// @name Pausing/Resuming Requests 
///---------------------------------- 

+(instancetype)operationWithDictionary:(NSDictionary*)dict; 

/** 
Pauses the execution of the request operation. 

A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished, cancelled, or paused operation has no effect. 
*/ 
- (void)pause; 

/** 
Whether the request operation is currently paused. 

@return `YES` if the operation is currently paused, otherwise `NO`. 
*/ 
@property (NS_NONATOMIC_IOSONLY, getter=isPaused, readonly) BOOL paused; 

/** 
Resumes the execution of the paused request operation. 

Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request. 
*/ 
- (void)resume; 

@end 

SROperation.m

#import "SROperation.h" 

typedef NS_ENUM(NSInteger, SROperationState) { 
    SROperationPausedState  = -1, 
    SROperationReadyState  = 1, 
    SROperationExecutingState = 2, 
    SROperationFinishedState = 3, 
}; 

static NSString * const kSROperationLockName = @"your.application.bundle.id.operationlock" 

static inline BOOL SRStateTransitionIsValid(SROperationState fromState, SROperationState toState, BOOL isCancelled) { 
    switch (fromState) { 
     case SROperationReadyState: 
      switch (toState) { 
       case SROperationPausedState: 
       case SROperationExecutingState: 
        return YES; 
       case SROperationFinishedState: 
        return isCancelled; 
       default: 
        return NO; 
      } 
     case SROperationExecutingState: 
      switch (toState) { 
       case SROperationPausedState: 
       case SROperationFinishedState: 
        return YES; 
       default: 
        return NO; 
      } 
     case SROperationFinishedState: 
      return NO; 
     case SROperationPausedState: 
      return toState == SROperationReadyState; 
     default: { 
#pragma clang diagnostic push 
#pragma clang diagnostic ignored "-Wunreachable-code" 
      switch (toState) { 
       case SROperationPausedState: 
       case SROperationReadyState: 
       case SROperationExecutingState: 
       case SROperationFinishedState: 
        return YES; 
       default: 
        return NO; 
      } 
     } 
#pragma clang diagnostic pop 
    } 
} 


static inline NSString * SRKeyPathFromSROperationState(SROperationState state) { 
    switch (state) { 
     case SROperationReadyState: 
      return @"isReady"; 
     case SROperationExecutingState: 
      return @"isExecuting"; 
     case SROperationFinishedState: 
      return @"isFinished"; 
     case SROperationPausedState: 
      return @"isPaused"; 
     default: { 
#pragma clang diagnostic push 
#pragma clang diagnostic ignored "-Wunreachable-code" 
      return @"state"; 
#pragma clang diagnostic pop 
     } 
    } 
} 

@interface SROperation() 

    @property (readwrite, nonatomic, assign) SROperationState state; 
    @property (readwrite, nonatomic, strong) NSRecursiveLock *lock; 
@end 

@implementation SROperation 

    +(instancetype)operationWithDictionary:(NSDictionary*)dict { 
     return [[self alloc] initWithDictionary:dict]; 
    } 

    -(instancetype)initWithDictionary:(NSDictionary*)aDictionary { 
     self = [self init]; 

     if (self) 
     { 
      self.aDictionary = aDictionary; 
      self.queuePriority = NSOperationQueuePriorityVeryHigh; 
     } 

     return self; 
    } 

    -(instancetype)init 
    { 
     self = [super init]; 
     if(self) { 
      _state = SROperationReadyState; 
      _lock = [[NSRecursiveLock alloc] init]; 
      _lock.name = kSROperationLockName; 
     } 
     return self; 
    } 

    #pragma mark - 

    - (void)setState:(SROperationState)state { 
     if (!SRStateTransitionIsValid(self.state, state, [self isCancelled])) { 
      return; 
     } 

     [self.lock lock]; 
     NSString *oldStateKey = SRKeyPathFromSROperationState(self.state); 
     NSString *newStateKey = SRKeyPathFromSROperationState(state); 

     [self willChangeValueForKey:newStateKey]; 
     [self willChangeValueForKey:oldStateKey]; 
     _state = state; 
     [self didChangeValueForKey:oldStateKey]; 
     [self didChangeValueForKey:newStateKey]; 
     [self.lock unlock]; 
    } 

    - (void)pause { 
     if ([self isPaused] || [self isFinished] || [self isCancelled]) { 
      return; 
     } 

     [self.lock lock]; 
     if ([self isExecuting]) { 


      dispatch_async(dispatch_get_main_queue(), ^{ 
       NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 
       [notificationCenter postNotificationName:SROperationDidFinishNotification object:self userInfo:nil]; 
      }); 
     } 

     self.state = SROperationPausedState; 
     [self.lock unlock]; 
    } 

    -(void)operationDidPause 
    { 

    } 

    - (BOOL)isPaused { 
     return self.state == SROperationPausedState; 
    } 

    - (void)resume { 
     if (![self isPaused]) { 
      return; 
     } 

     [self.lock lock]; 
     self.state = SROperationReadyState; 

     [self start]; 
     [self.lock unlock]; 
    } 


    - (BOOL)isReady { 
     return self.state == SROperationReadyState && [super isReady]; 
    } 

    - (BOOL)isExecuting { 
    return self.state == SROperationExecutingState; 
    } 

    - (BOOL)isFinished { 
     return self.state == SROperationFinishedState; 
    } 

    - (void)start { 
     [self.lock lock]; 
     if ([self isCancelled]) { 
      [self cancelConnection]; 
     } else if ([self isReady]) { 
      self.state = SROperationExecutingState; 
      [self operationDidStart]; 

     } 
     [self.lock unlock]; 
    } 

    - (void)operationDidStart { 
     [self.lock lock]; 
     if (![self isCancelled]) { 
      // YOUR ACTUAL OPERATION CODE 

      // finish 
      self.state = SROperationFinishedState; 
      dispatch_async(dispatch_get_main_queue(), ^{ 
       [[NSNotificationCenter defaultCenter] postNotificationName:SROperationDidFinishNotification object:nil]; 
      }); 
     } 
     [self.lock unlock]; 
    } 


    - (void)cancel { 
     [self.lock lock]; 
     if (![self isFinished] && ![self isCancelled]) { 
      [super cancel]; 

      if ([self isExecuting]) { 

      } 
     } 
     [self.lock unlock]; 
    } 


    #pragma mark - NSObject 

    - (NSString *)description { 
     [self.lock lock]; 
     NSString *description = [NSString stringWithFormat:@"<%@: %p, state: %@, cancelled: %@>", NSStringFromClass([self class]), self, SRKeyPathFromSROperationState(self.state), ([self isCancelled] ? @"YES" : @"NO")]; 
     [self.lock unlock]; 
     return description; 
    } 


    #pragma mark - NSCopying 

    - (id)copyWithZone:(NSZone *)zone { 
     SROperation *operation = [(SROperation *)[[self class] allocWithZone:zone] init]; 
     // copy more properties 

     return operation; 
    } 

} 
+0

実際には、このクラスをネットワーク操作のためにFIFOキュー(1回の操作につき1回の操作)で使用しましたが、依然としてこのトリックを行います。なぜdownvote? – lookaji

+0

質問は、非同期操作を作成する方法ではありませんでした(そして、あなたの '非同期'実装は表示されません)。疑問は、NSOperationベースのネットワーク操作で失敗時のリトライロジックを処理する方法でした。また、これは迅速な質問であり、客観的ではありませんでした。 – Rob

+0

かなり十分です:)私の場合は、そのコードを使用して、 'cancel'や' pause'を呼び出して失敗した場合、NSOperation自体の異常を処理して再試行できます。これは、それらの操作のために私はキャンセルや一時停止が必要ないので、私はそれらを失敗として扱うからです。より良い実装を見てうれしい!迅速なタグに気付かなかったのは残念です。 – lookaji

関連する問題