关于这个标题,起因是这样的。
最近一次做项目需求时,遇到这样一个需求,就是本来我们App
是必须注册或者第三方登录才可以使用,现在希望不登录也可以浏览App
里面的内容,只是在需要的时候才提示登录,并且在用户没有登录的情况下,用户选择并登录成功了,程序需自动完成用户操作登录前的操作。比如购买商品时没有登录,用户登录成功后,直接跳转至订单确认页面。
在接到这个需求时,我们的App
功能已经很多了,评估了下这个需求,发现App
里面很多功能是需要登录才可以操作,比如关注用户、购买商品、私信聊天、评论等等,而且这些功能的入口也比较多。
这么多的地方我们都要去写判断的代码显然是不科学的,那么有没有简单点的方式呢?怎么避免我们去做苦力活呢?🤔🤔🤔
于是,进一步分析,发现这些功能大部分都是用户主动通过点击按钮来触发下一步操作。此时,我们把关注点移到按钮UIButton
上。
最开始想到的办法是自定义一个button
,让所有需要登录操作的按钮继承这个按钮,然后,在这个按钮里面拦截自身事件进一步处理。但是,发现这么做还是需要改大量的代码。接着想到用类别来做,这样直接给按钮增加一个BOOL
属性,设置为YES
的按钮视为需要做登录才可以操作的按钮。然后,对于需要登录操作的按钮,在分类里面拦截其点击事件,并记录target
和action
,然后先判断是否登录:如果没有登录则丢弃其target和action,并且提示用户登录;如果用户已经登录或者登录成功了,则继续让target
执行action
,这样完美解决我们的需求,也只需要很少的代码即可搞定。
这个方案看似很不错,不过在实际做的时候还是走了弯路。一开始,我们想从下面方法入手
- (void)addTarget:(nullableid)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents
但是发现根本就不能实现。经过查找,找到了下面这个方法:
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
关于这个方法,苹果给了如下解释:
send the action. the first method is called for the event and is a point at which you can observe or override behavior. it is called repeately by the second.
这正是我们要找的方法,于是我们重写此方法,如下:
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
if (self.checkLogin)
{
self.selector = NSStringFromSelector(action);
self.objClass = target;
[self checkIsLogin];
}
else
{
[super sendAction:action to:target forEvent:event];
}
}
- (void)checkIsLogin
{
__weak typeof(self) weakSelf = self;
[LoginManager checkLoginSuccess:^{
SEL sel = NSSelectorFromString(weakSelf.selector);
if ([weakSelf.objClass respondsToSelector:sel])
{
if ([weakSelf.selector hasSuffix:@":"])
{
objc_msgSend(weakSelf.objClass, sel, self);
}
else
{
objc_msgSend(weakSelf.objClass, sel);
}
}
}];
}
简单解释下这段代码:
当按钮事件执行时会走sendAction:to:forEvent:
这个方法,于是,我们在这个方法里面,先判断该按钮是否需要登录后再操作,如果需要,阻断事件传递,并记录下按钮的action
和target
,然后判断是否登录了,如果已经登录或者用户登录成功了,那么再调用objc_msgSend(self.objClass, self.selector)
去实现按钮事件,如果用户放弃登录或者登录失败,则不做处理。
实现了上面的方法之后,我们只需要找出那些按钮事件需要登录后才能操作,然后,设置按钮的checkLogin = YES
即可,这样是不是省了很多不必要的代码。
到此,上面的实现已经解决了所有按钮点击需要判断登录的操作。还有些是上述方式解决不了的,则使用LoginManager
单独处理下,幸运的是,几乎很少地方需要单独处理。
通过这个案例:一方面巩固了对sendAction:to:forEvent:
这个方法的理解;另一方面在做需求的时候一定要发散思维,找到更合理的解决方法。
欢迎大家留言讨论,如果你有更好地方法,欢迎分享!
网友评论
button用- (BOOL)sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event 这个知道
//在基类接收到登录异常通知方法里添加这段代码
if (self.isViewLoaded && self.view.window) {
// 弹出登录控制器
......
}
这样在项目里统一处理,不用担心哪些地方需要登录处理
jihongboo刚刚
我们的项目也遇到了这样的问题,我的解决方案是不拦截用户请求,直接发到后端,但需要带token(登录)才能操作的API后端会返回403(权限错误),我就是在统一的错误处理的地方判断服务器返回代码,如果代码是403则提示登录。当然这也有弊端,就是需要保证用户在登录后必须保证正常的请求发出的token必须正确。不过其优点也很明显,我们甚至不用去判断每个按钮是否需要登录,直接由后端来判断。
- (BOOL)sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event
根据target的类型来判断是否下发。如果要下发就调用原来的实现,如果需要拦截就在做完你的事后直接return NO。这样效率会更高,也无需子类化。如果你要拦截的都是UIButton,那hook UIButton的这个方法就行了,给UIButton关联一个bool值,根据这个值判断是否需要拦截就行了。
这个方法是写在自定义按钮里还是写在控制器里啊