iOS客户端防止发送重复点击发请求

iOS客户端经常遇到点击某个按钮发送一个请求到服务器,貌似一个非常简单的需求有的时候其实并不是那么简单,比如网络不好的时候,用户重复点击一个按钮会发送多次请求,比如在我负责的客户端来说用户发帖功能导致的弊端就是,一个用户对一个帖子回复了很多条,有的时候甚至达到了10多条,如何解决这一的问题呢。方案其实有很多。

利用MBProgressHud等控件

众所周知MBProgressHud或者SVProgresHud经常被利用在项目中,主要是在网络请求发起到网络相应收到的这段时间在客户端形成一个遮罩,可以用来阻止用户点击UI进行操作,防止某些意外的请求产生。

  • 优点:解决了用户重复点击多次发送请求的问题,同时防止了在某些条件不具备的情况进行其他操作引发客户端出现问题的出现。
  • 缺点:有的时候不人性化,比如用户进入某个界面就是网速不好,一直请求数据,等了好长时间都没有结果,这个时候用户一般都会下意识点击返回按钮,但是这种情况下,返回按钮的点击事件也是不起作用的。

利用运行时设置相应按钮点击间隔

1. 对UIControl进行扩展

该方案来自http://www.cocoachina.com/ios/20150828/13260.html

@interface UIControl (delay)
@property (nonatomic, assign) NSTimeInterval uxy_acceptEventInterval;   // 可以用这个给重复点击加间隔
@end
#import "UIControl+delay.h"
#import <objc/runtime.h>

//增加两个属性
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";
static const char *UIControl_ignoreEvent = "UIControl_ignoreEvent";

@implementation UIControl (delay)
//时间间隔
- (NSTimeInterval)uxy_acceptEventInterval
{
    return [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
}
- (void)setUxy_acceptEventInterval:(NSTimeInterval)uxy_acceptEventInterval
{
    objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(uxy_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//是否响应事件的标志位
-(BOOL)uxy_ignoreEvent
{
    return [objc_getAssociatedObject(self, UIControl_ignoreEvent) boolValue];
}
-(void)setUxy_ignoreEvent:(BOOL)uxy_ignoreEvent
{
    objc_setAssociatedObject(self, UIControl_ignoreEvent, @(uxy_ignoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+(void)load
{
    //将系统的sendAction方法和自己实现的方法进行互换
    Method a=class_getInstanceMethod(self,@selector(sendAction:to:forEvent:));
    Method b = class_getInstanceMethod(self,@selector(__uxy_sendAction:to:forEvent:));
    method_exchangeImplementations(a,b);
}
//点击后会先进入这里
- (void)__uxy_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    if (self.uxy_ignoreEvent)//根据状态判断是否继续执行
        return;
    if (self.uxy_acceptEventInterval > 0)
    {
        self.uxy_ignoreEvent = YES;
        //周期性清空标志位
        [self performSelector:@selector(setUxy_ignoreEvent:) withObject:@(NO) afterDelay:self.uxy_acceptEventInterval];
    }
    //这里其实是系统的原来的sendAction to方法。
    [self __uxy_sendAction:action to:target forEvent:event];
}

@end

2.对UIButton进行扩展

该方案来自 http://www.tuicool.com/articles/NJvmIf

这个在点击UITabbar上的按钮时会崩溃,提示
-[UITabBarButton cs_acceptEventTime]: unrecognized selector sent to instance 0x7fc9d8f36c50,自己找了好久都没有找到原因,后来参考
http://blog.jobbole.com/79580/ 改写了load方法就好了,原因不明白一直不明白,UIButton继承UIControl应该没有什么问题,为什么UITabbarButton会出错呢。方案一对UIControl进行扩展,在load方法里面直接进行了交换,是因为UIControl的sendAction:to:event方法确实是存在的,也许UITabbarButton有特殊的地方吧,就是没有这个方法。

@implementation UIButton (delay)

// 因category不能添加属性,只能通过关联对象的方式。
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";

- (NSTimeInterval)cs_acceptEventInterval {
    return  [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
}

- (void)setCs_acceptEventInterval:(NSTimeInterval)cs_acceptEventInterval {
    objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(cs_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

static const char *UIControl_acceptEventTime = "UIControl_acceptEventTime";

- (NSTimeInterval)cs_acceptEventTime {
    return  [objc_getAssociatedObject(self, UIControl_acceptEventTime) doubleValue];
}

- (void)setCs_acceptEventTime:(NSTimeInterval)cs_acceptEventTime {
    objc_setAssociatedObject(self, UIControl_acceptEventTime, @(cs_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

// 在load时执行hook
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        //分别获取
        SEL beforeSelector = @selector(sendAction:to:forEvent:);
        SEL afterSelector = @selector(cs_sendAction:to:forEvent:);
        
        Method beforeMethod = class_getInstanceMethod(class, beforeSelector);
        Method afterMethod = class_getInstanceMethod(class, afterSelector);
        //先尝试给原来的方法添加实现,如果原来的方法不存在就可以添加成功。返回为YES,否则
        //返回为NO。
        //UIButton 真的没有sendAction方法的实现,这是继承了UIControl的而已,UIControl才真正的实现了。
        BOOL didAddMethod =
        class_addMethod(class,
                        beforeSelector,
                        method_getImplementation(afterMethod),
                        method_getTypeEncoding(afterMethod));
        NSLog(@"%d",didAddMethod);
        if (didAddMethod) {
            // 如果之前不存在,但是添加成功了,此时添加成功的是cs_sendAction方法的实现
            // 这里只需要方法替换
            class_replaceMethod(class,
                                afterSelector,
                                method_getImplementation(beforeMethod),
                                method_getTypeEncoding(beforeMethod));
        } else {
            //本来如果存在就进行交换
            method_exchangeImplementations(afterMethod, beforeMethod);
        }
    });
}
- (void)cs_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    if ([NSDate date].timeIntervalSince1970 - self.cs_acceptEventTime < self.cs_acceptEventInterval) {
        return;
    }
    if (self.cs_acceptEventInterval > 0) {
        self.cs_acceptEventTime = [NSDate date].timeIntervalSince1970;
    }
    [self cs_sendAction:action to:target forEvent:event];
    
}
@end

  • 优点:有效解决了用户双击UI造成事件触发两次的情况(不仅仅局限网络请求)/
  • 缺点 :在网络不好的情况下,很可能在m秒内确实没有收到服务器响应。如果用户一直点击按钮,很可能触发重复点击。而且可能和系统以及存在的事件冲突,有的时候会产生莫名其妙的错误。比如我加入这个类扩展后,项目中选择照片时候进行拍照上传的时候,本来需要点击一下拍摄按钮就可以成功的事情,确需要长时间触摸才能生效,所以这个方案待改进。

客户端网络请求方法中过滤

一个网络请求包含两部分:url参数,因此我们可以在网络请求方类里面增加一个NSMutableArray,用户url参数的md5进行一次加密作为key,发送之前我们可以对其值赋值为任意固定值,当服务器返回结果的时候我们可以将这个键值对移除。每次发送网络请求前,先从这个字典中查看本次请求的md5值是否存在,如果存在表明本次请求已经发送但是尚未收到响应,此时应该return,不再进行网络请求,否则就是收到响应了或者该请求是第一次发出,改方法貌似不错

注意:有的时候参数包含了时间戳,这样计算永远会不相同的,md5加密之前要清除参数中的时间戳或者随机字段。


交给服务器解决

上面的办法都是客户端进行解决的,其实仔细想想这个问题服务器端难道就能完全没有责任吗?显然不是! 比如有人恶意模仿客户端模拟频繁向服务器发出http请求,这势必会造成服务器端资源浪费,虽然说http协议是不能记住状态的(需要靠session技术实现),但是服务器对这样的行为就束手无策,显然是不符合常理的。介于本人对服务器的技术了解有限,所以感觉应该上一种解决方案里面的客户端实现的过滤加入到服务器端实现,基本和客户端一致。

具体方案参考:


服务器把每次把收到的请求进行MD5加密,作为一个字典的键,值可以设置任意,然后查找数据库,查找回来以后通过适当的形式返回客户端,在查找数据期间,收到请求先从字典查找键是否存在如果已经存在就不作出响应,因为正在查找中,否则操作数据库查找数据,并且将链接键入到字典里面。

上述方案是本人工作中的思考还有互联网上查找的方案总结,难免有不足之处,仅供参考。希望各位能够提供更好的解决方案,欢迎留言。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,495评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,022评论 4 62
  • 那么好的天气,却是我负能量爆棚的一天!我想发泄,却只能憋着,估计得癌症的人都是这么郁闷死的吧
    _子曰_阅读 120评论 0 0
  • 姓名:魏正君《六项精进》第270期感谢2组 公司:绵阳大北农农牧科技有限公司 【日精进打卡第10天】 【知~学习】...
    莫心莫肺阅读 146评论 0 0