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

Swift 宏

Swift 5.9(WWDC 2023)引入的宏(Macro)是近几年语言层最大的一次能力扩展。它让 Swift 第一次具备了编译期代码生成的官方机制——你可以在源码层面写一段"能生成代码的代码",编译器会在类型检查前把它展开成真正的 Swift 源码,再走正常的编译流程。SwiftUI 的 #Preview、Observation 框架的 @Observable、SwiftData 的 @Model、Swift Testing 的 @Test / #expect、FoundationModels 的 @Generable / @Tool,全都是宏。 一、为什么要有宏 1.1 Swift 之前能做什么,又差在哪 在宏之前,Swift 已经有一整套"元编程"手段,但每一种都有明显的边界: 机制 做什么 局限 @propertyWrapper 给属性注入行为(如 @Published、@State) 只能包属性,不能生成方法、不能生成类型、不能访问其他成员 @resultBuilder 把一段 DSL 收集成表达式(如 SwiftUI 的 ViewBuilder) 只作用于闭包里,无法改类型定义 KeyPath + Mirror 运行时反射读写属性 只能读,类型安全差,性能差(见 iOS反射) Codable 等编译器魔法 自动合成 init(from:) / encode(to:) 只有编译器内部能写,开发者写不出同等能力的东西 Sourcery / SwiftGen 文本级代码生成 游离于编译器之外,无类型信息、无法访问 AST Objective-C #define 纯文本替换 没有作用域、没有类型、没有卫生(hygiene),对调试器和 IDE 极不友好 宏就是要一次性补齐这块能力:在保留类型安全与 IDE 体验的前提下,把"只有 Swift 编译器团队能写的代码生成"开放给所有开发者。 ...

May 4, 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

static关键字详解

基础概念 static 是 C 语言定义的存储类说明符(Storage Class Specifier),Objective-C 和 Swift 中均有使用,但语义有所扩展。它的核心作用可以归纳为两点: 改变存储位置:将局部变量从栈区移到数据段,使其在函数返回后依然存活 限制链接可见性:将全局变量或函数的链接属性从 external 改为 internal,使其仅在当前编译单元(.m 文件)内可见 flowchart TD A["static 关键字"] --> B["修饰局部变量"] A --> C["修饰全局变量"] A --> D["修饰函数"] A --> E["Swift 中的 static"] B --> B1["存储位置:栈 → 数据段"] B --> B2["生命周期:函数作用域 → 整个进程"] B --> B3["作用域不变:仍限于函数内"] C --> C1["存储位置不变:仍在数据段"] C --> C2["链接属性:external → internal"] C --> C3["作用域缩小:全局 → 当前文件"] D --> D1["存储位置不变:仍在代码段"] D --> D2["链接属性:external → internal"] D --> D3["作用域缩小:全局 → 当前文件"] 一、静态局部变量 基本用法 在函数或方法内部用 static 修饰局部变量,使其只初始化一次,后续调用保留上次的值。 ...

May 3, 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

布局方法详解

本文详细介绍 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多线程方案对比 方案 简介 语言 生命周期管理 pthread POSIX标准的多线程API C 手动管理 NSThread 面向对象的线程封装 OC/Swift 手动管理 GCD Grand Central Dispatch C/OC/Swift 自动管理 NSOperation 基于GCD的面向对象封装 OC/Swift 自动管理 pthread pthread是POSIX标准的多线程API,是最底层的多线程方案,使用C语言编写。 #import <pthread.h> void *threadFunction(void *param) { NSLog(@"pthread执行任务: %@", [NSThread currentThread]); return NULL; } - (void)createPthread { pthread_t thread; pthread_create(&thread, NULL, threadFunction, NULL); } 特点: 跨平台,可移植性强 使用复杂,需要手动管理线程生命周期 实际开发中很少直接使用 NSThread NSThread是苹果对pthread的面向对象封装,使用更加简单。 创建线程的方式 // 方式1:实例方法创建,需要手动启动 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(doTask) object:nil]; thread.name = @"MyThread"; [thread start]; // 方式2:类方法创建,自动启动 [NSThread detachNewThreadSelector:@selector(doTask) toTarget:self withObject:nil]; // 方式3:隐式创建 [self performSelectorInBackground:@selector(doTask) withObject:nil]; 常用方法 // 获取当前线程 NSThread *currentThread = [NSThread currentThread]; // 获取主线程 NSThread *mainThread = [NSThread mainThread]; // 判断是否是主线程 BOOL isMain = [NSThread isMainThread]; // 线程休眠 [NSThread sleepForTimeInterval:2.0]; [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; // 退出当前线程 [NSThread exit]; 线程间通信 // 回到主线程执行 [self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO]; // 在指定线程执行 [self performSelector:@selector(doTask) onThread:thread withObject:nil waitUntilDone:NO]; GCD(Grand Central Dispatch) GCD是苹果推出的多线程解决方案,基于C语言实现,自动管理线程的生命周期。GCD的核心概念是 队列(Queue) 和任务(Task)。 ...

May 2, 2026

iOS响应者链与事件处理机制

当手指触碰iPhone屏幕上的一个按钮时,背后经历了从硬件感知、系统传递、命中测试、事件分发到最终响应的完整链路。本文将系统性地拆解iOS事件处理机制的每个环节。 一、响应者与响应者链 理解事件处理的前提是理解"谁有能力处理事件"。在iOS中,这个问题的答案是 响应者(Responder)。 1.1 UIResponder 所有能够接收并处理事件的对象都继承自 UIResponder: classDiagram NSObject <|-- UIResponder UIResponder <|-- UIView UIResponder <|-- UIViewController UIResponder <|-- UIApplication UIView <|-- UIWindow UIView <|-- UIControl UIView <|-- UIScrollView UIControl <|-- UIButton UIControl <|-- UISlider UIScrollView <|-- UITableView class UIResponder { +nextResponder: UIResponder? +touchesBegan(touches, event) +touchesMoved(touches, event) +touchesEnded(touches, event) +touchesCancelled(touches, event) } UIResponder 定义了处理触摸事件的四个核心方法: - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event; - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event; 1.2 nextResponder与响应者链 每个 UIResponder 都有一个 nextResponder 属性,指向"下一个响应者"。所有响应者通过这个属性串联成一条 响应者链(Responder Chain)。 ...

May 2, 2026

weak详解

本文将深入探讨iOS中weak引用的实现原理,包括底层数据结构、核心函数实现、生命周期管理,以及weak、unowned、unsafe_unretained三者的对比。 weak的基本概念 weak是一种弱引用修饰符,它不会增加对象的引用计数,也就是说不会持有对象。当对象被释放时,所有指向该对象的weak引用会自动被置为nil,这是weak最核心的特性。 // Objective-C中使用weak @property (nonatomic, weak) id<SomeDelegate> delegate; __weak NSObject *weakObj = strongObj; // Swift中使用weak weak var delegate: SomeDelegate? weak var weakObj = strongObj weak的底层数据结构 要理解weak的实现原理,首先需要了解几个核心数据结构。 SideTable SideTable是Runtime中非常重要的数据结构。关于SideTable在引用计数存储中的作用,请参考iOS中的内存管理-侧表存储。 struct SideTable { os_unfair_lock slock; // 锁,保证线程安全 RefcountMap refcnts; // 引用计数哈希表 weak_table_t weak_table; // 弱引用表 }; 系统维护了一个固定大小的SideTable数组,称为StripedMap。通过对对象地址做哈希和取模来定位对应的SideTable: // 通过对象地址获取对应的SideTable static SideTable& table = SideTables()[obj]; // 内部等效逻辑:index = hash(obj) % StripeCount // StripeCount为StripedMap的大小 由于对象数量远大于SideTable数量,多个对象会被映射到同一个SideTable,这类似于哈希表中的哈希冲突。这种设计的核心目的是分散锁竞争——每个SideTable拥有独立的锁,不同SideTable上的操作可以并行执行,相比单一全局表大幅提升了多线程性能。 weak_table_t weak_table_t是存储弱引用关系的哈希表,采用 开放寻址法(线性探测) 解决哈希冲突: struct weak_table_t { weak_entry_t *weak_entries; // 连续分配的数组,作为开放寻址哈希表的底层存储 size_t num_entries; // 当前已使用的条目数量 uintptr_t mask; // 容量掩码(= 数组容量 - 1),用于 hash & mask 快速取模 uintptr_t max_hash_displacement; // 最大哈希冲突偏移量 }; 虽然 weak_entries 的类型是 weak_entry_t *(即一块连续内存),但元素不是按顺序填入的——插入时通过 hash(referent地址) & mask 计算目标槽位,冲突时向后线性探测。因此它本质上是一个用数组实现的开放寻址哈希表,而非普通的顺序数组。 ...

May 2, 2026

Runtime

什么是 Runtime Runtime 是 Objective-C 的运行时系统,是一套底层的 C 语言 API。Objective-C 是一门动态语言,很多操作都是在运行时而非编译时决定的,这一切都依赖于 Runtime。 Runtime 提供的核心能力: 消息发送与转发 方法交换(Method Swizzling) 关联对象(Associated Objects) 动态创建类和对象 动态添加和修改方法、属性 关于对象、类、isa 指针等底层数据结构的详细介绍,请参考 Objective-C底层原理-NSObject。本文专注于 Runtime 的动态能力和实际应用。 消息发送机制 Objective-C 的消息发送机制是其动态性的核心体现。与 Swift 支持静态派发不同,Objective-C 的方法调用都是动态消息派发。更多关于两者的对比,请参考 Objective-C与Swift区别。 objc_msgSend Objective-C 的方法调用本质上是消息发送,编译器会将方法调用转换为 objc_msgSend 函数: // 源代码 [obj doSomething]; // 编译后 objc_msgSend(obj, @selector(doSomething)); 消息发送流程 1. 检查 receiver 是否为 nil,如果是则直接返回 2. 通过 isa 找到 receiver 的类对象 3. 在类对象的方法缓存(cache_t)中查找方法 4. 如果缓存命中,直接调用方法实现(IMP) 5. 如果缓存未命中,在类对象的方法列表中查找 6. 如果找到,缓存方法并调用 7. 如果未找到,沿着 superclass 链向上查找 8. 如果最终未找到,进入消息转发流程 方法缓存 为了提高消息发送效率,Runtime 在类对象中使用哈希表缓存最近调用的方法: ...

May 2, 2026