Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

iOS 基础速览 (辅)

这是辅助篇, 目标不是把你培养成 iOS 工程师, 而是让你在面试里接得住 iOS 相关提问, 展示 “跨端潜力”.重点是用 Android 概念类比, 快速建立对照.

一, Swift 语言基础

  • 变量: let(常量, 类比 Kotlin val), var(变量).类型推断.
  • 可选值 (Optional): var name: String?, 概念等同 Kotlin 可空类型.
    • 可选绑定 if let x = name { } / guard let x = name else { return }.
    • 强制解包 name!(类比 Kotlin !!).
    • 可选链 name?.count(类比 ?.).
    • 空合运算 name ?? "default"(类比 Elvis ?:).
  • 闭包 (Closure): 类比 Kotlin Lambda, { (x: Int) -> Int in x * 2 }, 尾随闭包语法.
  • 协议 (Protocol): 类比接口, 但更强大 (可扩展, 面向协议编程 POP).
  • 结构体 vs 类: struct 是值语义, class 是引用语义. 实际存储位置由编译器根据逃逸分析和运行时优化决定, 不能简化成 “struct 一定在栈, class 一定在堆”.
  • 枚举: 带关联值的枚举很强大, 类比 Kotlin sealed class.

二, 内存管理 ARC

  • ARC(Automatic Reference Counting): 编译期自动插入 retain/release, 不是 GC(无运行时回收线程).引用计数为 0 即释放.
  • 循环引用: 两个对象互相强引用导致无法释放 (类比内存泄漏).
  • 解决: weak(弱引用, 可为 nil), unowned(非持有引用, 常用于生命周期确定的关系).闭包捕获用 [weak self](类比避免 Handler 持有 Activity).
  • 与 Android 对比: Android 用 GC 可达性分析自动回收; iOS 用 ARC 引用计数, 开发者需主动打破循环引用.

三, UI 体系

  • UIViewController 生命周期: viewDidLoad(类比 onCreate)→ viewWillAppear → viewDidAppear → viewWillDisappear → viewDidDisappear.
  • UIKit: 命令式 UI (类比 View 体系), 用 Storyboard/XIB 或纯代码.
  • SwiftUI: 声明式 UI (类比 Jetpack Compose), @State/@Binding/@ObservedObject 管理状态, 概念和 Compose 高度相似: 你学了 Compose 就好理解.

最小 iOS 页面, 权限与沙盒认知

以下是上下文片段, 放在 UIKit 的 UIViewController 子类中; 它不是完整 App 工程. viewDidLoad 只表示 View 已加载, 不等于页面已经可见; 网络请求, 埋点和动画应按实际可见性分别放在合适的生命周期节点.

final class ProfileViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // 页面已出现在前台;适合开始需要可见页面的工作.
    }
}

应用级前后台事件由 AppDelegate/SceneDelegate(或 SwiftUI 的 scenePhase) 接收, 不能只依赖单个 ViewController. 权限也不是 “声明后即可访问”:先在 Info.plist 写清用途说明, 再在用户触发操作时请求系统授权, 并处理拒绝, 受限和到设置页恢复后的状态. 沙盒目录按数据语义选择: 用户创建且不可重建的文档放 Documents; 内部持久数据放 Library/Application Support; 可重建缓存放 Library/Caches; 临时文件放 tmp, 后两者均可能被系统清理. 是否备份和是否在 Files 中可见并不会由目录自动保证, 应按业务, 备份排除配置和文件共享配置分别验证. 不要把 token, 密钥或敏感明文放进 UserDefaults, 应使用 Keychain.

ARC 循环引用的可观察例子

以下是可在 Xcode Playground 或 App 工程运行的示例: driver 创建对象, 建立双向连接后将两个外部变量置空. 强引用版不会打印 deinit; 弱引用版预期打印两个 deinit. 该结果只用于说明引用关系, 不代表已在本仓库执行.

final class Owner {
    var child: Child?
    deinit { print("Owner released") }
}

final class Child {
    weak var owner: Owner? // 改成 `var owner: Owner?` 会形成强引用环
    deinit { print("Child released") }
}

func makeAndReleasePair() {
    var owner: Owner? = Owner()
    var child: Child? = Child()
    owner!.child = child
    child!.owner = owner
    owner = nil
    child = nil
}

makeAndReleasePair() // weak 版:预期打印 Owner released,Child released(顺序不作为契约)

将 weak var owner 改为强引用 var owner: Owner? 后再运行, 预期不打印任何 deinit, 因为二者互相持有. 排查泄漏时, 症状可能是退出页面后内存持续增长; 证据是 Xcode Memory Graph 中页面, 闭包或 delegate 仍被持有; 定位强引用边, 再按所有权关系选择 weak 或在任务结束时清理闭包. unowned 的重点是不持有对象; 它适用于生命周期关系确定的场景, 若访问已释放对象会崩溃. 常见写法是非可选 unowned, 但 Swift 也支持 optional unowned, 不能据此把它绝对描述为 “不为 nil”.

本篇只建立 iOS 原生基础对照; 共享 Kotlin 代码的工程配置见 Kotlin Multiplatform, 框架取舍见 跨端技术对比.

四, 并发 GCD

  • GCD(Grand Central Dispatch): iOS 的并发框架, 基于队列.
  • 主队列: DispatchQueue.main, UI 操作 (类比 Android 主线程 / Dispatchers.Main).
  • 全局队列: DispatchQueue.global(qos:), 后台任务 (类比 Dispatchers.IO/Default).
  • 异步 / 同步: async(不阻塞, 类比 launch), sync(阻塞当前).
  • 现代 Swift Concurrency 使用 async/await, Task, actor 和 Sendable. 可与 Kotlin 协程类比建立直觉, 但取消, actor 隔离和错误传播规则不能机械等同.
DispatchQueue.global().async {        // 后台
    let data = loadData()
    DispatchQueue.main.async {         // 回主线程更新 UI
        self.label.text = data
    }
}

五, Android / iOS 概念对照表

概念AndroidiOS
页面ActivityUIViewController
列表RecyclerViewUITableView/UICollectionView
声明式 UIJetpack ComposeSwiftUI
异步主线程切换Handler/协程GCD/async-await
内存管理GC (可达性分析)ARC (引用计数)
空安全Kotlin 可空类型Optional
接口interfaceprotocol
依赖管理GradleCocoaPods/SPM
本地存储SharedPreferences/DataStoreUserDefaults
包格式APK/AABIPA

高频面试题 (iOS 通常只是辅助考察)

Q1: iOS 的内存管理和 Android 有什么不同? iOS 用 ARC, 编译期插入 retain/release 做引用计数, 计数为 0 即释放, 无运行时 GC 线程, 需手动用 weak/unowned 打破循环引用. Android 用 GC 可达性分析在运行时回收, 无需手动管理引用计数.

Q2: weak 和 unowned 区别? 都不增加引用计数. weak 引用对象释放后变 nil (通常声明为可选); unowned 假设对象在引用期间一直存在, 访问已释放对象会崩溃. unowned 常见为非可选, 但 Swift 也支持 optional unowned; 生命周期不确定用 weak, 能严格证明被引用对象活得更久时再用 unowned.

Q3: struct 和 class 区别? struct 提供值语义, 赋值和传参在语义上产生独立值, 但编译器可以用 copy-on-write, 逃逸分析等方式优化; class 提供对象身份和共享引用, 由 ARC 管理. 值语义有助于减少无意共享, 但如果字段包含可变引用或跨并发域传递不满足 Sendable / 隔离规则, 并不会自动线程安全.

Q4: SwiftUI 和 Compose 像吗? 相似. 都是声明式, 状态驱动, UI=f (state), 单向数据流.@State 类比 remember+mutableStateOf,@Binding 类比状态提升. 理解了 Compose 基本能迁移过去.

Q5: 你做过 iOS 吗? 能转吗?(开放题) 诚实说做过部分 iOS, 理解 Swift/UIKit/ARC 基础, 有跨端经验; 强调底层 (C/C++) 和概念是相通的, 跨端学习成本可控. 不要夸大成 “精通”.