2

我需要在应用程序中实现类似/不同的功能。所有 API 调用都使用AFNetworking成功/错误处理程序(ios 块)进行。

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

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

如果没有,设计这种 API 请求的最佳实践是什么?

4

5 回答 5

1

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

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

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

我什至会将第一次通话延迟 3 秒左右,并且每次他们在该时间段内按下按钮时都会重置计时器。这样您就不会不必要地触发意外调用(将其视为核心数据保存,如果您知道在保存之前可能会进行一些更改)。

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

于 2013-08-07T11:46:07.390 回答
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 中,并将最大并发 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];
}
于 2013-08-07T11:37:36.463 回答
1

令我震惊的是,您有两种选择:

  1. 简单的解决方案是向用户提供点击按钮的积极 UI 反馈,例如 Moxy 建议的(即阻止“嘿,我应该再次点击它的 UX,因为它看起来不像我上次得到它"),但随后禁用与该按钮的进一步交互,直到上一个操作完成。或者,

  2. 更复杂的解决方案是立即反映 UI 中的相似/不相似变化并异步管理网络请求(不仅在线程方面,而且在逻辑上也是如此)。如果您这样做,您将希望为每个喜欢/不喜欢按钮保留weak对先前喜欢/不喜欢操作的引用(并且操作队列非常适合此类问题),这样当您发出新的喜欢/不喜欢请求时,您可以使其操作依赖于前一个(因此它们按顺序发生),和/或取消前一个。

于 2013-08-07T13:32:47.707 回答
1

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

于 2013-08-07T14:26:49.323 回答
0

对于 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)

    }
于 2018-09-01T06:31:48.533 回答