AFNetworking同步调用(如/不同)

时间:2013-08-07 10:37:28

标签: ios objective-c asynchronous block afnetworking

我需要在应用中实现 like / different 功能。所有API调用均使用AFNetworking和成功/错误处理程序( ios块)进行。

问题是,当用户在短时间内对按钮进行多次点击时,服务器会以错误的顺序接收到某些请求,然后一切都会出错。例如,双重喜欢或双重不同。

有没有办法同步通过AFNetworking发送所有请求?

如果没有设计此类API请求的最佳做法是什么?

5 个答案:

答案 0 :(得分:1)

如果将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];
}

答案 1 :(得分:1)

禁用按钮(就像评论建议的那样)并不是一个坏主意,特别是如果你抛出一个微调器或一些UI更改让用户知道你正在处理这个改变。

否则,您可以将API调用限制为仅允许单个调用。如果用户按下按钮,则触发调用并更改一些布尔值或跟踪值。如果他们再次按下按钮,则在本地保持更改状态,但等待第一个回调进入。如果他们一直按下按钮,只记录他们的更改,但在收到API调用已完成的通知之前,不要触发响应(如果失败,可能会有10-30秒的超时)。

呼叫完成后,查看用户想要的新值是否不同。如果是,发送它并防止将来的更改(但在本地跟踪它们),如果它是相同的(用户在第一次通话结束时按下按钮的次数),则不要发送它。

我甚至会将第一个呼叫延迟3秒左右,并且每次按下按钮时,该时间段内都会重置计时器。这样你就不会不必要地拨打意外的电话(想想它是一个coredata保存,如果你知道在保存之前你可能会有一些变化)。

同步队列的问题是如果他们按下按钮五次(或更多次),它将有一个相当长的等待队列。然后,如果他们关闭应用程序并且您的电话没有被发送怎么办?然后您的数据库有(可能)不准确的信息。

答案 2 :(得分:1)

让我觉得你有两个选择:

  1. 简单的解决方案是为用户提供正面的UI反馈按钮被点击,例如Moxy建议的(即用户体验,它可以阻止“嘿,我应该再次点击它,因为它看起来不像就像我上次获得它一样,但是然后禁用与该按钮的进一步交互,直到前一个动作完成。或者,

  2. 更复杂的解决方案是立即反映UI中的类似/不同的变化,并异步管理网络请求(不仅仅是线程方面,而且逻辑上也是如此)。如果你这样做,你会希望对每个喜欢/不同的按钮保持对先前喜欢/不同操作的weak引用(并且operation queues对于这类问题很好),这样当你使new成为一个喜欢/不同的请求,你可以让它的操作依赖于前一个(所以它们按顺序发生),和/或取消之前的操作。

答案 3 :(得分:1)

最简单的方法是恕我直言,就是在发送请求之前禁用该按钮。在成功或失败回调中获得响应后,您可以进行UI更改以提供用户喜欢的反馈,并且可以再次启用该按钮。

答案 4 :(得分:0)

对于Swift4,我使用队列对其进行了管理

exec msdb.dbo.sp_start_job @job_name = 'job_name';