iOS开发基础23-核心概念:Info.plist、UIApplication、UIWindow 与启动流程

iOS 核心概念:Info.plist、UIApplication、UIWindow 与启动流程

本文系统梳理 iOS 开发中四个基础核心概念:Info.plist 配置、UIApplication 单例、UIApplicationDelegate 生命周期、UIWindow 层级,以及完整的 App 启动流程,并补充 iOS 13+ Scene 架构的现代变化。


一、Info.plist 常见设置

1. 什么是 Info.plist

每个 iOS 项目都有一个名为 "{工程名}-Info.plist" 的配置文件(Information Property List),用于运行时配置应用的基本信息,如 Bundle ID、版本号、图标、权限声明等。该文件是 App 签名和系统识别的重要依据,不能删除。

  • 旧版 Xcode 中文件名为 Info.plist
  • InfoPlist.strings 用于 Info.plist 的本地化(如应用名称的多语言)。
  • 自定义 plist 文件应避免命名为 Info.plist,以免与主配置混淆。

2. 常见配置键

配置项 Key 说明
Bundle ID CFBundleIdentifier 应用唯一标识,上架和推送必需
版本号(用户可见) CFBundleShortVersionString 1.2.0,App Store 显示
构建号 CFBundleVersion 15,每次提交递增
应用显示名 CFBundleDisplayName 桌面图标下方名称,建议 ≤ 12 字符
主 Storyboard NSMainStoryboardFile 主界面 storyboard 文件名
启动图 Storyboard UILaunchStoryboardName 启动屏 storyboard(iOS 8+ 推荐)
本地化区域 CFBundleDevelopmentRegion 默认语言区域,如 zh_CN
支持方向 UISupportedInterfaceOrientations 支持的屏幕方向
网络安全 NSAppTransportSecurity HTTP 白名单配置(ATS)
状态栏管理方式 UIViewControllerBasedStatusBarAppearance YES 由 VC 管理,NO 由 UIApplication 管理
URL Scheme 白名单 LSApplicationQueriesSchemes iOS 9+ 跳转其他 App 前需声明
后台模式 UIBackgroundModes 音频、定位、蓝牙等后台运行权限

CFBundleIconFile 是旧版图标设置方式,现代 iOS 使用 Asset Catalog(Assets.xcassets 中的 AppIcon)配合 CFBundleIcons 键,不推荐再使用 CFBundleIconFile

3. PCH 文件

PCH(Precompiled Header)是预编译头文件,在编译时自动引入到每个 .m 文件中,用于存放公用宏和头文件。

#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

// Debug 下输出 Log,Release 下不输出
#ifdef DEBUG
#define XMGLog(...) NSLog(__VA_ARGS__)
#else
#define XMGLog(...)
#endif

// iOS 版本判断(推荐用 @available 或 NSFoundationVersionNumber,浮点数比较有精度风险)
#define iOS8Later ([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0)
#endif

现代使用说明

  • Xcode 6 及之后默认不再自动创建 PCH 文件,需手动添加 .pch 文件并在 Build Settings → Prefix Header 中指定路径。
  • PCH 会被每个源文件引入,内容过多会增加编译时间,现代开发推荐使用 @import 模块导入或在具体文件中导入所需头文件,仅将真正全局通用的宏放入 PCH。
  • iOS 版本判断推荐使用 @available(iOS 8.0, *)(编译期检查)或 NSFoundationVersionNumber,避免浮点数比较的精度问题。

二、UIApplication

1. 什么是 UIApplication

UIApplication 是应用程序的核心单例对象,每个 App 有且仅有一个实例,通过 [UIApplication sharedApplication] 获取。它负责应用级别的操作,如打开 URL、管理状态栏、处理远程通知、协调应用生命周期事件等。

2. 常用属性和方法

应用图标角标

// 设置角标数字(iOS 16+ 需先请求通知权限)
[UIApplication sharedApplication].applicationIconBadgeNumber = 10;

网络活动指示器

// 显示状态栏网络活动小菊花
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

networkActivityIndicatorVisible 在 iOS 13 后已废弃,刘海屏设备不再显示该指示器。

状态栏管理

iOS 7 起状态栏有两种管理方式:

方式一:由 UIViewController 管理(默认,推荐)

- (UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent; // 白色文字
}

- (BOOL)prefersStatusBarHidden {
    return NO;
}

方式二:由 UIApplication 管理

需在 Info.plist 中设置 UIViewControllerBasedStatusBarAppearance = NO

[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
[UIApplication sharedApplication].statusBarHidden = NO;

iOS 13+ 中 UIApplicationstatusBarStylestatusBarHidden 已废弃,推荐使用 UIStatusBarManager(通过 UIWindowScene 获取)或继续使用 ViewController 方式。

打开 URL(打电话、发短信、网页等)

UIApplication *app = [UIApplication sharedApplication];

// 打电话(标准格式为 tel:,不是 tel://)
[app openURL:[NSURL URLWithString:@"tel:10086"]];

// 发短信
[app openURL:[NSURL URLWithString:@"sms:10086"]];

// 发邮件
[app openURL:[NSURL URLWithString:@"mailto:12345@qq.com"]];

// 打开网页
[app openURL:[NSURL URLWithString:@"https://www.baidu.com"]];

iOS 10+openURL: 已废弃,推荐使用带完成回调的新 API:

[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:^(BOOL success) {
    NSLog(@"打开成功: %d", success);
}];

iOS 9+:通过 canOpenURL: 检测能否打开某个 App 前,需在 Info.plist 的 LSApplicationQueriesSchemes 数组中声明目标 URL Scheme,否则返回 NO。

3. 单例模式

UIApplication 是标准单例,整个 App 生命周期内只有一个实例。以下是常见的单例实现方式:

// 推荐写法:dispatch_once 保证线程安全且只执行一次
+ (instancetype)sharedManager {
    static Manager *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

实际开发中通常不禁止 alloc(允许创建其他实例用于测试等场景),只提供 sharedInstance 作为默认共享实例。如果需要严格单例(禁止外部创建),可重写 allocWithZone: 并抛出异常,但这种做法在现代开发中较少使用。


三、UIApplicationDelegate

1. 作用

UIApplicationDelegate 协议定义了应用生命周期和系统事件的回调方法。UIApplication 单例在发生应用状态变化(启动、进入后台、内存警告等)时,通知 delegate 对象处理。

2. 核心生命周期方法

#pragma mark - 应用启动

// 应用启动完成,最常用的初始化入口
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    return YES;
}

#pragma mark - 应用状态切换

// 应用即将从活跃状态变为非活跃(来电、下拉通知中心等)
- (void)applicationWillResignActive:(UIApplication *)application {}

// 应用进入后台(按 Home 键)
- (void)applicationDidEnterBackground:(UIApplication *)application {}

// 应用即将从后台进入前台
- (void)applicationWillEnterForeground:(UIApplication *)application {}

// 应用已进入活跃状态(可交互)
- (void)applicationDidBecomeActive:(UIApplication *)application {}

// 应用即将终止(非总是调用,如系统直接 kill 时不调用)
- (void)applicationWillTerminate:(UIApplication *)application {}

#pragma mark - 系统事件

// 收到内存警告,应释放可重建的资源
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {}

// 远程通知注册成功,获取 deviceToken
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {}

// 打开 URL(如从其他 App 跳转回来)
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    return YES;
}

3. AppDelegate 类

每个项目默认生成的 AppDelegate 类就是 UIApplication 的 delegate,遵守 UIApplicationDelegate 协议:

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 初始化窗口和根视图控制器
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.rootViewController = [[ViewController alloc] init];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

4. iOS 13+ SceneDelegate 架构变化

iOS 13 引入 UIScene(场景)概念,支持多窗口和多场景。App 的 UI 生命周期管理从 AppDelegate 拆分到 SceneDelegate

职责 iOS 12 及之前 iOS 13+
App 生命周期 AppDelegate AppDelegate(仍负责 App 级别)
UI 生命周期 / UIWindow AppDelegate SceneDelegate
窗口创建 application:didFinishLaunchingWithOptions: scene:willConnectToSession:options:
前后台切换 AppDelegate 方法 SceneDelegate 方法(sceneDidBecomeActive: 等)
// SceneDelegate.h
#import <UIKit/UIKit.h>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

// SceneDelegate.m
@implementation SceneDelegate

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    // 创建窗口(必须用 windowScene 初始化)
    UIWindowScene *windowScene = (UIWindowScene *)scene;
    self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
    self.window.rootViewController = [[ViewController alloc] init];
    [self.window makeKeyAndVisible];
}

- (void)sceneDidDisconnect:(UIScene *)scene {}
- (void)sceneDidBecomeActive:(UIScene *)scene {}
- (void)sceneWillResignActive:(UIScene *)scene {}
- (void)sceneWillEnterForeground:(UIScene *)scene {}
- (void)sceneDidEnterBackground:(UIScene *)scene {}

@end

Info.plist 中需配置 UIApplicationSceneManifest 声明 SceneDelegate,否则仍使用旧的 AppDelegate 窗口管理方式。


四、UIWindow

1. 什么是 UIWindow

UIWindowUIView 的子类,是 App 内容的顶层容器。一个 App 通常至少有一个 UIWindow,所有可见的视图都最终挂载在 window 上。iOS 13+ 多场景下,每个 UIWindowScene 对应一个或多个 window。

2. 添加内容到 UIWindow

方式一:设置 rootViewController(推荐)

window.rootViewController = yourViewController;
// rootViewController 的 view 会自动添加到 window,并由 VC 管理生命周期

方式二:直接 addSubview(不推荐)

[window addSubview:yourView];
// 直接添加 view 不会管理对应的 UIViewController 生命周期,不推荐用于主界面

3. 常用方法

// 成为 keyWindow(主窗口,接收键盘和非触摸事件)
[window makeKeyWindow];

// 成为 keyWindow 并显示(等价于 makeKeyWindow + hidden = NO)
[window makeKeyAndVisible];

iOS 15+ 中 makeKeyAndVisible 被标记为不推荐但仍可用,推荐先设置 windowScene 再调用 makeKeyWindow

4. 获取 UIWindow

// 获取所有 window(iOS 15+ 中 windows 属性被标记为不推荐)
NSArray *windows = [UIApplication sharedApplication].windows;

// 获取 keyWindow(iOS 13+ 中 keyWindow 属性已废弃)
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;

// 获取某个 view 所在的 window
UIWindow *viewWindow = yourView.window;

iOS 13+ 推荐获取 keyWindow 的方式

UIWindow *keyWindow = nil;
for (UIWindowScene *scene in [UIApplication sharedApplication].connectedScenes) {
    if (scene.activationState == UISceneActivationStateForegroundActive) {
        for (UIWindow *window in scene.windows) {
            if (window.isKeyWindow) {
                keyWindow = window;
                break;
            }
        }
        if (keyWindow) break;
    }
}

五、iOS 程序启动过程

1. main 函数与 UIApplicationMain

程序入口是 main.m 中的 main 函数,调用 UIApplicationMain

int main(int argc, char *argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

UIApplicationMain 函数签名:

int UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
参数 说明
argc / argv 命令行参数,透传即可
principalClassName UIApplication 类名(或子类),nil 默认为 UIApplication
delegateClassName AppDelegate 类名,必须遵守 UIApplicationDelegate

2. 完整启动流程

1. main() 函数执行
       ↓
2. 调用 UIApplicationMain()
       ↓
3. 创建 UIApplication 单例对象
       ↓
4. 创建并设置 UIApplication 的 delegate(AppDelegate)
       ↓
5. 启动主 RunLoop,开始事件循环
       ↓
6. 调用 delegate 的 application:didFinishLaunchingWithOptions:
       ↓
7. 创建 UIWindow(iOS 13+ 在 SceneDelegate 的 scene:willConnectToSession: 中创建)
       ↓
8. 设置 window.rootViewController
       ↓
9. [window makeKeyAndVisible],界面可见
       ↓
10. 调用 applicationDidBecomeActive:(或 sceneDidBecomeActive:),App 进入活跃状态

六、四大对象关系

对象 角色 关键职责
UIApplication 应用单例 应用级别操作、协调生命周期、打开 URL、管理状态栏
UIApplicationDelegate 应用代理 处理 App 生命周期事件、系统事件、远程通知、URL 打开
UIWindow 顶层视图容器 承载所有可见视图、管理 keyWindow、分发触摸事件
UIViewController 视图控制器 管理 view 层级、处理用户交互、控制视图生命周期

关系描述

  • UIApplication 单例持有 delegate(AppDelegate),在应用状态变化时通知 delegate。
  • App 启动时创建 UIWindow,设置 rootViewController,将 VC 的 view 添加到 window 上。
  • UIWindow 继承自 UIView,是视图层级的根,负责将触摸事件分发给子视图和视图控制器。
  • iOS 13+ 中,UIWindow 归属于 UIWindowScene,SceneDelegate 管理场景级别的 UI 生命周期。

七、Swift 版本对照

AppDelegate(iOS 13+)

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return true
    }

    func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
        // 释放可重建资源
    }
}

SceneDelegate(iOS 13+)

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = ViewController()
        window.makeKeyAndVisible()
        self.window = window
    }

    func sceneDidBecomeActive(_ scene: UIScene) {}
    func sceneWillResignActive(_ scene: UIScene) {}
    func sceneWillEnterForeground(_ scene: UIScene) {}
    func sceneDidEnterBackground(_ scene: UIScene) {}
}

UIApplication 常用操作

// 角标
UIApplication.shared.applicationIconBadgeNumber = 10

// 打开 URL
if let url = URL(string: "tel:10086") {
    UIApplication.shared.open(url, options: [:]) { success in
        print("打开: \(success)")
    }
}

// iOS 13+ 获取 keyWindow
let keyWindow = UIApplication.shared.connectedScenes
    .compactMap { $0 as? UIWindowScene }
    .flatMap { $0.windows }
    .first { $0.isKeyWindow }

单例

final class Manager {
    static let shared = Manager() // 线程安全,懒加载
    private init() {} // 禁止外部创建
}

八、总结

  • Info.plist 是 App 的运行时配置核心,包含 Bundle ID、版本号、权限声明等,不能删除。常用键需熟悉,尤其是 CFBundleIdentifierCFBundleShortVersionStringCFBundleVersion
  • UIApplication 是应用单例,负责应用级别操作。openURL: 在 iOS 10 后被 openURL:options:completionHandler: 替代;状态栏和网络指示器在 iOS 13+ 有废弃变化。
  • UIApplicationDelegate 处理应用生命周期事件。iOS 13+ 引入 SceneDelegate,UI 窗口管理从 AppDelegate 拆分到 SceneDelegate。
  • UIWindow 是顶层视图容器,推荐通过 rootViewController 设置内容。iOS 13+ window 归属于 UIWindowScenekeyWindowwindows 属性在新版本中有废弃。
  • 启动流程main → UIApplicationMain → 创建 UIApplication → 设置 delegate → 启动 RunLoop → didFinishLaunching → 创建 UIWindow → 设置 rootViewController → makeKeyAndVisible
  • PCH 文件现代开发中需谨慎使用,避免增加编译时间,推荐模块导入。

posted @ 2015-07-27 23:57  Mr.陳  阅读(545)  评论(0)    收藏  举报