在实际开发中几乎所有的APP都会存在用户体系,假如没有涉及用户体系的APP,提交审核的时候有很大概率会被苹果爹地直接拒绝不允许上架。而有了用户体系,那么就肯定会存在登录以及保持登录的需求,要不然用户每次启动APP都得重新登录,那就乖乖了。保持登录状态的方式目前大致有以下几种方式:
一、利用Cookie机制实现
我们知道cookie是为了解决http无状态的一种技术,被电商、oa等web应用广泛使用。如果我们的App和后端通讯采用的http通讯方式,可以利用cookie技术进行登录状态保持。比如我们可以把sessionID和有效期保存在cookie中,发给前端App,前端App收到后保存在本地。当访问后端服务把sessionID和有效期作为参数传给后台进行认证。直到sessionID失效,用户都不需要重新登录。
二、本地保存用户名和密码
当用户第一次输入账号和密码的时候,APP端本地保存用户的账号和密码,下次启动的时候获取本地保存的账户和密码进行登录,由我们开发者在后台进行登录处理,不过这个有个不是太合适的就是相当于在APP本地保存了用户的这些信息,意义上来说不安全,不过大致的实现方式如下:
1、在登陆页面对应的类loginViewController.h中定义两个TextField和一个Button,用来接受用户输入的用户名和密码,点击按钮登陆,如果登陆成功,就将用户的登陆信息存放在UserDefault中,然后跳入主页面。
@interface LoginViewController ()
@property (nonatomic, strong) UITextField *username;
@property (nonatomic, strong) UITextField *password;
@end
2、在loginViewController.m中实现两个TextField和一个Button,直接实现按钮的点击登录事件:登录请求成功后,走成功回调,回调下面实现将用户名和密码存入UserDefault中,页面跳转到主页面。下面数据请求的代码略去了,直接上存储UserDefault代码,跳转主页面。
NSString *username = self.username.text;
NSString *password = self.password.text;
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:username forKey:@"username"];
[userDefaults setObject:password forKey:@"password"];
[userDefaults synchronize];
UITabBarController *tabBarVc = [[UITabBarController alloc] init];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:tabBarVc];
AppDelegate *appdelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
appdelegate.window.rootViewController = nc;
3、在AppDelegate.m中实现:用户第一次进入APP时自动进入登录注册页,提示用户注册登录,用户登录成功后才进入主页,再次进入APP时,不用再次登录就直接进到主页了
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//初始化Window
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor whiteColor];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (![userDefaults objectForKey:@"username"]){
//进入主页
UITabBarController *tabbarVc = [[UITabBarController alloc]init];
UINavigationController *navVc = [[UINavigationController alloc]initWithRootViewController:tabbarVc];
self.window.rootViewController = navVc;
} else {
//进入登录页面
LoginViewController *loginVc = [[LoginViewController alloc] init];
[self.navController pushViewController:loginVc animated:YES];
}
[self.window makeKeyWindow];
return YES;
}
4、退出登录
//退出登录,清除用户信息
- (void)logout{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults removeObjectForKey:@"username"];
[userDefaults removeObjectForKey:@"password"];
//跳转登陆页
LoginViewController *loginVc = [[LoginViewController alloc] init];
[self.navController pushViewController:loginVc animated:YES];
}
三、前后端配合采用token方式
token方式在app认证上用的比较普遍,App初始登录时,提交账号和密码数据给服务端,服务端根据定义的的策略生成一个token字符串,token字符串中可以包含用户信息、设备ID等信息以保证用户的唯一性,服务端并对token设置一定的期限。服务端把生成的token字符串传给客户端,客户端保存token字符串,并在接下来的请求中带上这个字符串。相对于在App本地token的安全性更高了。
说明:这里说说关于设备唯一ID的小细节,如果直接把设备唯一ID传给后台的话,这里可能会出现一个问题,比如在同一台手机退出登录后再登录其他账号,那么就会出现多个账号的设备ID就都一样了。这样会带来什么后果呢?比如如果还使用极光推送的话,用这个设备ID注册别名的话,那么就会出现多个账号的别名都是一样的,那么推送就乱了。
//uuid + 手机 + iOS
NSString *uuid = [[GetUUID getUUID] stringByReplacingOccurrencesOfString:@"-" withString:@""];
//截取UUID的后十位
NSString *uuidPart = [uuid substringFromIndex:uuid.length - 10];
NSString *aliasStr = [NSString stringWithFormat:@"%@_%@_iOS",uuidPart,phoneNum];
上面就是我在实际开发使用设备ID唯一性的处理,就是UUID + 手机号码 + iOS(平台),安卓就是UUID + 手机号码 + Android。这样子就不仅避免了同一台设备登录多个账号的问题,还区分了不同平台登录的账号。
1、用户第一次登录之后本地保存token
[[NSUserDefaults standardUserDefaults] setObject:[responseObject.data objectForKey:@"token"] forKey:token];
[[NSUserDefaults standardUserDefaults] synchronize];
2、再次启动的时候获取本地保存的token
NSString *token = [[NSUserDefaults standardUserDefaults] objectForKey:token];
然后用这个token请求一个后台验证该token是否在有效期的接口,接着根据后台返回的信息进行相关页面的跳转处理。
到此为止,目前几种保持登录状态的方法大致介绍完了,不知道细心的你有没有发现一个小细节,那就是很多都是采用根据登录状态是否有效来设置window的rootViewController根控制器,就是说没有登录状态下rootViewController是loginViewController登录页,有登录状态有效的情况下rootViewController是navVC导航控制器或者其他UIViewController控制器等,如下:
if(有登录状态){
BaseTabBarViewController *baseVc = [[BaseTabBarViewController alloc] init];
self.window.rootViewController = baseVc;
}else{
LoginViewController *loginVc = [[LoginViewController alloc] init];
self.window.rootViewController = loginVc;
}
但是有一种比较特殊的情况,其实也不特殊,就是也是一种产品形式,那就是不管有没有登录状态下,window的rootViewController都是BaseTabBarViewController,就是说不会随着登录状态失效根控制器进行更换,因为很多APP也都存在游客模式,就是不用登录状态也可以进入APP进行基础的操作,而当点击到需要登陆地方时没有登录状态的话就跳转到登录页,有的话就继续下一步操作,那么这个时候我们应该怎么处理呢?下面继续说说暂时个人想到的处理方式:
处理方式1:
首先设置window的rootViewController根控制器为BaseViewController(UIViewController类型的),然后再立即获取用户的登录状态是否有效,不管登录状态是否有效都会把window的rootViewController设置为BaseTabBarViewController,只是在获取到用户登录状态信息之后BaseTabBarViewController里的有些UI展示不一样,直接上代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//token登录
[self initService];
//先创建根控制器,然后会在AppDelegate+AppService分类中进行根控制器的替换
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//先进行网络监测防止手机开启飞行模式时进入白屏
if (![XMFUserHttpHelper checkNetStatus]) {
XMFTabBarViewController *baseVc = [[XMFTabBarViewController alloc] init];
self.window.rootViewController = baseVc;
}else{
XMFBaseViewController *baseVc = [[XMFBaseViewController alloc] init];
self.window.rootViewController = baseVc;
}
[self.window makeKeyAndVisible];
}
//初始化服务
-(void)initService{
NSString *token = [[NSUserDefaults standardUserDefaults] objectForKey:token];
if (![sessionId isBlankString]) {//先判断是否是空字符串
//获取保存的token供下面接口的头部使用
[XMFGlobleManager getGlobleManager].token = token;
[SVProgressHUD show];
[[XMFUserHttpHelper sharedManager] sessionLoginCheckWithParam:nil success:^(XMFResponseModel *responseObject) {
[SVProgressHUD dismiss];
//token在有效期内
if (responseObject.returnCode == XMFHttpReturnCodeSuccess) {
token = [responseObject.data objectForKey:@"token"];
[XMFGlobleManager getGlobleManager].isLogin = YES;
//发送登录成功的通知
[[NSNotificationCenter defaultCenter] postNotificationName:DWQ_NOTIFY_LoginState_Change object:@YES userInfo:nil];
[[NSUserDefaults standardUserDefaults] setObject:[responseObject.data objectForKey:@"token"] forKey:token];
[[NSUserDefaults standardUserDefaults] synchronize];
[self setRootViewController];
}else{
[self setRootViewController];
//登录失败
[[NSNotificationCenter defaultCenter] postNotificationName:DWQ_NOTIFY_LoginState_Change object:@NO userInfo:nil];
}
} failure:^(NSError *error) {
[self setRootViewController];
}];
}else{
[self setRootViewController];
}
}
//等待验证token是否有效之后进行设置根控制器
-(void)setRootViewController{
XMFTabBarViewController *tabvc = [[XMFTabBarViewController alloc] init];
self.window.rootViewController = tabvc;
}
这样子大致就把不管有无登录状态rootViewController都为XMFTabBarViewController的情况处理好了,不过这里面还有一个小细节会影响体验,仔细看看上面是不是登录状态是否有效取决于后台返回,网络请求快的情况下可能感受不到,那万一网络请求缓慢的话,那就会出现用户会在等待AFHTTPSessionManager的manager.requestSerializer.timeoutInterval = 20;时间之后才出现了XMFTabBarViewController,这样的体验是有点影响的,那么再来继续优化一下。
处理方式2:
这里直接不管是否有登录状态都直接设置window的rootViewController为XMFTabBarViewController,代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//token登录
[self initService];
//先创建根控制器,然后会在AppDelegate+AppService分类中进行根控制器的替换
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
ZFTabBarViewController *baseVc = [[ZFTabBarViewController alloc] init];
self.window.rootViewController = baseVc;
[self.window makeKeyAndVisible];
}
//初始化服务
-(void)initService{
NSString *token = [[NSUserDefaults standardUserDefaults] objectForKey:token];
if (![sessionId isBlankString]) {//先判断是否是空字符串
//获取保存的token供下面接口的头部使用
[XMFGlobleManager getGlobleManager].token = token;
[SVProgressHUD show];
[[XMFUserHttpHelper sharedManager] sessionLoginCheckWithParam:nil success:^(XMFResponseModel *responseObject) {
[SVProgressHUD dismiss];
//token在有效期内
if (responseObject.returnCode == XMFHttpReturnCodeSuccess) {
token = [responseObject.data objectForKey:@"token"];
[XMFGlobleManager getGlobleManager].isLogin = YES;
//发送登录成功的通知
[[NSNotificationCenter defaultCenter] postNotificationName:DWQ_NOTIFY_LoginState_Change object:@YES userInfo:nil];
[[NSUserDefaults standardUserDefaults] setObject:[responseObject.data objectForKey:@"token"] forKey:token];
[[NSUserDefaults standardUserDefaults] synchronize];
}else{
//登录失败
[[NSNotificationCenter defaultCenter] postNotificationName:DWQ_NOTIFY_LoginState_Change object:@NO userInfo:nil];
}
} failure:^(NSError *error) {
}];
}
}
然后在会随着登录状态改变UI界面的页面进行对“isLogin”属性进行kvo监测,接着进行相应的UI改变,代码如下:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//增加对登录状态"isLogin"值变化的观察
kWeakSelf(self)
[JJKeyValueObserver addObserveObject:[XMFGlobleManager getGlobleManager] keyPath:@"isLogin" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew changeBlock:^(NSDictionary *dict) {
// DLog(@"登录状态发生了改变:%@",[dict description]);
weakself.rightLoginBtn.selected = [XMFGlobleManager getGlobleManager].isLogin;
}];
}
同时博主的kvo使用的是第三方JJKeyValueObserver,大家有需要的可以去GitHub上下载使用,也可以自己实现kvo,原理是一样的。那经过这么一番处理之后,整个保持登录的过程差不多就处理完了,同时博主开发中采用的是“前后端配合采用token方式”。
如果以上的分享帮助到你了,欢迎分享,更欢迎赞赏,也可以直接打开支付宝、微信、QQ的扫一扫功能直接扫下面的支付宝、微信、QQ三合一打赏码进行打赏支持作者创作,感谢感谢!
image
欢迎和我交流,QQ:834537795(小蜜蜂)