很多应用都会有这样一种登录设置:初次登录显示初次登录界面,若用户不登录可选择游客身份进入应用,当点击应用显示界面的某部分时,则弹出再次登录页面提示用户登录,此时用户登录则以用户身份进入应用。明析业务需求后,便可根据业务需求进行业务设计,可以先通过逻辑图理顺业务:
根据逻辑图,我们应当在程序初次进入时,首先验证账号是否存在或合法,根据判断结果进行分支处理。为方便账号判断我们,可以先创建一个账号管理工具类统一处理账号问题:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36@implementation CHAccountHelper
// 读取账号
+ (NSString *)account
{
return [CHUserDefaultsHelper account];
}
// 存储账号
+ (void)saveAccount:(NSString *)account
{
// Check Account
// Save Account
[CHUserDefaultsHelper saveAccount:account];
}
// 移除账号
+ (void)removeAccount
{
[CHUserDefaultsHelper removeAccount];
}
// 判断账号是否存在
+ (BOOL)isAccountExist
{
NSString *account = [CHAccountHelper account];
if (account && account.length > 0)
{
return YES;
}
return NO;
}
@end
这里,通过NSUserDefaults
来模拟账号存取。同时,为方便管理及后续维护,我们通常不通过账号管理工具类来直接进行账号数据的操作,而是根据存储方式,创建与之对应的数据存储工具类管理数据层。这样做的目的在于减低耦合性,将上层业务与底层数据进行一定的隔离,当数据层变动时(如存储方式从NSUserDefaults
替换为NSKeyedArchiver
),只需更改数据层,业务方并不会有太大改动,便于后续维护。再而,为方便数据层的管理,我们通常还会以分类方法的形式为数据存储工具类进行业务扩展:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20static NSString * const ACCOUNT_KEY = @"account";
@implementation CHUserDefaultsHelper (configureAccount)
+ (void)saveAccount:(NSString *)account
{
[CHUserDefaultsHelper saveString:account withKey:ACCOUNT_KEY];
}
+ (NSString *)account
{
return [CHUserDefaultsHelper stringWithKey:ACCOUNT_KEY];
}
+ (void)removeAccount
{
[CHUserDefaultsHelper removeObjectForKey:ACCOUNT_KEY];
}
@end
账号管理工具类设计完成后,便可根据逻辑图进一步组织业务代码。先在程序入口处理判断分支,若本地存在账号则以会员进入首页:1
[[UIApplication sharedApplication].keyWindow setRootViewController:[CHTabBarController new]];
否则,进入登录界面(首次):1
2
3CHNavigationController *navigationController = [[CHNavigationController alloc] initWithRootViewController:[CHLoginViewController new]];
[[UIApplication sharedApplication].keyWindow setRootViewController:navigationController];
在登录界面(首次)用户可选择登录以会员进入首页或者选择随便看看以游客进入首页。若选择随便看看,则需根据业务需要,在部分界面进行一些拦截判断,针对会员和游客做不同处理。以下提供两种常见的判断需求处理:
点击按钮,会员显示Alert界面,游客进入登录界面(再次)提示登录:1
2
3
4
5
6
7
8
9
10
11// 会员
if ([CHAccountHelper isAccountExist])
{
[[[UIAlertView alloc] initWithTitle:@"You have logged in!" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show];
return;
}
// 呈现登录界面(再次)提示游客登录
CHNavigationController *navigationController = [[CHNavigationController alloc] initWithRootViewController:[CHReLoginViewController new]];
[self.tabBarController presentViewController:navigationController animated:YES completion:completion];
点击TabBar的某个BarItem,会员可以进入,游客则进入登录界面(再次)提示登录:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if (![CHAccountHelper isAccountExist]) // 游客
{
if ([[(CHNavigationController *)viewController topViewController] isKindOfClass:[CHAboutViewController class]])
{
CHNavigationController *navigationController = [[CHNavigationController alloc] initWithRootViewController:[CHReLoginViewController new]];
[tabBarController presentViewController:navigationController animated:YES completion:completion];
return NO;
}
}
return YES;
}
需要注意的是,这里我们针对首次登录和再次登录情况分别提供了两个ViewController
进行管理(CHLoginViewController
和CHReLoginViewController
)。首次登录和再次登录虽然界面基本一致,但是分属不同的业务————首次登录和再次登录,不应该在一个ViewController
进行业务分离(诸如LoginType
等入口判断),这样一旦业务变动,判断只会愈加复杂,导致代码难以管理和维护。而将业务划归不同的ViewController
管理能极大地避免这种情况,缺点仅在于多了点代码。同时,为了提高代码的复用性,我们还可以对登录界面进行封装处理,共享界面布局,通过代理或Block将点击事件回传,分别供首次登录和再次登录调用,这里以代理为例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20@protocol CHLoginViewDelegate <NSObject>
@optional
- (void)loginView:(CHLoginView *)loginView didClickLoginButton:(UIButton *)button;
- (void)loginView:(CHLoginView *)loginView didClickLookArroundButton:(UIButton *)button;
@end
// CHLoginViewController调用
- (void)loginView:(CHLoginView *)loginView didClickLookArroundButton:(UIButton *)button
{
[[UIApplication sharedApplication].keyWindow setRootViewController:[CHTabBarController new]];
}
// CHReLoginViewController调用
- (void)loginView:(CHLoginView *)loginView didClickLookArroundButton:(UIButton *)button
{
[self dismissViewControllerAnimated:YES completion:nil];
}
至此需求基本完成。但是观察代码,我们会发现跳转部分有大量重复代码,且跳转逻辑分散在项目的各个角落,不利于代码的管理和维护。对此,我们可以通过将跳转抽象为一类业务,从业务层上对跳转业务进行统一调度和管理:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54@interface CHService : NSObject
// 业务回调处理
typedef void(^CHServiceSuccessHandle)();
typedef void(^CHServiceFailureHandle)();
typedef void(^CHServiceAllCompletionHandle)();
// 跳转业务
// 跳转到登录界面(首次)
+ (void)handleSkipToLoginViewControllerCompletion:(CHServiceAllCompletionHandle)completion
{
CHNavigationController *navigationController = [[CHNavigationController alloc] initWithRootViewController:[CHLoginViewController new]];
[[UIApplication sharedApplication].keyWindow setRootViewController:navigationController];
!completion?:completion();
}
// 跳转到登录界面(再次)
+ (void)handleSkipToReLoginViewControllerCompletion:(CHServiceAllCompletionHandle)completion
{
UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
if (!rootViewController || ![rootViewController isKindOfClass:[CHTabBarController class]]) return;
CHNavigationController *navigationController = [[CHNavigationController alloc] initWithRootViewController:[CHReLoginViewController new]];
[rootViewController presentViewController:navigationController animated:YES completion:completion];
}
// 跳转到首页
+ (void)handleSkipToHomeViewControllerComletionHandle:(CHServiceAllCompletionHandle)completion
{
[[UIApplication sharedApplication].keyWindow setRootViewController:[CHTabBarController new]];
!completion?:completion();
}
// 跳转到首页(未登录)
+ (void)handleSkipToHomeViewControllerBeforeLoginCompletion:(CHServiceAllCompletionHandle)completion
{
// do something
[CHService handleSkipToHomeViewControllerComletionHandle:completion];
}
// 跳转到首页(已登录)
+ (void)handleSkipToHomeViewControllerAfterLoginCompletion:(CHServiceAllCompletionHandle)completion
{
// do something
[CHService handleSkipToHomeViewControllerComletionHandle:completion];
}
进一步,我们也可以对登录和登出业务做同样处理:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49// 模拟登录处理
+ (void)handleLoginWithParameters:(NSString *)parameters successHandle:(CHServiceSuccessHandle)success failureHandle:(CHServiceFailureHandle)failure allCompletionHandle:(CHServiceAllCompletionHandle)completion
{
// Send Login Request
BOOL sendLoginRequest = YES;
// BOOL sendLoginRequest = NO;
// If Login success
if (sendLoginRequest)
{
// Save account
[CHAccountHelper saveAccount:parameters];
!success?:success();
[CHService handleSkipToHomeViewControllerAfterLoginCompletion:completion];
return;
}
// Failure
!failure?:failure();
!completion?:completion();
}
// 模拟登出处理
+ (void)handleLogoutSuccessHandle:(CHServiceSuccessHandle)success failureHandle:(CHServiceFailureHandle)failure allCompletionHandle:(CHServiceAllCompletionHandle)completion
{
// Send Logout Request
BOOL sendLogoutRequest = YES;
// BOOL sendLoginRequest = NO;
// If Logout success
if (sendLogoutRequest)
{
// remove account
[CHAccountHelper removeAccount];
!success?:success();
[CHService handleSkipToLoginViewControllerCompletion:completion];
return;
}
// Failure
!failure?:failure();
!completion?:completion();
}
最后,我们在需要的位置调用即可:1
2
3
4
5
6
7
8
9
10
11
12
13- (void)loginView:(CHLoginView *)loginView didClickLoginButton:(UIButton *)button
{
[CHService handleLoginWithParameters:@"account" successHandle:^{
} failureHandle:^{
} allCompletionHandle:nil];
}
- (void)loginView:(CHLoginView *)loginView didClickLookArroundButton:(UIButton *)button
{
[CHService handleSkipToHomeViewControllerBeforeLoginCompletion:nil];
}
以上例子除了提供处理首次登录和再次登录的一种方案外,也提供了一般开发中实现业务的思维过程:分析业务,理顺逻辑,组织代码,实现需求和分离优化。对开发者而言,或许后者比前者更为重要。
附,示例代码:https://github.com/ColinHwang/Demo-of-Tutorial-in-Blog/tree/master/iOS-FirstLoginAndReLoginDemo