布局方法详解

本文详细介绍 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

OpenClaw 源码导读(一):架构总览 — 为"单个主人"而设计的 AI Gateway

2025 年 11 月,Peter Steinberger(PSPDFKit 创始人、前著名 iOS 社区人物)把自己折腾出来的个人 AI 助手 Molty 开源为 OpenClaw。短短几个月,仓库吃到 35 万+ star、93 个 release、360 多个 contributor,核心代码量在 TypeScript/Swift/Kotlin/Go/Python 之间横跨 40 多万行。它和 Claude Code、Codex CLI 乍看都是"跑在本地的 CLI Agent",但设计哲学完全不同——Claude Code 是一个编程助手,OpenClaw 是一个长在你设备上的私人秘书:它挂在 WhatsApp/Telegram/iMessage/微信 的 IM 客户端后面,能自己发消息、自己开浏览器、自己调用 iOS/Android 上的摄像头和麦克风。 本文是 OpenClaw 源码导读系列的第一篇,目标是把整个项目的"地图"摊开。先讲清楚它要解决什么问题、在怎样的信任模型下运行,再把仓库 100 多个顶层模块拎出来分类,最后给出后续系列文章的导航。 一、它到底是什么 1. 一句话定义 OpenClaw 官方给自己的定位是 “Personal AI Assistant. Any OS. Any Platform. The lobster way.” 翻译过来就是:一个在你自己设备上运行、从你自己现有的聊天工具里和你对话的个人 AI 助手。 这个定位里藏着三个关键差异: Personal:它不是多租户 SaaS,也不是团队协作工具,而是单一主人的私人助手。整个信任模型就是为"只有一个 operator"优化的。 Any OS / Any Platform:Gateway 是 Node 进程,可以跑在 macOS/Linux/Windows(WSL2)、甚至 Fly.io/Docker/NAS 上;客户端包含 iOS/Android/macOS 原生 App,还有 Web Dashboard。 The lobster way:作者把它拟人化成一只太空龙虾 Molty,这是一个品牌/吉祥物层面的设计,但也反映了项目的"玩心"。 2. 和 Claude Code 对比:两个相似却完全不同的 CLI Agent 由于作者 Steipete 本身是 Claude Code 的重度用户和 Anthropic 的合作者,社区最常问的问题就是"和 Claude Code 有啥区别"。这里先画一张对比表: ...

May 2, 2026

iOS中的数据库

数据持久化是移动端开发的核心能力之一。iOS提供了从轻量级KV存储到完整关系数据库的多层次方案。本文将从底层存储原理出发,系统性地梳理iOS中各类数据库与持久化技术的设计思想、实现机制和适用场景。 一、iOS数据持久化方案全景 iOS中常见的数据持久化方案,按数据复杂度和使用场景可分为以下几层: graph TD subgraph KV["轻量级KV存储"] A["UserDefaultsplist序列化"] B["Keychain加密存储"] C["MMKVmmap + protobuf"] end subgraph FILE["文件存储"] D["plist / JSON"] E["NSCoding / Codable归档"] end subgraph SQL["关系数据库"] F["SQLiteC语言API"] G["FMDBOC封装"] H["WCDBORM + 加密"] end subgraph ORM["ORM框架"] I["Core DataApple官方"] J["Realm零拷贝"] K["SwiftDataSwift原生"] end KV --> |"数据结构更复杂"| FILE FILE --> |"需要结构化查询"| SQL SQL --> |"需要对象映射"| ORM 方案 数据模型 查询能力 线程安全 加密 适用场景 UserDefaults KV (plist类型) Key查找 线程安全 无 用户偏好、简单配置 Keychain KV (Data) Key查找 线程安全 系统加密 密码、Token、证书 MMKV KV (protobuf) Key查找 线程安全 可选AES 高频读写的KV数据 plist/JSON文件 字典/数组 无 不安全 无 静态配置、缓存 NSCoding/Codable 对象图 无 不安全 无 对象序列化 SQLite 关系表 SQL 需手动处理 可选(SQLCipher) 结构化数据、复杂查询 FMDB 关系表 SQL FMDatabaseQueue 可选 SQLite的OC封装 WCDB 关系表+ORM SQL+ORM 自动管理 内置 高性能结构化存储 Core Data 对象图 NSPredicate NSManagedObjectContext 无 复杂对象关系 Realm 对象 类型安全查询 对象冻结 可选AES 高性能对象存储 SwiftData Swift对象 #Predicate宏 ModelContext 无 Swift原生数据持久化 二、轻量级KV存储 2.1 UserDefaults UserDefaults是iOS最常用的轻量级存储方案,底层将数据以plist格式序列化到磁盘。 ...

May 2, 2026