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() 的执行流程

  1. 调用所有通过 atexit() 注册的函数(按注册的逆序调用)。
  2. 刷新所有打开的文件流缓冲区(fflush)。
  3. 关闭所有打开的文件流。
  4. 删除 tmpfile() 创建的临时文件。
  5. 调用 _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() 的执行流程

  1. 解除对 SIGABRT 信号的阻塞(如果之前被阻塞)。
  2. 调用 raise(SIGABRT) 给自己发送 SIGABRT 信号。
  3. SIGABRT 的默认处理动作是终止进程并生成核心转储(core dump)。
  4. 进程终止,返回状态码 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. 什么情况下可以考虑强制退出应用

虽然不推荐,但在某些极端场景下,可能需要强制退出:

场景 说明 建议
严重数据损坏 应用核心数据损坏,无法继续运行 提示用户后退出,引导重装
安全风险 检测到应用被破解/注入,存在安全风险 提示后退出
企业内部应用 企业分发的应用,有强制退出需求 可以使用,但需提示用户
越狱检测 金融类应用检测到越狱环境 提示后限制功能或退出

即使用这些场景,也应该:

  1. 先弹出提示框,告知用户原因。
  2. 用户确认后再退出。
  3. 退出前保存必要数据。
  4. 加淡出动画,避免突兀。

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. 异常捕获的完整方案

一个完整的崩溃捕获方案需要同时注册:

  1. NSSetUncaughtExceptionHandler:捕获 OC 异常。
  2. signal():捕获信号类崩溃。
  3. (可选)NSSetUncaughtExceptionHandler + 信号的组合。

但即使这样,也不能 100% 捕获所有崩溃(如栈溢出、内核级 panic、OOM 杀进程等)。生产环境推荐使用专业的崩溃收集 SDK。


六、App 切换到后台与挂起

很多人混淆了"退出应用"和"切换到后台"。理解应用的生命周期很重要。

1. 应用的五种状态

状态 说明
Not Running 应用未运行,或被系统终止
Inactive 应用在前台但不接收事件(如来电、弹出通知时短暂进入)
Active 应用在前台并正常运行,接收用户事件
Background 应用在后台执行代码(有时间限制)
Suspended 应用在后台被挂起,不执行代码,驻留在内存中

2. 用户按 Home 键/上滑回桌面

用户按 Home 键(或全面屏上滑)时:

  1. 应用从 ActiveInactiveBackground
  2. 调用 applicationDidEnterBackground:
  3. 应用有大约 5 秒(可申请延长到 30 秒~3 分钟)的后台执行时间。
  4. 时间到后,应用从 BackgroundSuspended(挂起),代码停止执行,但仍在内存中。
  5. 当用户再次打开应用时,从 SuspendedBackgroundInactiveActive,恢复运行。

挂起不是退出。挂起的应用仍然在内存中,用户再次打开时可以秒恢复。只有当系统内存不足时,才会杀死挂起的应用(他杀)。

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. 内存警告与杀进程

当系统内存不足时:

  1. 先给所有应用发送 didReceiveMemoryWarning(应用在前台和后台都会收到)。
  2. 如果内存仍然不足,系统开始杀死挂起的后台应用(从最久未使用的开始),不会通知应用,直接终止。
  3. 如果前台应用内存占用过大,系统也可能直接杀死前台应用(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() 强制退出。用退出登录代替退出应用,引导用户通过系统方式关闭应用。

posted @ 2016-10-20 10:18  Mr.陳  阅读(20197)  评论(0)    收藏  举报