布局方法详解

本文详细介绍 iOS 中三种布局方式(Frame、Auto Layout、UIStackView)的原理和对比,视图更新的三个阶段(约束、布局、绘制),UIView 与 CALayer 的关系和区别,以及 updateConstraints、layoutSubviews、setNeedsLayout、layoutIfNeeded、setNeedsDisplay 等相关方法的作用、调用时机和区别。 iOS 布局方式 iOS 提供了三种主要的布局方式,按出现时间排列: 布局方式 引入版本 核心思想 API Frame 布局 iOS 2 直接指定视图的位置和大小 frame、bounds、center Auto Layout iOS 6 通过约束描述视图间关系,系统自动计算 frame NSLayoutConstraint、Anchor API UIStackView iOS 9 线性排列子视图,自动管理约束 UIStackView Frame 布局 直接通过设置 frame 属性来确定视图的位置和大小,是最基础的布局方式。 let label = UILabel() label.frame = CGRect(x: 20, y: 100, width: 200, height: 44) view.addSubview(label) 优点:性能最好(无需约束求解),逻辑直观,完全可控。 缺点:需要手动计算所有数值,难以适配不同屏幕尺寸和动态内容,维护成本高。 适用场景:简单固定布局、性能敏感的场景(如 UITableViewCell 内大量视图手动布局)、需要频繁更新 frame 的动画。 Auto Layout Auto Layout 是基于 约束(Constraint) 的布局系统。开发者不直接设置 frame,而是描述视图之间的关系(如"A 的左边距 B 右边 16pt"),系统通过求解约束方程组自动计算出每个视图的 frame。 ...

May 2, 2026

iOS中的生命周期

iOS开发中,理解各种生命周期是非常重要的基础知识。本文将详细介绍应用生命周期、UIViewController生命周期、UIView生命周期,以及其他常见的生命周期。 应用生命周期(App Lifecycle) 应用状态 iOS应用有五种运行状态: 状态 描述 Not Running 应用未启动或已被系统终止 Inactive 应用在前台运行但未接收事件(如来电、锁屏时的过渡状态) Active 应用在前台运行并接收事件,这是应用的正常运行状态 Background 应用在后台执行代码,通常只有短暂的执行时间 Suspended 应用在后台但不执行代码,系统会在内存不足时自动终止该状态的应用 状态转换图 stateDiagram-v2 [*] --> Not_Running Not_Running --> Inactive : 启动 Inactive --> Active Active --> Background Background --> Inactive Background --> Suspended Suspended --> [*] UIApplicationDelegate 方法(iOS 12及之前) 在iOS 12及之前的版本中,应用生命周期主要通过UIApplicationDelegate协议来管理: // 应用启动完成 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 初始化配置、第三方SDK等 return true } // 应用即将进入非活动状态 func applicationWillResignActive(_ application: UIApplication) { // 暂停正在进行的任务、禁用定时器 // 游戏应该在此暂停 } // 应用已进入后台 func applicationDidEnterBackground(_ application: UIApplication) { // 释放共享资源、保存用户数据 // 可以请求额外的后台执行时间 } // 应用即将进入前台 func applicationWillEnterForeground(_ application: UIApplication) { // 撤销进入后台时所做的更改 } // 应用已变为活动状态 func applicationDidBecomeActive(_ application: UIApplication) { // 重启被暂停的任务 // 如果应用之前在后台,可以刷新UI } // 应用即将终止 func applicationWillTerminate(_ application: UIApplication) { // 保存数据、清理资源 // 注意:如果应用从Suspended状态被终止,此方法不会被调用 } UISceneDelegate 方法(iOS 13+) 从iOS 13开始,Apple引入了Scene-based生命周期,支持多窗口场景。 ...

May 5, 2026

卡顿-离屏渲染

离屏渲染(Offscreen Rendering)是GPU渲染中的性能杀手。本文介绍离屏渲染的原理、触发条件以及优化方案。 什么是离屏渲染 正常渲染流程 正常情况下,GPU直接将渲染结果写入帧缓冲区(Frame Buffer): flowchart LR subgraph 正常渲染["正常渲染 On-Screen Rendering"] A[GPU渲染] --> B[Frame Buffer帧缓冲区] --> C[屏幕显示] end style A fill:#e1f5fe style B fill:#fff3e0 style C fill:#e8f5e9 特点: 直接渲染到帧缓冲区 渲染完成即可显示 效率最高 离屏渲染流程 离屏渲染需要先渲染到离屏缓冲区,再合成到帧缓冲区: flowchart LR subgraph 离屏渲染["离屏渲染 Offscreen Rendering"] A[GPU渲染] --> B[Offscreen Buffer离屏缓冲区] B --> C[Frame Buffer帧缓冲区] C --> D[屏幕显示] end E[额外的缓冲区] -.-> B style A fill:#e1f5fe style B fill:#f48fb1 style C fill:#fff3e0 style D fill:#e8f5e9 style E fill:#f48fb1,stroke:#f44336,stroke-dasharray: 5 5 性能开销: ...

May 2, 2026

卡顿

卡顿是影响用户体验的关键问题之一。当应用无法在一帧的时间内(通常为16.67ms)完成渲染工作时,就会出现掉帧,用户会感知到界面不流畅。 本系列文章从渲染原理入手,系统性地介绍iOS卡顿的检测与优化方法。 卡顿监控是 APM 流畅性子系的核心能力,线上指标体系、检测采集、上报策略请参考 APM 系列:指标体系、数据采集、业界方案(微信 Matrix / 字节 Slardar 卡死监控)。 什么是卡顿 iOS设备的屏幕刷新率通常为60Hz(ProMotion设备可达120Hz),这意味着每秒需要渲染60帧画面。每一帧的渲染时间窗口约为16.67ms(1000ms / 60)。 当CPU或GPU无法在这个时间窗口内完成工作时,就会发生掉帧(Frame Drop),用户会感知到卡顿。 掉帧情况 用户感知 严重程度 偶尔掉1-2帧 几乎无感知 轻微 连续掉帧 明显卡顿 中等 长时间阻塞(>250ms) 界面冻结 严重 渲染流程概述 iOS的渲染流程涉及CPU和GPU的协作,通过Core Animation Pipeline完成: flowchart TB subgraph APP["App 进程(CPU 阶段)"] direction TB Layout["Layout(布局计算)layoutSubviews、约束计算"] Display["Display(绘制显示)drawRect:、文本绘制"] Prepare["Prepare(准备)图片解码、格式转换"] Commit["Commit(提交)打包图层树"] Layout --> Display --> Prepare --> Commit end subgraph RS["Render Server 进程(GPU 阶段)"] direction TB Decode["解码图层树"] Vertex["顶点着色器"] Raster["光栅化"] Fragment["片段着色器"] Buffer["帧缓冲区写入"] Decode --> Vertex --> Raster --> Fragment --> Buffer end APP -->|"IPC 传输"| RS RS --> VSync["VSync 信号到来"] VSync --> Screen["显示到屏幕"] CPU与GPU的职责 阶段 主要工作 常见瓶颈 CPU 布局计算、视图创建、文本计算、图片解码 复杂布局、大量文本、主线程阻塞 GPU 纹理渲染、图层混合、离屏渲染 离屏渲染、大量透明图层、超大图片 卡顿的常见原因 CPU瓶颈 主线程阻塞:耗时操作在主线程执行 复杂布局:大量约束计算、频繁布局 文本计算:复杂富文本、大量文本渲染 图片解码:大图片在主线程解码 对象创建:大量对象的创建和销毁 GPU瓶颈 离屏渲染:圆角、阴影、遮罩触发离屏渲染 图层混合:大量透明图层叠加 超大图片:纹理尺寸超过GPU限制 复杂特效:模糊、滤镜等GPU密集操作 文章导航 本系列包含以下文章,建议按顺序阅读: ...

May 2, 2026

类型擦除

类型擦除(Type Erasure)是一种编程技术,用于在运行时隐藏具体的类型信息,使得不同类型可以通过统一的接口进行操作。在iOS开发中,Objective-C和Swift都有类型擦除的概念,但实现方式和应用场景有所不同。 Swift中的类型擦除 Swift是一门强类型语言,类型信息在编译时被严格检查。但在某些场景下,我们需要隐藏具体类型信息,这时就需要使用类型擦除技术。 存在类型与不透明类型 在理解类型擦除之前,需要先理解 Swift 类型系统中两个关键概念:存在类型(Existential Type) 和不透明类型(Opaque Type)。它们是协议在类型层面的两种使用方式,也是理解 any 和 some 关键字的基础。 存在类型(Existential Type) “存在类型"这个术语来自类型论中的存在量词(∃)。它表达的语义是:“存在某个类型 T 遵循了协议 P,但我不知道也不关心 T 具体是什么。” 当你把一个协议当作类型来使用时(而非泛型约束),你就在使用存在类型: protocol Animal { func makeSound() -> String } struct Dog: Animal { func makeSound() -> String { "Woof!" } } struct Cat: Animal { func makeSound() -> String { "Meow!" } } // animals 数组中的每个元素都是存在类型 // 编译器不知道每个元素的具体类型,只知道"存在某个类型遵循了 Animal" var animals: [any Animal] = [Dog(), Cat()] 存在类型的核心特征: 运行时多态:具体类型在运行时才确定,同一个变量可以在不同时刻持有不同的具体类型 有性能开销:Swift 通过存在容器(Existential Container)来存储值,方法调用需要通过见证表间接派发 类型信息被隐藏:调用方只能通过协议接口与值交互,无法访问具体类型的特有成员 在 Swift 5.6 之前,直接把协议写成类型(如 let x: Animal)就是存在类型,但语法上没有任何标记。Swift 5.6 引入 any 关键字使其变得显式,目的是提醒开发者这里存在运行时开销。Swift 5.7 起,对于带有关联类型的协议,必须使用 any 才能作为存在类型。 ...

May 2, 2026

计算机网络

网络分层模型 OSI七层模型 OSI(Open Systems Interconnection)是国际标准化组织提出的网络通信参考模型: 层级 名称 功能 协议/设备举例 7 应用层 为应用程序提供网络服务 HTTP, FTP, DNS, SMTP 6 表示层 数据格式转换、加密/解密 SSL/TLS, JPEG, ASCII 5 会话层 建立、管理和终止会话 RPC, SQL 4 传输层 端到端的可靠数据传输 TCP, UDP 3 网络层 路由选择与IP寻址 IP, ICMP, ARP 2 数据链路层 帧的封装与MAC寻址 Ethernet, Wi-Fi 1 物理层 比特流的物理传输 光纤, 双绞线 TCP/IP四层模型 实际工程中更常用的是TCP/IP四层模型,它将OSI的上三层合并为应用层,下两层合并为网络接口层: graph TB subgraph "TCP/IP四层模型" A["应用层HTTP, DNS, FTP, SMTP"] B["传输层TCP, UDP"] C["网络层IP, ICMP, ARP"] D["网络接口层Ethernet, Wi-Fi"] end subgraph "OSI七层模型" E["应用层"] F["表示层"] G["会话层"] H["传输层"] I["网络层"] J["数据链路层"] K["物理层"] end A --- E A --- F A --- G B --- H C --- I D --- J D --- K 数据封装与解封装 数据在发送方从上到下逐层封装,每一层都将上层传来的数据视为载荷(Payload),在其前面添加本层的头部信息(部分层还会添加尾部信息),然后交给下一层处理。接收方则从下到上逐层解封装,每一层剥离本层头部后将载荷交给上层。 ...

May 3, 2026

包瘦身-App Thinning

App Thinning是Apple提供的包体积优化技术,让用户只下载适合其设备的内容。本文详细介绍App Thinning的三个组成部分及其原理。 整体架构 开发者上传: ┌─────────────────────────────────────────┐ │ Universal IPA │ │ ├── arm64 代码 │ │ ├── @1x/@2x/@3x 图片 │ │ ├── iPhone/iPad 资源 │ │ └── Metal GPU资源(OpenGL ES已弃用) │ └─────────────────────────────────────────┘ ↓ App Store处理 ↓ ┌───────────┐ ┌──────────┐ ┌──────────┐ │ iPhone 12 │ │ iPhone 8 │ │ iPad Pro │ │ 变体 │ │ 变体 │ │ 变体 │ │ @3x图片 │ │ @2x图片 │ │ @2x图片 │ │ arm64 │ │ arm64 │ │ arm64 │ │ A14 GPU │ │ A11 GPU │ │ M1 GPU │ └───────────┘ └──────────┘ └──────────┘ App Thinning包含三个部分: ...

May 2, 2026

包瘦身-分析工具

在进行包体积优化之前,首先需要了解如何分析包体积。本文介绍常用的分析工具和Mach-O文件结构。 查看IPA构成 解压IPA文件分析各部分大小: # 解压IPA unzip -q YourApp.ipa -d ./ipa_content # 查看各文件大小 find ./ipa_content -type f -exec ls -lh {} \; | sort -k5 -h -r | head -20 LinkMap分析 LinkMap文件记录了链接器生成可执行文件的详细信息,可以精确分析二进制大小: # Build Settings中设置 Write Link Map File = YES Path to Link Map File = $(TARGET_TEMP_DIR)/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt LinkMap文件结构 # Path: /path/to/YourApp # Arch: arm64 # Object files: [ 0] linker synthesized [ 1] /path/to/ViewController.o [ 2] /path/to/Model.o # Sections: # Address Size Segment Section 0x100001000 0x00012345 __TEXT __text 0x100013345 0x00001234 __TEXT __stubs # Symbols: # Address Size File Name 0x100001000 0x00000100 [ 1] -[ViewController viewDidLoad] 0x100001100 0x00000050 [ 2] -[Model init] 解析脚本 可以使用脚本解析LinkMap统计各模块大小: ...

May 2, 2026

包瘦身-可执行文件优化

可执行文件通常占iOS应用包体积的30-50%,是优化的重点。本文介绍各种可执行文件优化手段及其原理。 编译器优化选项 优化级别原理 编译器优化级别控制了LLVM在编译时应用的优化Pass数量和类型。 什么是优化Pass? Pass是指编译器对代码进行的一次特定优化处理过程。LLVM将优化工作分解为多个独立的Pass,每个Pass负责一种特定类型的优化: 源代码 → 前端解析 → IR(中间表示)→ Pass1 → Pass2 → Pass3 → ... → 优化后的代码 → 机器码 常见的优化Pass包括: Pass名称 作用 Dead Code Elimination 删除永远不会执行的代码 Constant Folding 编译时计算常量表达式,如3+5直接变成8 Function Inlining 将函数调用替换为函数体本身 Loop Unrolling 展开循环,减少循环控制开销 Common Subexpression Elimination 消除重复计算的表达式 不同的优化级别启用不同数量和类型的Pass: # Build Settings Optimization Level = -Os # 级别:-Os 优化大小(推荐用于Release) -O0(无优化) 不应用任何优化Pass,编译速度最快。代码按原样生成,保留所有变量和控制流,便于调试。适用于Debug构建。 -O1(基础优化) 应用不显著增加编译时间的基础优化,包括: 基本的死代码消除 简单的常量折叠 基本块合并 死代码消除: 死代码消除(Dead Code Elimination)是指编译器自动移除永远不会被执行或执行结果不会被使用的代码。注意:这里的死代码消除是编译阶段的优化,作用于函数内部;而后文提到的Dead Code Stripping是链接阶段的优化,用于移除未被引用的整个函数或类。 -O1 适合需要一定优化但对编译速度敏感的场景。 -O2(标准优化) 应用大多数优化Pass,是性能和体积的平衡点。包括: ...

May 2, 2026

包瘦身-资源优化

资源文件通常占iOS应用包体积的40-60%,是优化的重要方向。本文介绍各种资源优化手段及其原理。 图片优化 Asset Catalog原理 Asset Catalog(.xcassets)编译后生成Assets.car文件,这是一个优化过的资源容器: 编译前: Images.xcassets/ ├── AppIcon.appiconset/ │ ├── icon-60@2x.png │ └── icon-60@3x.png ├── Logo.imageset/ │ ├── logo@2x.png │ └── logo@3x.png └── Contents.json 编译后: Assets.car ← 单一文件,包含所有资源 Assets.car的优化: 图片被重新编码,使用更高效的压缩 支持App Thinning,按设备分发 运行时高效加载(内存映射) 可以使用assetutil查看Assets.car内容: assetutil --info Assets.car 图片压缩原理 PNG压缩原理: PNG使用DEFLATE无损压缩,但可以通过以下方式减小体积: 原始PNG结构: ┌────────────────┐ │ PNG Header │ ├────────────────┤ │ IHDR (图像信息) │ ├────────────────┤ │ IDAT (图像数据) │ ← 压缩的像素数据 ├────────────────┤ │ 辅助chunks │ ← 可以移除 └────────────────┘ 优化方向: 1. 移除不必要的chunks(如tEXt、iTXt) 2. 优化DEFLATE压缩参数 3. 减少颜色数量(有损) 4. 使用更高效的滤波器 有损压缩原理(pngquant): ...

May 2, 2026