【跨平台】IOS端应用开发扫盲

本文从初学者角度出发,介绍IOS端应用开发的一些基础知识。
最新在KMP的开发过程中,对于一些IOS端的全局性的UI样式代码修改,有些无从下手。Swift语言层面上还比较容易看懂,但是对于系统规则机制,app运行机制等了解尚浅,对此做一个基础的总结。
开发语言层面对比
1. 语言特性对比
| 特性 | Swift | Java | 异同点与优势 |
|---|---|---|---|
| 类型系统 | 强类型,类型推断 | 强类型,类型推断有限 | Swift 的类型推断更强大,很多时候无需显式声明变量类型,代码更简洁。 |
| 内存管理 | 自动引用计数 (ARC) | 垃圾回收 (GC) | 这是两者最核心的区别。Swift 的 ARC 性能更高,但需要注意循环引用;Java 的 GC 开发者更省心,但可能带来运行时卡顿。 |
| 可选类型 | Optional 类型 | Null | Swift 强制处理 nil,开发者必须显式地用 if let 或 guard let 解包,从而从语言层面杜绝了空指针异常。Java 的 NullPointerException 是一个常见痛点。 |
| 编程范式 | 面向对象、函数式 | 纯面向对象 | Swift 融合了面向对象、函数式和协议导向编程(Protocol-Oriented Programming, POP)思想,代码更灵活,尤其在泛型和协议方面。 |
| 结构体 | 支持结构体 (Struct) | 不支持,只有类 | Swift 的结构体是值类型,类是引用类型。这提供了更多的灵活性和性能优化空间,例如在处理轻量级数据时使用结构体可以避免不必要的内存分配和引用计数开销。 |
| 函数式 | 支持高阶函数、闭包 | 支持 lambda 表达式 | Swift 的闭包(Closures)功能强大且易用,是其函数式编程特性的重要体现。 |
| 多线程 | GCD、Operation Queue | Thread, Executor, Coroutines | 两者都提供了完善的多线程解决方案,但具体实现方式不同。Swift 的 GCD (Grand Central Dispatch) 是一个非常强大的基于任务队列的并发模型。 |
2. 运行环境对比
| 环境 | Swift | Java | 异同点与优势 |
|---|---|---|---|
| 运行时 | 原生 (Native) 运行时 | 虚拟机 (JVM / ART) | Swift 代码直接编译成机器码在 CPU 上执行,没有虚拟机的性能开销,启动更快,执行效率更高。 |
| 编译过程 | LLVM 编译器 | Java 编译器(Javac) | Swift 的 LLVM 编译器非常先进,能够生成高度优化的机器码。 |
| 跨平台 | 主要用于 Apple 生态 | 跨平台能力强大 | Swift 主要用于 iOS、macOS、watchOS 等苹果平台,虽然有开源项目尝试跨平台,但生态和工具链远不如 Java。Java 的 JVM 可以运行在 Windows、Linux、Android 等多个操作系统上,“一次编写,到处运行”。 |
| 语言版本 | 频繁更新 | 稳定,但更新较慢 | Swift 语言发展迅速,版本更新频繁,新特性不断加入。Java 语言相对稳定,版本更新周期较长。 |
总的来说,Swift 直接编译成机器码,没有虚拟机开销,运行速度快。安全可靠,可选类型从语言层面消除了空指针异常,类型推断减少了编程错误。融合了多种编程范式,如函数式、协议导向等,语法简洁、富有表现力。
而 Java 得益于 JVM,Java 具有无可比拟的跨平台能力。其拥有庞大而成熟的社区和工具生态,有无数的框架和库可供选择。语言版本和 API 相对稳定,适合大型企业级应用开发。
代码编写到运行经历了哪些流程
1. 代码编写与编译阶段
在这个阶段,开发者用 Swift 或 Objective-C 语言编写代码。使用 Xcode 这个集成开发环境 (IDE),它包含了所有的工具链,如编译器、调试器等。
- 编译器: Xcode 默认使用 LLVM (Low Level Virtual Machine) 编译器。
- Swift 源码通过 Swift 编译器编译成 LLVM Intermediate Representation (IR),然后再编译成机器码。
- Objective-C 源码则直接通过 Clang 编译器编译成机器码。
- 编译优化: 编译器会对代码进行各种优化,例如 dead code elimination(移除无用代码)、常量折叠等,以提高应用的运行效率。
- 产物: 编译的最终产物是可执行的机器码文件(Mach-O 文件),以及应用所需的其他资源文件(如图片、UI 布局文件等)。
2. 应用打包阶段
编译完成后,Xcode 会将所有必需的文件打包成一个可分发、可安装的格式。
- Bundle 概念: iOS 应用的核心是一个 Bundle。它是一个特殊的文件夹,其目录结构是固定的。Bundle 内部包含了可执行文件、所有的资源文件(图片、声音、NIB/Storyboards 等)、以及一个重要的
Info.plist文件。 Info.plist文件: 这是一个属性列表文件,包含了应用的元数据,例如应用名称、版本号、支持的设备方向、所需的权限(如相机、位置服务)等等。操作系统和应用本身都会读取这个文件来获取关键信息。- IPA 文件: 最终,整个 Bundle 会被压缩成一个 .ipa 文件。
.ipa文件本质上是一个 ZIP 压缩包,.ipa的作用就类似于 Android 的.apk文件。
3. 应用安装阶段
用户从 App Store 下载或通过其他方式获取到 .ipa 文件后,系统会进行安装。
- 解压与签名验证: 系统首先解压
.ipa文件,然后进行严格的数字签名验证。每一个在 App Store 上发布的 iOS 应用都必须由 Apple 签发证书进行签名。- 目的: 签名验证的目的是确保应用没有被篡改,且来自可信的开发者。这是 iOS 安全机制的重要一环。
- 权限配置: 系统会根据
Info.plist文件中声明的权限,为应用配置对应的沙盒环境。这决定了应用可以访问哪些系统资源和数据。 - 目录结构: 应用的 Bundle 会被安装到
/private/var/containers/Bundle/Application目录下。同时,系统还会为应用创建数据目录,包括 Documents、Library 和 tmp,这些目录位于/private/var/mobile/Containers/Data/Application下,用于应用存储数据。
4. 应用运行阶段
当用户点击应用图标时,应用开始启动和运行。
main()函数: 所有 iOS 应用都从一个main()函数开始执行,这与 C/C++ 程序的入口点相同。UIApplication:main()函数会调用UIApplicationMain函数来创建一个UIApplication对象。UIApplication是 iOS 应用的单例,负责管理应用的生命周期、事件循环和与系统之间的交互。AppDelegate:UIApplication会将应用的生命周期事件(如应用启动、进入后台、收到内存警告等)通知给AppDelegate对象。AppDelegate是应用的代理,开发者可以在其中实现相应的回调方法,来处理这些系统事件。- 主线程: UI 更新、事件处理等所有与界面相关的操作都必须在 主线程 上执行。这与 Android 上的 UI 线程(Main thread)是相同的概念,都是为了避免并发问题,保证用户界面的流畅性。
- 沙盒机制: 应用在运行过程中,其读写操作都严格限制在其沙盒目录内,无法访问沙盒外的其他应用数据,从而保证了系统安全和数据隔离。
APP运行环境
Android 应用运行在 ART(Android Runtime) 虚拟机上,这是一个基于 JIT(Just-In-Time)和 AOT(Ahead-Of-Time)编译的运行时环境,负责执行 Java/Kotlin 代码。它提供了垃圾回收、内存管理和沙箱隔离,确保每个应用都在一个独立、受保护的环境中运行。
iOS 应用则直接运行在 原生(Native) 环境下,执行由 Objective-C 或 Swift 编写的代码。这些代码直接编译成机器码,由苹果的 Cocoa Touch 框架和 XNU 内核直接执行。
- 没有虚拟机:iOS 不使用虚拟机,这使得其应用的启动速度和执行效率通常更高。
- 内存管理:iOS 主要通过 ARC(Automatic Reference Counting) 机制来自动管理内存。当一个对象的引用计数变为零时,系统会自动回收它。这与 ART 的垃圾回收机制(Garbage Collection)不同,但目标都是为了简化内存管理。
- 沙盒机制:和 Android 一样,iOS 也有严格的沙盒机制。每个应用都在一个独立的沙盒中运行,不能随意访问其他应用的数据。
内存管理
Android 和 iOS 在内存管理上采用了两种截然不同的策略,这直接影响了开发者编写代码的方式和对性能的考量。简单来说,Android 使用了垃圾回收 (Garbage Collection, GC),而 iOS 则依赖于自动引用计数 (Automatic Reference Counting, ARC)。
1. Android 的内存管理:垃圾回收(GC)
安卓的垃圾回收是一种自动的内存管理机制。
- 工作原理:
- 在 Android 中,当一个对象不再被任何变量引用时,它就成了“垃圾”。
- GC 线程会定期扫描内存中的所有对象。当它发现一个不再被引用的对象时,就会将其标记为可回收,并在合适的时机释放这块内存。
- 开发者无需手动释放内存。
- GC 的优缺点:
- 优点: 开发者不需要关心何时释放内存,这大大降低了内存管理的复杂性,可以更专注于业务逻辑。
- 缺点:
- 不可控性: GC 的执行时机是不确定的。当 GC 运行时,它会暂停应用的主线程(这被称为 “Stop-The-World”),这可能导致应用的卡顿,尤其是在处理大量对象时。
- 内存开销: GC 需要额外的内存来追踪和管理对象,这可能导致比 ARC 略高的内存占用。
- 内存泄漏:
- 尽管有 GC,但 Android 仍然会发生内存泄漏。最常见的情况是长生命周期对象引用了短生命周期对象。
- 例如,一个
Activity对象被一个全局的单例对象引用,当Activity应该被销毁时,由于单例对象依然持有它的引用,GC 无法回收它,从而导致内存泄漏。开发者需要特别注意这种情况。
2. iOS 的内存管理:自动引用计数(ARC)
ARC 是一个编译器级别的内存管理机制。
- 工作原理:
- 每个对象都有一个引用计数器。当一个对象被创建时,其引用计数为 1。
- 当一个变量引用了这个对象时,它的引用计数会加 1(称为
retain);当引用被移除时,引用计数会减 1(称为release)。 - 当引用计数减到 0 时,说明没有任何变量在使用这个对象,ARC 会立即释放这块内存。
- 这个过程都是由编译器在编译时自动插入
retain和release代码来实现的,开发者无需手动调用。
- ARC 的优缺点:
- 优点:
- 性能高: ARC 的内存管理在编译时完成,没有运行时暂停(Stop-The-World)的开销,因此性能更高,更适合对响应速度要求高的移动应用。
- 可预测性: 内存释放的时机是确定的,一旦引用计数归零,对象就会立即被释放。
- 缺点:
- 循环引用(Retain Cycle): 这是 ARC 最主要的问题。当两个或多个对象相互持有对方的强引用时,它们的引用计数永远不会归零,导致内存无法被释放,造成内存泄漏。
- 优点:
- 如何解决循环引用:
- 开发者需要使用 弱引用(
weak) 和 无主引用(unowned) 来打破循环引用。 weak: 不会增加对象的引用计数。当引用的对象被释放后,weak引用会自动置为nil。常用于父子关系中,子对象对父对象的引用。unowned: 类似于weak,也不会增加引用计数,但它假定所引用的对象在它自身生命周期内永远不会被释放。如果引用的对象被释放了,访问unowned引用会引发运行时错误。常用于明确知道被引用对象生命周期比自身长的场景。
- 开发者需要使用 弱引用(
总的来说,Android 的 GC 让开发者更省心,但代价是可能牺牲部分性能。iOS 的 ARC 提供了更精细的控制和更高的性能,但要求开发者对引用关系有清晰的认识,并主动解决循环引用问题。理解这两种机制的优劣,是成为一名优秀的跨平台开发者必备的知识。
界面管理
Android 的 Activity 主要负责管理一个单独的用户界面(UI),并处理用户交互。它的生命周期(如 onCreate、onPause、onDestroy)是开发者需要重点关注的。
iOS 中对应的概念是 UIViewController。
- UIViewController 是 iOS 应用界面的核心。每一个屏幕(或一个屏幕上的一个重要部分)都由一个
UIViewController来管理。它负责管理视图(View),处理事件,并在用户界面和数据模型之间扮演桥梁角色。 - 生命周期:
UIViewController同样拥有自己的生命周期方法,例如viewDidLoad(视图加载完成)、viewWillAppear(视图即将显示)和viewWillDisappear(视图即将消失)。开发者需要在这几个方法中处理界面的初始化和状态保存。 - 页面跳转:Android 通常通过
Intent来启动新的Activity。在 iOS 中,页面跳转通常通过UINavigationController(用于堆栈式的页面管理)或present(_:animated:completion:)方法(用于模态弹出页面)来实现。
后台任务
Android 的 Service 主要用于在后台执行长时间运行的操作,且没有用户界面,比如下载文件或播放音乐。Service 可以在 Activity 被销毁后继续运行。
iOS 并没有一个与 Service 完全对应的组件,它的后台任务管理更加严格和精细化,主要通过以下几种机制实现:
- 短暂的后台任务(Background Task):当应用从前台切换到后台时,系统会给它几秒钟的时间(通常是 3 到 10 分钟)来完成一些任务,例如保存数据或结束网络请求。
- 后台模式(Background Modes):对于需要持续在后台运行的应用,如音乐播放器、位置追踪或 VoIP 电话,开发者需要在
Info.plist文件中声明特定的“后台模式”。只有被系统明确允许的这些服务(如音乐播放、地理位置更新等)才能在后台持续运行。 - 后台刷新(Background App Refresh):允许应用定期在后台刷新内容,但刷新时机由系统根据设备电量、网络状态等因素智能决定。
- 静默推送(Silent Push Notifications):服务器可以向应用发送一种特殊的“静默推送”,唤醒应用在后台执行一小段代码(例如拉取新内容)。