我需要在app中实现类似/不同的功能。所有API调用都是使用AFNetworking
和成功/错误处理程序(ios块)进行的。
问题是,当用户在短时间内对按钮进行多次点击时,服务器会以错误的顺序接收某些请求,然后一切都会出错。例如,双重喜欢或双重不同发生。
有没有办法通过AFNetworking
同步发送所有请求?
如果没有什么是设计此类API请求的最佳做法?
禁用按钮(如注释所示)并不是一个坏主意,特别是如果您抛出微调器或某些UI更改以让用户知道您正在处理更改。
否则,您可以将API调用限制为仅允许单个调用。如果用户按下按钮,则触发调用并更改一些布尔值或跟踪值。如果他们再次按下按钮,则在本地保持更改状态,但等待第一个回调进入。如果他们一直按下按钮,只记录他们的更改,但在收到API调用已完成的通知之前,不要触发响应(如果失败,可能会有10-30秒的超时)。
呼叫完成后,查看用户想要的新值是否不同。如果是,发送它并防止将来的更改(但在本地跟踪它们),如果它是相同的(用户在第一次通话结束时按下按钮的次数),则不要发送它。
我甚至会将第一个呼叫延迟3秒左右,并且每次按下该时间段内的按钮都会重置计时器。这样你就不会不必要地拨打意外的电话(想想它是一个coredata保存,如果你知道在保存之前你可能会有一些变化)。
同步队列的问题是,如果他们按下按钮五次(或更多次),它将有一个相当长的等待队列。然后,如果他们关闭应用程序并且您的电话没有被发送怎么办?然后您的数据库有(可能)不准确的信息。
最简单的方法是恕我直言,就是在发送请求之前禁用该按钮。在成功或失败回调中获得响应后,您可以进行UI更改以提供用户喜欢他喜欢的任何反馈,并且可以再次启用该按钮。
我觉得你有两个选择:
weak
引用之前的喜欢/不同的操作(并且operation queues非常适合这类问题),这样当你创建一个喜欢/不同的请求时,你可以使它的操作依赖于前一个(因此它们顺序发生),和/或取消先前的操作。如果将AFNetworking操作放在操作队列中,它们将在完成事件之前返回。查看此博文:http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/
在您的情况下,您需要创建一个类似于以下内容的NSOperation子类:
//Header file
@interface LikeOperation : NSOperation
@property (readonly, nonatomic) BOOL isExecuting;
@property (readonly, nonatomic) BOOL isFinished;
+ (instancetype)operationWithCompletionSuccessBlock:(void(^)())onSuccess failure:(void(^)(NSError *anError))onError;
@end
//Implementation file
#import "LikeOperation.h"
typedef void (^SuccessBlock)();
typedef void (^ErrorBlock)(NSError*);
@interface LikeOperation()
@property (readwrite, copy, nonatomic) SuccessBlock onSuccess;
@property (readwrite, copy, nonatomic) ErrorBlock onError;
@property (assign, nonatomic) BOOL isExecuting;
@property (assign, nonatomic) BOOL isFinished;
@property (readwrite, strong, nonatomic) AFHTTPClient *client;
@end
@implementation LikeOperation
static NSString *const kBaseURLString = @"www.example.org";
static NSString *const kURLString = @"www.example.org/like";
- (id)initWithCompletionSuccessBlock:(void (^)())onSuccess failure:(void (^)(NSError *))onError
{
self = [super init];
if (self)
{
self.onSuccess = onSuccess;
self.onError = onError;
}
return self;
}
+ (instancetype)operationWithCompletionSuccessBlock:(void (^)())onSuccess failure:(void (^)(NSError *))onError
{
return [[self alloc] initWithCompletionSuccessBlock:onSuccess
failure:onError];
}
- (void)start
{
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start)
withObject:nil
waitUntilDone:NO];
return;
}
NSString *key = NSStringFromSelector(@selector(isExecuting));
[self willChangeValueForKey:key];
self.isExecuting = YES;
[self didChangeValueForKey:key];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURL *baseURL = [NSURL URLWithString:kBaseURLString];
self.client = [AFHTTPClient clientWithBaseURL:baseURL];
});
NSURL *url = [NSURL URLWithString:kURLString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [self.client HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.onSuccess();
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
self.onError(error);
}];
[operation start];
}
- (void)finish
{
NSString *isExecutingKey = NSStringFromSelector(@selector(isExecuting));
NSString *isFinishedKey = NSStringFromSelector(@selector(isFinished));
[self willChangeValueForKey:isExecutingKey];
[self willChangeValueForKey:isFinishedKey];
self.isExecuting = NO;
self.isFinished = YES;
[self didChangeValueForKey:isExecutingKey];
[self didChangeValueForKey:isFinishedKey];
}
@end
之后,您可以将上述操作安全地放在NSOperationQueue中,并将max concurrent maxConcurrentOperationCount设置为1,以便操作一个接一个地运行。您可能还想探索nsoperation依赖关系,如http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html中所述
//Code to initialize the operation queue
self.queue = [[NSOperationQueue alloc] init];
self.queue.name = @"Post data queue";
self.queue.maxConcurrentOperationCount = 1;
//perform like
- (void)like
{
NSOperation *likeOperation = [LikeOperation operationWithCompletionSuccessBlock:^{
} failure:^(NSError *anError) {
}];
[self.queue addOperation:likeOperation];
}
对于Swift4,我用队列管理它
import UIKit
import Alamofire
class LikeOperation: Operation {
private var _isExecuting = false
private var _finished = false
private var request:DataRequest? = nil
private var imageID:String
typealias completionBlock = ((GeneralResponse<User>?) -> Void)?
var finishedBlock : completionBlock
init(imageID:String, completionBlock:completionBlock) {
self.imageID = imageID
self.finishedBlock = completionBlock
super.init()
}
override var isExecuting: Bool {
get {
return _isExecuting
} set {
willChangeValue(forKey: "isExecuting")
_isExecuting = isExecuting
didChangeValue(forKey: "isExecuting")
}
}
override var isFinished: Bool {
get {
return _finished
} set {
willChangeValue(forKey: "isFinished")
_finished = newValue
didChangeValue(forKey: "isFinished")
}
}
override func start() {
if isCancelled {
isFinished = true
return
}
isExecuting = true
func completeOperation() {
isFinished = true
isExecuting = false
}
self.request = APIClient.insertImageLike(ImageID: self.imageID, completion: { (completion:GeneralResponse<User>?, error) in
self.finishedBlock?(completion!)
completeOperation()
})
}
override func cancel() {
super.cancel()
if isExecuting {
isFinished = true
isExecuting = false
}
request?.cancel()
}
}
func callAPIToLike (post:Post) {
guard let id = post.id else {
self.showAlert(withMessage: ErrorMessages.General.somethingWentWrong)
return
}
AppGlobalManager.sharedInstance.homeScreenLikeAPIQueue.cancelAllOperations()
let operation = LikeOperation.init(imageID: "\(id)") { (object) in
}
AppGlobalManager.sharedInstance.homeScreenLikeAPIQueue.addOperation(operation)
}