iOS开发基础79-强制退出程序
iOS 应用退出方式完全解析:从 exit/abort/assert 到底层原理与崩溃处理
在 iOS 开发中,"退出应用"是一个看似简单实则有很多门道的话题。iOS 没有提供官方的退出应用 API,这是有意为之的设计。本文从进程退出的三种方式(自然死亡、自杀、他杀)讲起,深入解析 exit()、abort()、assert() 的底层原理与区别,补充异常捕获、崩溃处理、后台挂起、App Store 审核注意事项,并澄清实际开发中"退出登录"和"退出应用"的本质区别。
一、概述:iOS 为什么没有官方退出 API
1. iOS 的应用生命周期设计理念
和桌面操作系统(macOS、Windows)不同,iOS 的设计理念是:应用的生命周期由用户控制,而不是由应用自己控制。
- 用户通过 Home 键(或全面屏的上滑手势)将应用切换到后台。
- 用户通过上滑关闭应用(双击 Home 或从底部上滑暂停,再上滑某个应用卡片)强制终止应用。
- 应用不应该自己决定"我要退出",因为这会让用户感觉应用崩溃了。
2. 为什么没有官方退出 API
| 原因 | 说明 |
|---|---|
| 用户体验 | 应用自行退出没有过渡动画,用户会误以为是崩溃,体验糟糕 |
| 设计理念 | iOS 强调用户掌控应用生命周期,应用不应自行终止 |
| 后台机制 | iOS 有完善的后台挂起机制,应用不需要自己退出,系统会管理内存 |
| 审核风险 | 应用内提供"退出"按钮可能被 App Store 审核拒绝 |
一句话总结:iOS 应用的正确"退出"方式是用户按 Home 键回到桌面,而不是应用自己调用 exit()。
二、进程退出的三种方式
任何程序的退出都可以归为三类:自然死亡、自杀、他杀。
1. 自然死亡(正常退出)
程序执行完所有逻辑,从 main() 函数正常 return,进程自然结束。
int main(int argc, char * argv[]) {
// 程序逻辑
return 0; // 自然死亡,返回 0 表示正常退出
}
在 iOS 应用中,main() 函数会进入 UIApplicationMain 的事件循环(RunLoop),永远不会 return,所以 iOS 应用几乎不会"自然死亡"。只有命令行工具才会自然死亡。
2. 自杀(主动退出)
程序主动调用退出函数终止自己。iOS 中常见的"自杀"方式:
| 函数 | 说明 | 正常/异常 |
|---|---|---|
exit(int status) |
正常退出,调用 atexit 函数,返回状态码 | 正常退出 |
_exit(int status) / _Exit(int status) |
直接系统调用退出,不调用 atexit | 正常退出 |
abort() |
异常终止,发送 SIGABRT 信号,可能生成崩溃日志 | 异常退出 |
assert(condition) |
DEBUG 模式下条件不成立时调用 abort() | 异常退出 |
kill(getpid(), signal) |
给自己发信号终止 | 取决于信号 |
raise(signal) |
给自己发信号 | 取决于信号 |
3. 他杀(被系统/用户终止)
进程被外部力量终止,不是自己决定的。iOS 中常见的"他杀"场景:
| 场景 | 说明 |
|---|---|
| 用户上滑强制关闭 | 双击 Home 或上滑暂停,再上滑应用卡片 |
| 内存警告杀进程 | 应用在后台且内存不足时,系统优先杀后台应用 |
| 后台超时杀进程 | 应用申请后台执行时间(默认 30 秒~3 分钟),超时后系统杀进程 |
| 系统更新/重启 | 系统更新或重启时终止所有应用 |
| 看门狗超时 | 应用启动、响应、退出超时,系统看门狗(Watchdog)强制杀进程 |
| 调试器终止 | Xcode 点击 Stop 按钮终止应用 |
注意:"他杀"通常不会调用应用的
applicationWillTerminate:方法(用户上滑关闭会调用,内存警告杀进程不会调用)。
三、各种退出方式的底层原理与对比(重点)
1. exit() 详解
函数签名:
void exit(int status);
作用:正常终止程序,status 是返回给操作系统的状态码(0 表示正常,非 0 表示异常)。
exit() 的执行流程:
- 调用所有通过
atexit()注册的函数(按注册的逆序调用)。 - 刷新所有打开的文件流缓冲区(fflush)。
- 关闭所有打开的文件流。
- 删除
tmpfile()创建的临时文件。 - 调用
_exit(status)系统调用,进入内核,进程终止。
atexit 示例:
#include <stdlib.h>
#include <stdio.h>
void cleanup1() {
printf("cleanup1 called\n");
}
void cleanup2() {
printf("cleanup2 called\n");
}
int main() {
atexit(cleanup1);
atexit(cleanup2);
printf("main ending\n");
exit(0);
// 输出顺序:
// main ending
// cleanup2 called(逆序调用)
// cleanup1 called
}
iOS 中使用 exit():
- (void)exitApp {
// 保存数据、清理资源
exit(0); // 0 表示正常退出
}
⚠️ 重要:exit() 不会调用 applicationWillTerminate:
applicationWillTerminate: 是 UIApplicationDelegate 的方法,只有用户正常关闭应用(上滑关闭)时才会调用。调用 exit() 是进程级别的直接终止,不会走 UIKit 的退出流程,所以 applicationWillTerminate: 不会被调用。如果有数据需要在退出前保存,必须在调用 exit() 之前手动保存。
2. abort() 详解
函数签名:
void abort(void);
作用:异常终止程序,通常用于遇到不可恢复的严重错误时。
abort() 的执行流程:
- 解除对 SIGABRT 信号的阻塞(如果之前被阻塞)。
- 调用
raise(SIGABRT)给自己发送 SIGABRT 信号。 - SIGABRT 的默认处理动作是终止进程并生成核心转储(core dump)。
- 进程终止,返回状态码 134(128 + 6,SIGABRT 的信号编号是 6)。
abort() 和 exit() 的核心区别:
| 对比项 | exit() | abort() |
|---|---|---|
| 退出类型 | 正常退出 | 异常终止 |
| 调用 atexit | ✅ 会调用 | ❌ 不会调用 |
| 刷新/关闭文件 | ✅ 会 | ❌ 不会 |
| 底层实现 | 调用 _exit() 系统调用 | 调用 raise(SIGABRT) |
| 返回状态码 | 自定义(通常 0) | 固定 134(128+6) |
| 生成崩溃日志 | ❌ 不会 | ✅ 会(系统认为是崩溃) |
| 触发崩溃收集 | ❌ 不会 | ✅ 会(Bugly、友盟等会捕获) |
关键区别:
abort()会被系统判定为崩溃,会生成崩溃日志并被第三方崩溃收集 SDK(如 Bugly、Firebase Crashlytics)捕获。而exit()是正常退出,不会生成崩溃日志。所以绝对不要在生产环境用abort()退出应用,否则崩溃率会飙升,影响 App Store 排名和用户信任。
3. assert() 详解
宏定义:
#include <assert.h>
void assert(scalar expression);
作用:断言。如果 expression 为假(0),则输出错误信息并调用 abort() 终止程序。
特点:
- 仅在 DEBUG 模式下有效:Release 模式下
assert()会被编译为空(因为定义了NDEBUG宏),不会执行任何检查。 - 条件不成立时,输出文件名、行号、函数名、条件表达式,然后调用
abort()。 - 用于开发阶段的调试和防御性编程,确保假设条件成立。
示例:
- (void)processData:(NSData *)data {
// 断言 data 不为 nil,为 nil 时在 DEBUG 模式下崩溃
NSAssert(data != nil, @"data 不能为 nil");
// 处理数据
}
NSAssert是 Foundation 框架对assert的封装,用法类似,支持格式化字符串输出错误信息。
assert 的正确使用场景:
- 检查方法参数的合法性(开发阶段)。
- 检查内部状态是否符合预期。
- 检查"不可能发生"的情况(如果发生了说明有 bug)。
- 不要用 assert 处理可恢复的错误(如网络失败、文件不存在),这些应该用错误处理逻辑。
4. _exit() 和 _Exit()
#include <unistd.h>
void _exit(int status); // POSIX 标准
#include <stdlib.h>
void _Exit(int status); // C99 标准,和 _exit 等价
这两个函数是直接的系统调用,立即终止进程,不调用 atexit 函数,不刷新文件缓冲区。exit() 内部在做完清理工作后,最终也是调用 _exit()。
exit(0); // 会调用 atexit、刷新文件、关闭文件,然后调用 _exit(0)
_exit(0); // 直接系统调用退出,不做任何清理
5. 信号方式:kill() 和 raise()
#include <signal.h>
// 给指定进程发信号
int kill(pid_t pid, int sig);
// 给自己发信号
int raise(int sig);
常见的终止信号:
| 信号 | 编号 | 说明 |
|---|---|---|
SIGTERM |
15 | 终止请求(可被捕获/忽略),系统正常关闭应用时发这个 |
SIGKILL |
9 | 强制杀死(不可被捕获/忽略),内存警告杀进程用这个 |
SIGABRT |
6 | 异常终止,abort() 内部发这个 |
SIGSEGV |
11 | 段错误(野指针、内存访问违规),最常见的崩溃原因 |
SIGINT |
2 | 中断(Ctrl+C) |
// 给自己发 SIGTERM 信号终止(效果类似 exit,但走信号流程)
raise(SIGTERM);
// 给自己发 SIGKILL(强制杀死,不可拦截)
kill(getpid(), SIGKILL);
6. 退出方式完整对比表
| 方式 | 正常/异常 | 调用 atexit | 刷新文件 | 生成崩溃日志 | 调用 applicationWillTerminate | 生产环境可用 |
|---|---|---|---|---|---|---|
main return |
正常 | ✅ | ✅ | ❌ | - | ✅(命令行工具) |
exit(0) |
正常 | ✅ | ✅ | ❌ | ❌ | ⚠️ 不推荐 |
_exit(0) |
正常 | ❌ | ❌ | ❌ | ❌ | ⚠️ 不推荐 |
abort() |
异常 | ❌ | ❌ | ✅ | ❌ | ❌ 禁止 |
assert() |
异常 | ❌ | ❌ | ✅ | ❌ | ❌(仅 DEBUG) |
raise(SIGTERM) |
正常 | ✅ | ✅ | ❌ | ❌ | ⚠️ 不推荐 |
kill(SIGKILL) |
异常 | ❌ | ❌ | ❌ | ❌ | ❌ 禁止 |
| 用户上滑关闭 | 正常 | - | - | ❌ | ✅ | ✅(用户操作) |
| 内存警告杀进程 | 异常 | ❌ | ❌ | ❌ | ❌ | -(系统操作) |
四、实际开发中的"退出"场景
1. 最常见的"退出":退出登录(不是退出应用)
实际开发中,99% 的"退出"需求是退出登录,而不是退出应用。退出登录是回到未登录状态,应用继续运行。
退出登录的正确做法:
- (void)logout {
// 1. 清除用户数据
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"token"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"userInfo"];
[[NSUserDefaults standardUserDefaults] synchronize];
// 2. 清除缓存(可选)
[[SDImageCache sharedImageCache] clearMemory];
// 3. 切换根控制器到登录页
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
LoginViewController *loginVC = [storyboard instantiateViewControllerWithIdentifier:@"LoginViewController"];
UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:loginVC];
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
keyWindow.rootViewController = navVC;
[keyWindow makeKeyAndVisible];
// 4. 加过渡动画(可选)
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
transition.duration = 0.3;
[keyWindow.layer addAnimation:transition forKey:nil];
}
退出登录 ≠ 退出应用。退出登录只是清除用户状态并回到登录页,应用仍然在运行,用户可以重新登录。这是最正确、最推荐的做法。
2. 什么情况下可以考虑强制退出应用
虽然不推荐,但在某些极端场景下,可能需要强制退出:
| 场景 | 说明 | 建议 |
|---|---|---|
| 严重数据损坏 | 应用核心数据损坏,无法继续运行 | 提示用户后退出,引导重装 |
| 安全风险 | 检测到应用被破解/注入,存在安全风险 | 提示后退出 |
| 企业内部应用 | 企业分发的应用,有强制退出需求 | 可以使用,但需提示用户 |
| 越狱检测 | 金融类应用检测到越狱环境 | 提示后限制功能或退出 |
即使用这些场景,也应该:
- 先弹出提示框,告知用户原因。
- 用户确认后再退出。
- 退出前保存必要数据。
- 加淡出动画,避免突兀。
3. 带动画的退出示例(修正原文的过时代码)
原文用了 UIAlertView(iOS 9 已废弃),这里用 UIAlertController 重写:
- (void)showExitConfirmation {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示"
message:@"确定要退出应用吗?"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消"
style:UIAlertActionStyleCancel
handler:nil];
UIAlertAction *exitAction = [UIAlertAction actionWithTitle:@"退出"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction * _Nonnull action) {
[self exitApplicationWithAnimation];
}];
[alert addAction:cancelAction];
[alert addAction:exitAction];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)exitApplicationWithAnimation {
// 退出前保存数据
[self saveDataBeforeExit];
UIWindow *window = [UIApplication sharedApplication].keyWindow;
// 淡出动画
[UIView animateWithDuration:0.5 animations:^{
window.alpha = 0;
window.transform = CGAffineTransformMakeScale(0.9, 0.9);
} completion:^(BOOL finished) {
exit(0); // 动画结束后退出
}];
}
即使加了动画,
exit(0)仍然是不推荐的做法,仅在极端场景使用。
五、异常捕获与崩溃处理
和"退出"紧密相关的是异常捕获和崩溃处理。理解崩溃的原理,才能更好地理解各种退出方式。
1. @try-@catch-@finally
Objective-C 提供了异常处理语法:
@try {
// 可能抛出异常的代码
NSArray *array = @[];
NSString *obj = array[0]; // 数组越界,会抛出 NSRangeException
} @catch (NSException *exception) {
// 捕获异常
NSLog(@"捕获异常: %@, %@", exception.name, exception.reason);
} @finally {
// 无论是否捕获异常,都会执行
NSLog(@"finally 总是执行");
}
注意事项:
@try-@catch只能捕获 Objective-C 异常(NSException 及其子类)。- 不能捕获 C++ 异常、信号(SIGSEGV 等)、mach 异常。
- 即使捕获了异常,应用的状态可能已经损坏,不建议在捕获后继续运行,应该记录日志后退出或重置状态。
- ARC 下
@try-@catch可能有内存管理问题,谨慎使用。
2. NSSetUncaughtExceptionHandler(捕获未处理的 OC 异常)
注册一个全局函数,捕获所有未被 @catch 处理的 OC 异常:
// AppDelegate.m
#import <UIKit/UIKit.h>
void UncaughtExceptionHandler(NSException *exception) {
NSLog(@"捕获到未处理异常: %@", exception.name);
NSLog(@"异常原因: %@", exception.reason);
NSLog(@"调用栈: %@", [exception callStackSymbols]);
// 保存崩溃日志到本地
NSDictionary *info = @{
@"name": exception.name ?: @"",
@"reason": exception.reason ?: @"",
@"callStack": [exception callStackSymbols] ?: @[],
@"time": [[NSDate date] description]
};
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"crash.log"];
[info writeToFile:path atomically:YES];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 注册异常捕获
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
return YES;
}
3. 信号捕获(捕获 Signal 崩溃)
NSSetUncaughtExceptionHandler 只能捕获 OC 异常,不能捕获信号类崩溃(如野指针 SIGSEGV、abort() 的 SIGABRT)。需要用 signal() 函数注册信号处理:
#import <signal.h>
void SignalHandler(int signal) {
NSLog(@"捕获到信号: %d", signal);
// 保存崩溃信息
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"signal_crash.log"];
NSString *info = [NSString stringWithFormat:@"Signal: %d\nTime: %@\n", signal, [NSDate date]];
[info writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
// 注意:信号处理函数中不能调用大多数系统 API(不安全)
// 应该尽量简单,保存信息后让程序继续崩溃(生成系统崩溃日志)
}
- (void)registerSignalHandler {
// 注册常见崩溃信号
signal(SIGABRT, SignalHandler); // abort()
signal(SIGSEGV, SignalHandler); // 段错误(野指针)
signal(SIGBUS, SignalHandler); // 总线错误
signal(SIGILL, SignalHandler); // 非法指令
signal(SIGTRAP, SignalHandler); // 断点/trap
signal(SIGFPE, SignalHandler); // 浮点错误
}
注意:信号处理函数中能做的事情非常有限(异步信号安全限制),不能调用 Objective-C 方法、不能分配内存、不能加锁。实际项目中推荐使用成熟的崩溃收集 SDK(Bugly、Firebase Crashlytics、Sentry 等),它们已经处理好了这些底层细节。
4. 异常捕获的完整方案
一个完整的崩溃捕获方案需要同时注册:
NSSetUncaughtExceptionHandler:捕获 OC 异常。signal():捕获信号类崩溃。- (可选)
NSSetUncaughtExceptionHandler+ 信号的组合。
但即使这样,也不能 100% 捕获所有崩溃(如栈溢出、内核级 panic、OOM 杀进程等)。生产环境推荐使用专业的崩溃收集 SDK。
六、App 切换到后台与挂起
很多人混淆了"退出应用"和"切换到后台"。理解应用的生命周期很重要。
1. 应用的五种状态
| 状态 | 说明 |
|---|---|
| Not Running | 应用未运行,或被系统终止 |
| Inactive | 应用在前台但不接收事件(如来电、弹出通知时短暂进入) |
| Active | 应用在前台并正常运行,接收用户事件 |
| Background | 应用在后台执行代码(有时间限制) |
| Suspended | 应用在后台被挂起,不执行代码,驻留在内存中 |
2. 用户按 Home 键/上滑回桌面
用户按 Home 键(或全面屏上滑)时:
- 应用从
Active→Inactive→Background。 - 调用
applicationDidEnterBackground:。 - 应用有大约 5 秒(可申请延长到 30 秒~3 分钟)的后台执行时间。
- 时间到后,应用从
Background→Suspended(挂起),代码停止执行,但仍在内存中。 - 当用户再次打开应用时,从
Suspended→Background→Inactive→Active,恢复运行。
挂起不是退出。挂起的应用仍然在内存中,用户再次打开时可以秒恢复。只有当系统内存不足时,才会杀死挂起的应用(他杀)。
3. 后台执行时间
- (void)applicationDidEnterBackground:(UIApplication *)application {
// 申请后台执行时间
UIBackgroundTaskIdentifier taskID = [application beginBackgroundTaskWithExpirationHandler:^{
// 时间快到时调用,在这里清理
[application endBackgroundTask:taskID];
}];
// 在后台执行任务(如下载数据、保存数据)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self saveImportantData];
[application endBackgroundTask:taskID]; // 任务完成,结束后台任务
});
}
4. 内存警告与杀进程
当系统内存不足时:
- 先给所有应用发送
didReceiveMemoryWarning(应用在前台和后台都会收到)。 - 如果内存仍然不足,系统开始杀死挂起的后台应用(从最久未使用的开始),不会通知应用,直接终止。
- 如果前台应用内存占用过大,系统也可能直接杀死前台应用(OOM,Out Of Memory),这种情况不会调用
applicationWillTerminate:,也不会生成崩溃日志(系统认为是正常的内存管理)。
OOM 崩溃是最难排查的崩溃类型之一,因为没有崩溃日志,只能通过内存监控和分析工具推断。
七、常见问题与注意事项
问题 1:强制退出应用会被 App Store 审核拒绝吗?
答案:没有明确的条款禁止 exit(),但如果应用内有明显的"退出应用"按钮,或者应用在正常使用流程中自行退出,审核团队可能以"用户体验不佳"或"不符合 iOS 设计规范"为由拒绝。
建议:
- 不要在应用内提供"退出应用"按钮。
- 用"退出登录"代替"退出应用"。
- 极端场景需要退出时,先提示用户,由用户确认。
问题 2:exit() 和 abort() 哪个更"安全"?
答案:都不推荐在生产环境使用。如果必须用,exit(0) 比 abort() 好,因为:
exit(0)是正常退出,不会生成崩溃日志,不会被崩溃收集 SDK 上报。abort()是异常终止,会生成崩溃日志,崩溃率会飙升,影响 App Store 排名和用户信任。
但即使是
exit(0),也会让用户感觉应用闪退了,体验很差。
问题 3:调用 exit() 后 applicationWillTerminate: 会调用吗?
答案:不会。applicationWillTerminate: 只有在用户正常关闭应用(上滑关闭)时才会调用。exit() 是进程级别的直接终止,不走 UIKit 的退出流程。
建议:在调用 exit() 之前,手动保存所有需要持久化的数据。
问题 4:如何区分"用户主动关闭"和"崩溃"?
| 特征 | 用户主动关闭 | 崩溃 |
|---|---|---|
| 生成崩溃日志 | ❌ 不会 | ✅ 会 |
| 调用 applicationWillTerminate | ✅ 会 | ❌ 不会 |
| 系统退出原因 | UIApplicationExitReasonUserInitiated | 各种异常原因 |
| 崩溃收集 SDK 上报 | ❌ 不会 | ✅ 会 |
iOS 14+ 可以在 applicationWillTerminate: 中通过 UIApplication.terminationReason 判断退出原因:
UIApplicationExitReasonUserInitiated:用户主动关闭UIApplicationExitReasonSecurity:安全原因UIApplicationExitReasonBackgroundTaskTimeout:后台任务超时- 等等
问题 5:应用启动时可以用 exit() 做"安全退出"吗?
有些应用在启动时检测到越狱、破解、设备不安全等情况,会调用 exit() 退出。这种做法:
- 技术上可行。
- 但用户体验差,用户会以为应用闪退了。
- 更好的做法是:弹出一个提示页面,告知用户风险,提供"仍然进入"或"退出"的选项,由用户决定。
问题 6:assert 在 Release 模式下会执行吗?
答案:不会。assert() 宏在 Release 模式下(定义了 NDEBUG)会被编译为空,不执行任何检查。所以:
assert()只在 DEBUG 模式下有效,用于开发调试。- 不要在
assert()中写有副作用的代码(如assert([self doSomething])),因为 Release 模式下doSomething不会执行。 - Release 模式下的条件检查应该用普通的
if语句。
问题 7:退出应用后,单例和静态变量会销毁吗?
答案:进程退出后,所有内存都会被操作系统回收,包括单例、静态变量、堆内存、栈内存等。不需要在退出前手动释放单例或静态变量,进程退出后一切都会被清理。
但需要注意的是:
exit()会调用atexit注册的函数,可以在这些函数中做清理。abort()不会调用atexit,但进程退出后内存仍会被回收。- 不需要担心"内存泄漏"在进程退出后仍然存在——进程没了,内存就全还给系统了。
八、Swift 版本对照
1. 退出应用
// exit()
exit(0)
// abort()
abort()
// fatalError(Swift 推荐的严重错误终止方式,内部调用 abort)
fatalError("严重错误,应用终止")
// assert(DEBUG 模式)
assert(condition, "条件不成立")
// precondition(Release 模式也会检查,条件不成立时 fatalError)
precondition(condition, "条件不成立")
Swift 中推荐用
fatalError()代替abort(),用precondition()代替assert()(如果需要 Release 也检查)。
2. 异常捕获
Swift 不支持 @try-@catch 捕获 NSException(Swift 的 Error 机制和 OC 的 NSException 是两套体系)。如果需要在 Swift 中捕获 OC 异常,需要用 Objective-C 包装:
// Swift 中无法直接捕获 NSException,需要 OC 包装
// 推荐用 Swift 的 do-try-catch 处理 Swift Error
do {
try somethingThatThrows()
} catch {
print("错误: \(error)")
}
3. 退出登录(切换根控制器)
func logout() {
// 清除数据
UserDefaults.standard.removeObject(forKey: "token")
// 切换根控制器
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
let navVC = UINavigationController(rootViewController: loginVC)
UIApplication.shared.keyWindow?.rootViewController = navVC
UIApplication.shared.keyWindow?.makeKeyAndVisible()
}
4. 带动画退出
func exitAppWithAnimation() {
guard let window = UIApplication.shared.keyWindow else { return }
UIView.animate(withDuration: 0.5, animations: {
window.alpha = 0
window.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
}) { _ in
exit(0)
}
}
九、总结
- iOS 没有官方退出 API:这是设计理念决定的,应用生命周期由用户控制,不应自行退出。
- 进程退出三种方式:
- 自然死亡:main() return,iOS 应用几乎不会发生。
- 自杀:exit()(正常退出,调用 atexit)、abort()(异常终止,生成崩溃日志)、assert()(DEBUG 断言,内部调用 abort)、_exit()(直接系统调用)、信号方式。
- 他杀:用户上滑关闭、内存警告杀进程、后台超时、看门狗超时、系统重启。
- exit() vs abort() 核心区别:exit() 是正常退出,不生成崩溃日志;abort() 是异常终止,会生成崩溃日志并被崩溃 SDK 上报,生产环境绝对不能用 abort()。
- 实际开发中的"退出":99% 的场景是"退出登录"(清除用户数据 + 切换根控制器到登录页),不是退出应用。
- 极端场景强制退出:仅在数据损坏、安全风险等极端场景考虑,且必须先提示用户,退出前保存数据,加淡出动画。
- 异常捕获:NSSetUncaughtExceptionHandler 捕获 OC 异常,signal() 捕获信号崩溃,两者结合才能捕获大部分崩溃。生产环境推荐用专业 SDK(Bugly、Crashlytics)。
- 后台挂起 ≠ 退出:应用按 Home 键后进入后台,然后被挂起(Suspended),代码停止执行但仍在内存中,用户再次打开时秒恢复。只有系统内存不足时才会杀死挂起的应用。
- 核心建议:不要在生产应用中调用 exit()/abort() 强制退出。用退出登录代替退出应用,引导用户通过系统方式关闭应用。

浙公网安备 33010602011771号