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

+load与+initialize的区别

+load和+initialize是Objective-C中两个特殊的类方法,它们都会被Runtime自动调用,但调用时机、调用方式和使用场景有很大区别。理解它们的差异对于iOS开发和性能优化非常重要。 基本定义 +load方法 +load方法在类被加载到内存时由Runtime自动调用,发生在main函数执行之前。 @implementation MyClass + (void)load { NSLog(@"MyClass loaded"); } @end +initialize方法 +initialize方法在类第一次收到消息时由Runtime自动调用,是一种懒加载机制。 @implementation MyClass + (void)initialize { NSLog(@"MyClass initialized"); } @end 核心区别对比 特性 +load +initialize 调用时机 main函数之前,类加载时 类首次收到消息时 调用方式 直接调用函数指针 通过objc_msgSend 调用次数 每个类只调用一次 可能被调用多次 是否阻塞启动 是 否 是否需要显式调用父类 否,自动调用 否,自动调用 Category的行为 都会被调用 会覆盖主类实现 线程安全 是 是 调用顺序 父类 -> 子类 -> Category 父类 -> 子类 未实现时的行为 不调用 可能调用父类实现 调用时机详解 +load的调用时机 +load的调用发生在dyld加载镜像的过程中: dyld加载Mach-O ↓ 映射到内存 ↓ 读取__DATA段中的__objc_nlclslist(非懒加载类列表) ↓ 调用所有类的+load方法 ↓ 读取__objc_nlcatlist(非懒加载分类列表) ↓ 调用所有分类的+load方法 ↓ main()函数执行 +initialize的调用时机 +initialize在类第一次收到消息时调用: ...

May 2, 2026

iOS编译原理

编译器架构:LLVM 与 Clang iOS 开发中的两种主要语言 Objective-C 和 Swift 都基于 LLVM 编译器基础设施。理解 LLVM 的架构是理解 iOS 编译原理的基础。 LLVM 的三段式架构 LLVM 采用经典的前端-中间表示-后端三段式设计,将编译过程解耦为三个独立的阶段: flowchart LR subgraph Frontend["前端(Frontend)"] direction TB A1["Clang(OC/C/C++)"] A2["Swift 前端"] end subgraph Middle["中间表示"] direction TB B1["LLVM IR"] end subgraph Backend["后端(Backend)"] direction TB C1["arm64(iOS真机)"] C2["x86_64(模拟器)"] end A1 --> B1 A2 --> B1 B1 --> C1 B1 --> C2 阶段 职责 iOS 中的实现 前端(Frontend) 词法分析、语法分析、语义分析,将源码转换为中间表示 Clang(OC/C/C++)、Swift 前端 中间表示(IR) 与语言无关、与平台无关的中间形式,承载优化 LLVM IR(Swift 还有额外的 SIL 层) 后端(Backend) 将 IR 转换为目标平台的机器码 arm64(真机)、x86_64(Intel 模拟器) 三段式架构的核心优势是解耦:新增一门语言只需实现一个前端,新增一个目标平台只需实现一个后端,所有语言和平台共享同一套优化基础设施。这正是 Apple 能同时支持 OC 和 Swift 两种语言、多种架构(arm64、x86_64)的技术基础。 ...

May 2, 2026

iOS反射

反射(Reflection)是指程序在运行时检查、访问和修改自身结构(类型、属性、方法等)的能力。在 iOS 开发中,Objective-C 和 Swift 分别提供了不同层次的反射支持:OC 依赖 Runtime 提供完整的动态反射能力,Swift 则通过 Mirror 提供只读的内省能力。 Objective-C 的反射能力 Objective-C 的反射能力完全建立在 Runtime 之上。Runtime 在运行时维护了完整的类型元数据(类对象、元类对象、方法列表、属性列表、成员变量列表、协议列表等),并通过一系列 C 函数 API 暴露给开发者,使得程序可以在运行时动态地查询和修改几乎所有类型信息。 关于 Runtime 的消息发送、消息转发、Method Swizzling、关联对象等核心能力,请参考 runtime。本节聚焦于 Runtime 中与"反射"直接相关的能力——即运行时的类型内省和动态操作。 类型内省 类型内省(Introspection)是反射中最基础的能力——在运行时查询一个对象的类型信息。 NSObject 提供的内省方法 NSObject 定义了一组内省方法,这些方法底层都依赖 Runtime 的 isa 指针和类型元数据来实现: id obj = [[NSMutableArray alloc] init]; // 类型判断 [obj isKindOfClass:[NSArray class]]; // YES — 判断是否是某个类或其子类的实例 [obj isMemberOfClass:[NSMutableArray class]]; // YES — 判断是否是某个类的直接实例 [obj conformsToProtocol:@protocol(NSCoding)]; // YES — 判断是否遵循某个协议 // 方法响应检查 [obj respondsToSelector:@selector(addObject:)]; // YES — 判断是否能响应某个消息 // 获取类型信息 NSStringFromClass([obj class]); // @"__NSArrayM"(真实的私有子类名) NSStringFromSelector(@selector(count)); // @"count" 需要注意 isKindOfClass: 和 isMemberOfClass: 的区别:前者沿继承链向上查找,后者只比较当前类。另外,[obj class] 返回的可能是私有子类(如类簇 NSArray 的实际类是 __NSArrayI 或 __NSArrayM),而 object_getClass(obj) 能获取真正的 isa 指向的类(KVO 场景下会是 NSKVONotifying_ 前缀的子类)。 ...

May 2, 2026