私のネットワークリクエストは、FIFO形式で行う必要があります。ネットワークの問題(オフライン、タイムアウトなど)が原因で障害が発生した場合は、キューを続行する前に再試行が失敗した要求が必要です。どうすればこれを達成できますか?NSOperationをリセットする
操作が失敗すると、私は実行を設定して、両方を終了します。これがNSOperation
の「リセット」として機能すると仮定します。しかし、キューは決して再開しません。
紛失しているものがありますか?
私のネットワークリクエストは、FIFO形式で行う必要があります。ネットワークの問題(オフライン、タイムアウトなど)が原因で障害が発生した場合は、キューを続行する前に再試行が失敗した要求が必要です。どうすればこれを達成できますか?NSOperationをリセットする
操作が失敗すると、私は実行を設定して、両方を終了します。これがNSOperation
の「リセット」として機能すると仮定します。しかし、キューは決して再開しません。
紛失しているものがありますか?
あなたは再試行を実行するネットワークオペレーションを構築し、ネットワーク完了ブロックが呼び出されたときにのみ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
}
}
このカスタムクラスを使用して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;
}
}
実際には、このクラスをネットワーク操作のためにFIFOキュー(1回の操作につき1回の操作)で使用しましたが、依然としてこのトリックを行います。なぜdownvote? – lookaji
質問は、非同期操作を作成する方法ではありませんでした(そして、あなたの '非同期'実装は表示されません)。疑問は、NSOperationベースのネットワーク操作で失敗時のリトライロジックを処理する方法でした。また、これは迅速な質問であり、客観的ではありませんでした。 – Rob
かなり十分です:)私の場合は、そのコードを使用して、 'cancel'や' pause'を呼び出して失敗した場合、NSOperation自体の異常を処理して再試行できます。これは、それらの操作のために私はキャンセルや一時停止が必要ないので、私はそれらを失敗として扱うからです。より良い実装を見てうれしい!迅速なタグに気付かなかったのは残念です。 – lookaji
の再試行のすべてが完了するまでの動作は 'isFinished'を設定しない場合、最も簡単な方法です。したがって、操作を「リセット」するのではなく、操作に「再試行」ロジックを組み込む必要があります。 – Rob
オペレーション間に依存関係を作成して、FIFOを維持することができます。そして再試行のために、NSOperationのisCancelledブール値フラグをチェックし、そこでリトライロジックを書きます。 –
@Robありがとうございます!オペレーションは、成功または失敗をキューに報告する前に再試行を処理する必要があります。完璧な意味合いを持つ。私は喜んで答えをあなたが作成する場合は、このコメントを受け入れるだろう。 – ChickensDontClap