iOS中如何确保页面在获取RESTful API Token后再加载?
老哥,我刚好踩过这个坑,给你几个落地性强的方案,帮你把Token获取和页面加载的逻辑理顺,彻底告别每个API都要查Token的麻烦:
方案一:用启动过渡页+异步回调控制根页面加载
这是最常用也最友好的方式,用户体验不会打折扣:
- 先在Storyboard里设置一个过渡加载页(比如叫
LaunchLoadingVC)作为初始ViewController,页面上放个加载动画(UIActivityIndicatorView或者自定义的加载图),没有其他可交互元素。 - 在
AppDelegate.m的didFinishLaunchingWithOptions里,先正常初始化这个加载页,然后启动你的NSURLSessionDataTask去拿Token:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 初始化加载页 UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; UIViewController *loadingVC = [storyboard instantiateViewControllerWithIdentifier:@"LaunchLoadingVC"]; self.window.rootViewController = loadingVC; [self.window makeKeyAndVisible]; // 异步获取Token NSURLSession *session = [NSURLSession sharedSession]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"你的Token接口地址"]]; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (!error && data) { // 解析Token并保存(比如存在UserDefaults或者Keychain) NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; NSString *token = result[@"token"]; [[NSUserDefaults standardUserDefaults] setObject:token forKey:@"APIToken"]; // Token获取成功,切换到主页面(比如MainTabBarVC) UIViewController *mainVC = [storyboard instantiateViewControllerWithIdentifier:@"MainTabBarVC"]; self.window.rootViewController = mainVC; } else { // 处理Token获取失败的情况,比如显示重试按钮 // 可以给loadingVC加个方法,显示错误提示 [(LaunchLoadingVC *)loadingVC showTokenError]; } }); }]; [task resume]; return YES; }
- 这样用户打开App看到的是加载页,直到Token拿到手才会进入主页面,从根源上保证所有后续页面加载时Token已经存在。
方案二:全局Token状态管理+页面路由拦截
如果不想用过渡页,也可以搞个全局的Token管理器,统一控制页面访问:
- 创建一个单例类
APIManager,用来管理Token状态和所有API请求:
@interface APIManager : NSObject @property (nonatomic, assign, readonly) BOOL isTokenReady; @property (nonatomic, copy, readonly) NSString *apiToken; + (instancetype)sharedManager; - (void)fetchTokenWithCompletion:(void(^)(BOOL success))completion; @end @implementation APIManager static APIManager *_sharedInstance; @property (nonatomic, assign) BOOL isTokenReady; @property (nonatomic, copy) NSString *apiToken; + (instancetype)sharedManager { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedInstance = [[self alloc] init]; }); return _sharedInstance; } - (void)fetchTokenWithCompletion:(void(^)(BOOL success))completion { // 这里写获取Token的异步逻辑,和之前一样 NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:... completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (!error) { self.apiToken = @"解析后的Token"; self.isTokenReady = YES; completion(YES); } else { self.isTokenReady = NO; completion(NO); } }); }]; [task resume]; } @end
- 在
AppDelegate启动时先调用fetchToken,然后在Storyboard的初始页面(比如一个空白的入口页)的viewDidAppear里检查Token状态:
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; APIManager *manager = [APIManager sharedManager]; if (manager.isTokenReady) { // 跳转到主页面 UIViewController *mainVC = [self.storyboard instantiateViewControllerWithIdentifier:@"MainTabBarVC"]; [self presentViewController:mainVC animated:YES completion:nil]; } else { // 显示加载动画,等待Token [manager fetchTokenWithCompletion:^(BOOL success) { if (success) { UIViewController *mainVC = [self.storyboard instantiateViewControllerWithIdentifier:@"MainTabBarVC"]; [self presentViewController:mainVC animated:YES completion:nil]; } else { // 处理错误,比如显示重试按钮 } }]; } }
- 这种方式的好处是可以统一管理Token的刷新、过期重取等逻辑,后续所有API请求都通过
APIManager发起,不用再单独检查Token。
方案三:同步等待Token(不推荐,仅应急用)
如果实在要在启动时同步拿到Token,也可以用信号量,但强烈不推荐,因为会阻塞主线程,可能导致App启动超时被系统杀掉:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); __block NSString *token = nil; NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:... completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (!error) { token = @"解析后的Token"; } dispatch_semaphore_signal(semaphore); }]; [task resume]; // 等待最多5秒,避免无限阻塞 dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)); if (token) { // 正常加载主页面 UIViewController *mainVC = [self.storyboard instantiateViewControllerWithIdentifier:@"MainTabBarVC"]; self.window.rootViewController = mainVC; } else { // 处理Token获取失败,比如显示错误页 } return YES; }
这种方式会让App启动时卡住,用户体验很差,除非万不得已不要用。
总结一下,方案一和方案二是最优解,核心思路就是把Token获取作为页面加载的前置条件,从App启动的入口就把这个逻辑堵死,这样后续所有页面都不用再检查Token是否存在了。
内容的提问来源于stack exchange,提问作者foresightyj
相关产品推荐
相关产品推荐

