【问题标题】:AFNetworking synchronous calls (like/unlike)AFNetworking 同步调用(like/unlike)
【发布时间】:2013-08-07 10:37:27
【问题描述】:

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

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

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

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

【问题讨论】:

  • 为什么不禁用按钮,直到收到相关响应?
  • 我们认为禁用按钮是个坏主意。如果没有其他方法可以实现这一点,那将是有意义的。
  • 您应该禁用该按钮并向用户反馈他们实际上是第一次触摸它。例如,在 Facebook iOS 应用中,当您点击“赞”按钮时,它会改变颜色并制作缩放动画。
  • 或者将您的请求添加到串行队列。

标签: ios objective-c asynchronous block afnetworking


【解决方案1】:

禁用按钮(如 cmets 建议的那样)不是一个坏主意,尤其是当您抛出微调器或一些 UI 更改以让用户知道您正在处理更改时。

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

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

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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      我觉得你有两个选择:

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

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 操作将成为解决方案的一部分,但这并不能解决 OP 问题的业务逻辑问题。此外,还有两个小观察。 1. 您的initWithCompletionSuccessBlock 缺少返回类型; 2. 您的块可能应该使用copy 内存语义,而不是strong(参见Objects Use Properties to Keep Track of Blocks 中的注释)。
        • 已编辑以修复 init 方法返回类型。至于使用copy,相信ARC在使用strong的时候已经复制了block,查看stackoverflow.com/questions/10453261/…
        • 优秀。另外,如果您认为 AFNetworking 的运营不能胜任这项工作,我不确定我是否同意该分析。您可以像这里一样将它们添加到您自己的串行队列中,而无需编写自己的操作子类。此外,您不一定需要使用串行队列,但您也可以使用依赖项和/或取消先前的操作。最重要的是,AFNetworking 的运营应该能够很好地处理这项工作。但我同意基于NSOperation 的方法是可行的方法。
        • Re copy 块属性的语义,正如 Apple 文档所说,ARC 不需要它,但“属性属性显示结果行为的最佳实践”。
        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 2017-12-31
          • 1970-01-01
          • 1970-01-01
          • 2016-10-20
          • 2012-11-25
          • 2017-08-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多