设计模式概述

什么是设计模式? 设计模式(Design Pattern)是软件开发中反复出现问题的经典解决方案。它们是前人在大量实践中总结出的、被证明有效的代码设计经验,可以帮助开发者编写出更加灵活、可维护、可复用的代码。 设计模式不是具体的代码,而是解决特定问题的思想和模板。正确运用设计模式可以: 提高代码的可读性和可维护性 增强代码的可复用性 降低模块间的耦合度 提供通用的设计词汇,便于团队沟通 设计模式的起源 设计模式的概念最早由"四人帮"(Gang of Four,简称GoF)在1994年出版的《Design Patterns: Elements of Reusable Object-Oriented Software》一书中系统化提出。书中总结了23种经典设计模式,这些模式至今仍是软件设计的重要参考。 设计模式分类 GoF将23种设计模式分为三大类: 创建型模式(Creational Patterns) 创建型模式关注对象的创建机制,旨在以合适的方式创建对象,而不是直接使用new操作符。这类模式使得代码在创建对象时更加灵活。 模式 描述 iOS常见应用 单例模式 确保一个类只有一个实例 UIApplication.shared、FileManager.default 工厂方法模式 定义创建对象的接口,由子类决定实例化哪个类 NSNumber的工厂方法 抽象工厂模式 创建一系列相关对象而无需指定具体类 UIKit组件创建 建造者模式 将复杂对象的构建与表示分离 URLComponents、AlertController 原型模式 通过复制现有实例创建新对象 NSCopying协议 结构型模式(Structural Patterns) 结构型模式关注类和对象的组合,用于形成更大的结构。这类模式帮助确保当系统的一部分发生改变时,整个系统不需要随之改变。 模式 描述 iOS常见应用 适配器模式 将一个类的接口转换成客户期望的另一个接口 协议适配、第三方库封装 桥接模式 将抽象部分与实现部分分离 平台无关的抽象设计 组合模式 将对象组合成树形结构以表示部分-整体层次结构 UIView层级结构 装饰器模式 通过包装对象在运行时动态添加职责 包装器对象、链式装饰 外观模式 为子系统提供一个统一的高层接口 SDK封装、模块门面 享元模式 运用共享技术有效支持大量细粒度对象 字体对象、颜色对象等可共享的不可变对象 代理模式 为其他对象提供一种代理以控制访问 NSProxy、虚拟代理、保护代理 行为型模式(Behavioral Patterns) 行为型模式关注对象之间的通信和职责分配,描述类或对象如何交互以及如何分配职责。 ...

May 27, 2026

值类型和引用类型的区别

定义 值类型 值类型是指变量直接存储自身字段或描述信息的类型。值类型具有值语义,当将一个值类型变量赋值给另一个变量时,语义上会得到一份独立的值;底层是否立即复制完整数据,取决于编译器优化和写时拷贝等实现。 引用类型 引用类型是指变量存储的是指向数据在内存中位置的引用(指针)。当将一个引用类型变量赋值给另一个变量时,两个变量指向同一块内存区域。 Objective-C中的类型分类 在Objective-C中: 值类型:基础数据类型(int、float、double、BOOL、char等)、结构体(struct)、枚举(enum) 引用类型:除基础数据类型之外的大部分类型,包括NSObject及其子类(NSString、NSArray、NSDictionary、自定义类等) Swift中的类型分类 Swift对类型的分类更加清晰: 值类型:基础数据类型(Int、Float、Double、Bool等)、字符串(String)、结构体(Struct)、枚举(Enum)、数组(Array)、字典(Dictionary)、集合(Set)等 引用类型:类(Class)、闭包(Closure)、Actor等 拷贝机制差异 值类型 - 值语义拷贝 每个值类型变量都有自己的字段存储 对一个变量的操作不会影响另一个变量 赋值语义上会得到独立副本;如果字段是引用类型,复制的是引用本身,底层也可能通过写时拷贝延迟真正的数据复制 引用类型 - 浅拷贝 引用类型在内存中有一个指向该位置的引用 引用类型的变量可以指向相同类型的数据 对一个变量进行的操作会影响另一变量所指向的数据 内存分配机制 需要注意:值类型/引用类型描述的是语义模型,不等价于栈/堆分配规则。Swift编译器会根据生命周期、逃逸分析、优化级别、协议类型包装、集合存储等因素决定具体放在哪里。 理解Swift对象和字段的内存位置,可以先记住三个规则: class实例本体通常在堆上,通过引用计数管理生命周期 struct/enum的字段通常内联存储在这个值本身所在的位置 class类型的字段存储的是对象引用,也就是一个指针,真实class实例仍然在堆上 常见存储位置 局部值类型变量:生命周期明确、未逃逸时,通常可以放在当前线程的栈区,甚至被优化到寄存器中 class实例:实例本体在堆区,局部变量中保存的是指向堆对象的引用 class里的struct属性:struct属性内联存储在class实例这块堆内存中 struct里的class属性:struct内部只保存class引用,真实class实例仍然在堆上 被逃逸闭包捕获的值类型可能随闭包上下文一起存储在堆区 Array、Dictionary、Set、String等写时拷贝值类型通常只有一小段描述信息是值本身,真实元素或字符缓冲区可能在堆上 协议类型(existential container)持有大值类型时,如果超过存在容器的内联缓冲区大小,会使用堆分配 indirect enum的间接关联值会通过堆上的盒子存储,常见于递归枚举 全局变量、静态变量不属于栈区,也不是普通意义上的堆对象,它们通常位于全局/静态存储区 栈区内存分配和销毁通常只需移动栈顶指针,成本较低;堆区更动态,但分配、释放和引用计数维护都有额外成本。 struct中包含class属性 class Dog { var name: String init(name: String) { self.name = name } } struct Person { var age: Int var dog: Dog } var p1 = Person(age: 18, dog: Dog(name: "Lucky")) var p2 = p1 p2.age = 20 p2.dog.name = "Max" print(p1.age) // 18 print(p1.dog.name) // Max 内存关系可以近似理解为: ...

May 10, 2026

Objective-C与Swift区别

语言设计 Objective-C 是C的超集,在C的基础上添加了Smalltalk风格的消息传递机制 对象间交互不通过调用方法而是发送消息,以实现面向对象编程和超高的动态性 动态性由 Objective-C Runtime 系统支持,可以在运行时修改类结构、方法交换等,详见 Runtime 弱类型语言,核心类型id可以指向任何Objective-C对象 Swift 从很多现代化语言中汲取精华,是一门偏重于安全、性能的现代化语言 Swift 也有自己的运行时系统(Swift Runtime),但设计哲学完全不同,更强调编译期优化 强类型语言,编译器在编译阶段进行严格的类型检查 语法特性 Objective-C 语法冗长,需要显式声明类型 命名通常较长且需要添加前缀避免冲突 需要单独的.h和.m文件声明接口和实现 引用其他文件/模块必须要import(Objective-C中import详解) 协议不支持默认实现 泛型是"轻量级泛型",仅用于编译时类型检查,运行时类型信息会被擦除 Swift 语法简洁,强大的类型推断减少冗余的类型声明 有命名空间(模块)的存在避免命名冲突 不需要单独的头文件声明接口 默认情况下变量必须初始化 引用同模块的其他文件不需要import(Swift中import详解) 协议支持默认实现、支持拓展,支持关联类型以实现泛型协议 值类型也可以遵守协议 泛型在编译时进行类型特化优化,支持完整的泛型元编程 内存管理 Objective-C 大部分都是引用类型,依赖引用计数管理内存 ARC在编译时自动插入retain、release、autorelease调用 64位系统使用优化的isa(Non-pointer isa),通过位域在isa中嵌入引用计数(extra_rc字段),溢出时使用SideTable存储 实例变量(ivar)会被Runtime自动初始化为0/nil Swift 引用类型也是依赖引用计数管理内存 存在大量的值类型,例如结构体、枚举、数组、字典等 非逃逸的值类型的内存存在栈区由系统进行自动管理 纯Swift类的引用计数直接存储在对象头部的RefCount字段中,无需SideTable,访问更高效 编译器在编译时就确保了初始化,不需要运行时统一清零 拓展:值类型和引用类型的区别 拓展:iOS中的内存管理 性能对比 Objective-C 基于消息传递机制本质上是动态派发,运行时查找方法实现,有一定开销 ARC管理引用计数也有开销 引用类型内存存在堆区,内存管理的性能开销会比栈区多很多 Swift 方法派发尽可能使用静态派发(如对final类/方法、私有方法、值类型方法的调用) 编译器可以内联优化,速度接近C 内联优化:比如A方法调用B方法,B方法调用C方法,代码编写调用A方法时编译器通过内联直接调用C方法的实现 值类型内存分配在栈区,性能开销相对较小 虽然值类型是深拷贝,Swift为了优化值类型性能引入写时拷贝(Copy-on-Write)机制,值类型只在写操作发生深拷贝,其他时候都是浅拷贝 安全性 Objective-C 弱类型(动态类型)占主导 核心类型id可以指向任何Objective-C对象 编译器几乎不做类型检查,会有运行时崩溃的风险(如向不响应某消息的对象发送该消息) nil消息发送不会崩溃但可能导致逻辑错误,不容易感知 局部变量不会自动初始化,可能包含栈上的垃圾数据(未定义行为) Swift 可选类型强制处理空值,编译器强制解包检查 强类型和编译时检查阻止了大量常见错误 变量必须初始化后才能使用,编译器在编译阶段强制检查,消除未定义行为 强制初始化使编译器能更精确地追踪变量生命周期,带来更多优化机会 数组越界等操作会在运行时触发明确的崩溃,便于定位问题 编程范式 Objective-C 主要是面向对象编程(OOP) 通过继承实现代码复用 依赖Runtime实现面向切面编程(AOP),如Method Swizzling Swift 拥抱多范式,对函数式编程、面向协议编程(POP)、声明式编程很友好 通过协议扩展提供默认实现,协议组合替代多继承 值类型(struct/enum)也可以遵循协议,不再局限于类 拓展:OOP、POP与AOP ...

May 8, 2026

mmap详解

mmap(Memory-mapped file,内存映射文件)是一种将文件或设备映射到进程地址空间的技术。通过mmap,可以像操作内存一样操作文件,是iOS开发中实现高性能IO的重要手段。 基本概念 什么是mmap mmap是一个POSIX系统调用,它在进程的虚拟地址空间中创建一个映射,将文件内容映射到内存地址。映射建立后,对该内存区域的读写操作会直接反映到文件上。 文件映射:将磁盘上的普通文件映射到内存,用于高效读写文件内容(iOS开发中最常用) 设备映射:将硬件设备的I/O内存(如显卡显存、网卡缓冲区)映射到用户空间,让程序可以像读写内存一样直接与硬件交互,常用于驱动开发和嵌入式系统 flowchart LR subgraph traditional["传统文件IO (read/write)"] direction LR U1["用户缓冲区"] <-- "② 数据拷贝" --> K1["内核缓冲区"] K1 <-- "① 磁盘读取" --> F1["磁盘文件"] end subgraph mmap_io["mmap 内存映射"] direction LR U2["虚拟地址空间映射区域"] <-. "直接映射零拷贝" .-> F2["磁盘文件"] end traditional ~~~ mmap_io 方式 数据流向 拷贝次数 传统IO 磁盘 → 内核缓冲 → 用户缓冲 2次 mmap 磁盘 ↔ 映射区域(直接访问) 0次 mmap的工作原理 flowchart TD A["调用mmap()"] --> B["内核在进程虚拟地址空间分配一段连续地址"] B --> C["建立虚拟地址到文件的映射关系(页表项指向文件页)"] C --> D["返回映射区域的起始地址"] D --> E["进程访问映射地址"] E --> F{"页面是否在内存中?"} F -- 否 --> G["触发缺页中断(Page Fault)"] G --> H["内核从磁盘读取对应页面到物理内存"] H --> I["更新页表,建立映射"] I --> J["进程继续访问"] F -- 是 --> J J --> K["读写操作直接作用于内存"] K --> L["内核适时将脏页写回磁盘"] API详解 mmap函数原型 #include <sys/mman.h> void *mmap( void *addr, // 建议的映射起始地址,通常传NULL让系统决定 size_t length, // 映射区域的长度(字节) int prot, // 内存保护标志(可读/可写/可执行) int flags, // 映射类型标志 int fd, // 文件描述符 off_t offset // 文件偏移量(必须是页大小的整数倍) ); // 返回值:成功返回映射区域的起始地址,失败返回MAP_FAILED 参数详解 prot(内存保护标志): ...

May 7, 2026

Swift底层原理-结构体、类和协议

在Objective-C底层原理-NSObject文章中,我们深入了解了Objective-C对象的底层实现。本文将探讨Swift中类和结构体的底层原理。 Swift数据结构的分类 Swift中的类根据是否继承自NSObject,在底层实现上存在显著差异: 继承自NSObject的Swift类:兼容Objective-C运行时,支持完整的Objective-C特性 纯Swift类:使用Swift原生运行时,性能更优但Objective-C互操作性受限 Swift中结构体是值类型,具有以下核心特征: 栈上分配内存(非逃逸情况) 值语义和完整拷贝 写时拷贝优化(COW) 静态方法派发 Swift类的底层实现 继承自NSObject的Swift类 当Swift类继承自NSObject时,必须兼容Objective-C的运行时系统,其底层实现与Objective-C对象高度一致。 内存布局 class Class: NSObject { var name: String var isMale: Bool var age: Int init(name: String, isMale: Bool, age: Int) { self.name = name self.isMale = isMale self.age = age super.init() } } 实例内存布局: 偏移量 内容 大小 说明 0x0-0x7 isa_t isa 8字节 指向类对象,包含优化的位域信息 0x8-0x17 String name 16字节 Swift String结构(64位系统) 0x18 Bool isMale 1字节 布尔值 0x19-0x1F (padding) 7字节 Swift编译器根据下一个字段(Int,8字节对齐要求)自动插入填充,确保Int字段从8的倍数地址开始 0x20-0x27 Int age 8字节 64位整数 0x28-0x2F (padding) 8字节 最终内存对齐填充 关键特性: ...

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

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