重置 NSOperation
Resetting NSOperation
我的网络请求需要以 FIFO 串行方式发生。如果由于网络问题(即离线、超时)而失败,我需要失败的请求在继续队列之前重试。我怎样才能做到这一点?
当操作失败时,我将执行和完成都设置为 - 假设这将作为 NSOperation
的 'reset'。但是队列永远不会恢复。
有没有我遗漏的东西?
我使用这个自定义 class 来管理 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;
}
}
您可以构建一个执行重试的网络操作,并且仅在调用网络完成块时设置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: [=11=])!) }
requests.forEach { request in
queue.addOperation(NetworkOperationWithRetry(request: request) { data, response, error in
// do something with the `data`, `response`, and `error`
})
}
现在,这只是在 NSURLErrorNotConnectedToInternet
上重试,但您可以根据需要更改该逻辑。同样,我可能倾向于添加一些 "max retries" 逻辑。此外,如果缺少 Internet 连接确实是问题所在,而不是在实现 Internet 连接之前重试,我可能倾向于将操作的 isReady
通知设置为可达性。
顺便说一句,上面使用了下面的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
}
}
我的网络请求需要以 FIFO 串行方式发生。如果由于网络问题(即离线、超时)而失败,我需要失败的请求在继续队列之前重试。我怎样才能做到这一点?
当操作失败时,我将执行和完成都设置为 - 假设这将作为 NSOperation
的 'reset'。但是队列永远不会恢复。
有没有我遗漏的东西?
我使用这个自定义 class 来管理 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;
}
}
您可以构建一个执行重试的网络操作,并且仅在调用网络完成块时设置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: [=11=])!) }
requests.forEach { request in
queue.addOperation(NetworkOperationWithRetry(request: request) { data, response, error in
// do something with the `data`, `response`, and `error`
})
}
现在,这只是在 NSURLErrorNotConnectedToInternet
上重试,但您可以根据需要更改该逻辑。同样,我可能倾向于添加一些 "max retries" 逻辑。此外,如果缺少 Internet 连接确实是问题所在,而不是在实现 Internet 连接之前重试,我可能倾向于将操作的 isReady
通知设置为可达性。
顺便说一句,上面使用了下面的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
}
}