迪米特法则

什么是迪米特法则? 迪米特法则(Law of Demeter, LoD)也称为最少知识原则(Least Knowledge Principle, LKP),由 Ian Holland 在 1987 年提出。 Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. 每个单元应该只对与其紧密相关的单元有有限的了解。 更通俗的表述是:只和直接朋友交流,不要和陌生人说话。 核心思想 什么是"直接朋友"? 一个对象的"直接朋友"包括: 当前对象本身(this/self) 作为参数传入的对象 当前对象创建的对象 当前对象的成员变量 全局对象(如单例,技术上可访问,但应谨慎依赖) class Person { let wallet: Wallet // 成员变量 - 直接朋友 func buyItem(shop: Shop, item: Item) { // 参数 - 直接朋友 let receipt = Receipt() // 自己创建的对象 - 直接朋友 // 以下都是与直接朋友交流 let money = wallet.getMoney() shop.sell(item: item) receipt.record(item: item) } } 什么是"陌生人"? 如果需要通过多层对象去访问另一个对象,那个被间接访问的对象就是"陌生人"。 ...

May 2, 2026

外观模式

定义 外观模式(Facade Pattern)是一种结构型设计模式,它为子系统中的一组接口提供一个统一的高层接口。外观模式定义了一个高层接口,使得子系统更加容易使用。 外观模式的核心思想是通过引入一个外观类,将复杂子系统的内部复杂性隐藏起来,只暴露一个简单的接口给客户端。 为什么需要外观模式 外观模式要解决的核心问题是:简化复杂子系统的使用,为客户端提供一个更简单的接口。 问题场景:假设我们正在开发一个视频播放功能,需要处理音频、视频、字幕等多个子系统。 直接使用各个子系统: class VideoPlayerController { func playVideo(url: URL) { // 1. 初始化音频引擎 let audioEngine = AudioEngine() audioEngine.configure(sampleRate: 44100) audioEngine.setOutputDevice(.speaker) // 2. 初始化视频解码器 let videoDecoder = VideoDecoder() videoDecoder.setCodec(.h264) videoDecoder.configureHardwareAcceleration(true) // 3. 初始化渲染器 let renderer = VideoRenderer() renderer.setResolution(1920, 1080) renderer.setFrameRate(30) // 4. 加载字幕 let subtitleLoader = SubtitleLoader() let subtitles = subtitleLoader.load(from: url) // 5. 同步各个组件 let synchronizer = AVSynchronizer() synchronizer.link(audio: audioEngine, video: videoDecoder) // 6. 开始播放 let data = loadVideoData(url) let decodedVideo = videoDecoder.decode(data) renderer.render(decodedVideo) audioEngine.play() // 还要处理暂停、seek、音量调节... } } 这种方式有什么问题? ...

May 2, 2026

动态化 DSL 详解

动态化 DSL(Domain-Specific Language,领域特定语言)是"不改 App 主包、不走应用商店审核,只下发一段描述文件就能改变界面和逻辑"的一类技术。它在 2015~2023 年成为国内大厂客户端的必备基础设施:阿里 DinamicX / Tangram / VirtualView / LuaView、字节 Lynx、腾讯 Mars / 小程序、滴滴 Hummer、美团 MRN / Recce、京东 Taro-Native、拼多多 Lego、支付宝 Nebula / BirdNest、爱奇艺 Doric ……几乎每一家超级 App 都至少造过一套自己的动态化 DSL。 进入 2026 年 AI Coding 时代,动态化 DSL 的定位发生了根本性转变: 过去它服务于"业务迭代速度"——解决包大小限制、审核周期、多端复用问题。 现在它服务于"Agent 生成 UI"——LLM 更擅长生成 JSON / 结构化 DSL 而不是跨平台原生代码,DSL 成为 Generative UI / SDUI(Server-Driven UI)落地移动端的唯一高效载体。 本文从定义出发,讲透动态化 DSL 的历史演进、分类体系、核心渲染管线、关键原理(AST / 表达式引擎 / 布局 / 数据绑定 / 差分),再深入解剖 DinamicX、Tangram、LuaView、Lynx、Hummer、Recce 六大主流方案的工程实现,最后系统分析 AI Coding 时代的优劣势与决策矩阵。 ...

May 2, 2026

WebView离线包

背景 Hybrid 开发模式下,WebView 加载 H5 页面的体验一直是痛点。一个典型的 H5 页面加载流程涉及:初始化 WebView -> DNS 解析 -> 建立连接 -> 下载 HTML -> 解析 HTML -> 下载 CSS/JS/图片 -> 渲染页面。在弱网或首次加载场景下,白屏时间常常达到 2~5 秒,远不如 Native 体验。 离线包的核心思路是:将 H5 的静态资源(HTML、CSS、JS、图片、字体等)预先打包下发到客户端本地,WebView 加载时直接从本地读取资源,跳过网络请求环节,从而大幅缩短页面加载时间。 离线包加载 vs 在线加载 sequenceDiagram participant App participant WebView participant Local as 本地离线包 participant Server as 远程服务器 Note over App, Server: 在线加载流程 App->>WebView: loadURL WebView->>Server: DNS + TCP + TLS + HTTP请求 Server-->>WebView: HTML WebView->>Server: 请求 CSS/JS/图片 Server-->>WebView: 资源文件 WebView->>WebView: 渲染页面 Note over App, Server: 离线包加载流程 App->>WebView: loadURL(被拦截) WebView->>Local: 读取本地 HTML Local-->>WebView: HTML WebView->>Local: 读取本地 CSS/JS/图片 Local-->>WebView: 资源文件 WebView->>WebView: 渲染页面 对比项 在线加载 离线包加载 首屏时间 2~5秒(弱网更久) 0.5~1秒 网络依赖 强依赖 仅更新时需要网络 白屏问题 严重 基本消除 资源新鲜度 实时最新 有一定延迟 流量消耗 每次访问都消耗 仅增量更新消耗 整体架构 一个完整的离线包系统包含三大部分: ...

May 2, 2026

KVC底层原理

基础概念 KVC(Key-Value Coding,键值编码)是 Apple 提供的一种通过字符串 key 间接访问对象属性的机制,定义在 NSKeyValueCoding 协议中,NSObject 默认遵循该协议。 // 直接访问 person.name = @"Tom"; NSString *name = person.name; // KVC 访问 [person setValue:@"Tom" forKey:@"name"]; NSString *name = [person valueForKey:@"name"]; KVC 的核心价值在于:通过字符串动态访问属性,不需要在编译期知道属性名。这使得字典转模型、序列化/反序列化、Interface Builder 的 Runtime Attributes 等场景成为可能。 setValue:forKey: 的底层流程 当调用 [obj setValue:value forKey:@"name"] 时,Runtime 按照以下顺序查找: flowchart TD A["setValue:forKey:@'name'"] --> B{"查找 setter 方法"} B -->|"找到"| C["调用 setter"] B -->|"未找到"| D{"accessInstanceVariablesDirectly返回 YES?"} D -->|"YES"| E{"按顺序查找实例变量_name → _isName → name → isName"} D -->|"NO"| F["调用 setValue:forUndefinedKey:默认抛出 NSUndefinedKeyException"] E -->|"找到"| G["直接设置实例变量值"] E -->|"未找到"| F 第一步:查找 setter 方法 Runtime 按以下顺序查找 setter 方法: ...

May 2, 2026

FoundationModels

2025年WWDC上,Apple把Apple Intelligence背后的那个"设备端基础模型"第一次对开发者开放——这就是FoundationModels框架。过去要在App里接入LLM,基本只有两条路:调OpenAI/Claude这类云端API,或者自己在端上集成llama.cpp、MLC、Core ML等推理框架。前者有隐私和成本问题,后者有工程门槛和包体积问题。FoundationModels试图提供第三条路:一个系统级的、免费的、离线可用的、原生Swift的LLM API。 本文从框架定位开始,一路讲到Guided Generation、Tool Calling、Streaming、Adapter微调等细节,目标是让你在读完后能直接上手用它写出一个生产可用的功能。 一、FoundationModels是什么 1.1 一句话定义 FoundationModels是iOS 26 / macOS 26及之后的系统框架,提供对设备端Apple Intelligence语言模型的编程访问能力。模型本身约3B参数,经过4-bit量化,常驻系统,不计入App包体积,所有推理在设备上完成。 1.2 能做什么 / 不能做什么 能做 不能做 文本生成、摘要、分类、抽取、改写、翻译 世界知识问答(模型太小,不适合当"百科") 结构化输出(生成指定Swift类型) 图像生成 / 视频生成 工具调用(Function Calling) 嵌入向量生成(Embedding) 短对话、小规模推理 长上下文的复杂推理(窗口有限) 完全离线、无需联网 替代GPT-4这类前沿大模型 定位非常清楚:它是一个"端侧小而美"的模型,负责那些没必要上云、对延迟/隐私/成本敏感的任务。 1.3 为什么值得iOS开发者关注 零成本:不像调OpenAI API,每token都花钱。 零延迟网络层:首token延迟可以做到100ms级。 隐私合规:输入不出设备,符合医疗、金融、教育场景的合规要求。 离线可用:地铁、飞机、隧道里也能跑。 原生Swift DSL:宏驱动的@Generable、Tool协议,跟SwiftUI一样"声明式"。 与系统深度集成:可直接访问Apple Intelligence上下文(比如屏幕识别、个人数据等,受权限约束)。 二、整体架构 graph TB subgraph APP ["应用层"] UI["SwiftUI / UIKit"] VM["ViewModel"] end subgraph FM ["FoundationModels Framework"] SLM["SystemLanguageModel模型句柄 + 可用性"] SESSION["LanguageModelSession会话对象 / 上下文"] GEN["Generable宏结构化输出"] TOOL["Tool协议工具调用"] STREAM["ResponseStream流式输出"] GUARD["Guardrails安全过滤"] end subgraph SYSTEM ["系统层"] ANE["Apple Neural Engine"] GPU["GPU"] MODEL["On-device Foundation Model≈3B params, 4-bit quant"] ADAPTER["LoRA Adapters"] end UI --> VM VM --> SESSION SESSION --> SLM SESSION --> GEN SESSION --> TOOL SESSION --> STREAM SESSION --> GUARD SLM --> MODEL MODEL --> ANE MODEL --> GPU ADAPTER --> MODEL 几个关键抽象: ...

May 2, 2026

命令模式

定义 命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成对象,从而可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。 命令模式的核心思想是:将"请求"封装成一个对象,使得可以用不同的请求、队列或日志来参数化其他对象,同时支持可撤销操作。 为什么需要命令模式 命令模式要解决的核心问题是:将请求(操作)封装成对象,使得可以对请求进行存储、传递、撤销等操作。 问题场景:假设我们正在开发一个文本编辑器,需要支持撤销/重做功能。 最直接的方式可能是这样: class TextEditor { var text: String = "" func insertText(_ newText: String, at position: Int) { let index = text.index(text.startIndex, offsetBy: position) text.insert(contentsOf: newText, at: index) } func deleteText(from start: Int, length: Int) { let startIndex = text.index(text.startIndex, offsetBy: start) let endIndex = text.index(startIndex, offsetBy: length) text.removeSubrange(startIndex..<endIndex) } } // 用户操作 let editor = TextEditor() editor.insertText("Hello", at: 0) editor.insertText(" World", at: 5) editor.deleteText(from: 5, length: 6) // 如何实现撤销?需要记住每次操作的类型、参数、以及如何逆向执行 // 这变得非常复杂... 这种方式有什么问题? ...

May 2, 2026

WebView底层原理

前言 iOS 上的 WebView 目前以 WKWebView 为事实标准。它对外表现得像一个 UIView 子类,但内部其实对接着整个 WebKit 引擎——一个由多个独立进程协作的庞大系统。日常开发中遇到的那些困扰:为什么 Web 页白屏不会把 App 带崩?为什么 Cookie 和 App 自身的 NSHTTPCookieStorage 总是对不上?为什么 JS 调用 Native 永远是异步?为什么 NSURLProtocol 拦不到 WKWebView 的请求?这些问题单看 API 都得不到答案,必须下潜到 WebKit 源码里才能看清楚。 好在 WebKit 是 Apple 官方开源的项目,仓库在 github.com/WebKit/WebKit。本文基于其 Source/ 目录下的公开源码,按"源码地图 → 多进程架构 → 各进程职责 → 全链路串联 → 工程启示"的顺序,把 WKWebView 从创建到渲染一帧的底层机制梳理清楚。 一、先看源码地图 阅读源码前,先在脑子里建立目录结构。以 WebKit/WebKit 仓库的 Source/ 目录为例: Source/ ├── JavaScriptCore/ # JS 引擎(解释器、JIT 编译器、GC) ├── WebCore/ # 渲染引擎核心(DOM、CSSOM、Layout、Paint) ├── WebKit/ # 多进程框架 + Cocoa 层 API │ ├── UIProcess/ # 主进程侧(App 进程) │ │ ├── API/Cocoa/ # WKWebView.mm、WKProcessPool.mm 等 │ │ ├── API/ios/ # WKWebViewIOS.mm │ │ ├── ios/ # WKContentView.mm、WebPageProxyIOS.mm │ │ └── ... │ ├── WebProcess/ # 渲染进程侧(WebContent) │ ├── NetworkProcess/ # 独立网络进程 │ ├── GPUProcess/ # 独立 GPU 进程 │ └── Shared/ # 跨进程共享的数据结构、IPC 消息定义 └── WTF/ # 基础容器、线程、字符串工具库 日常 iOS 开发能看到的 WKWebView,对应的实现就在 Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm。它是一个 Objective-C++ 文件,内部只是持有一个 C++ 的 WebPageProxy 对象,几乎所有实际工作都转发给它。为什么要这样设计?原因就藏在下一节要讲的多进程架构里。 ...

May 2, 2026

SOLID原则

概述 SOLID 是面向对象设计的五大基本原则的首字母缩写,由 Robert C. Martin(Uncle Bob)在 2000 年代初期提出。这五个原则是构建可维护、可扩展软件的基础,在 iOS 开发中同样适用。 字母 原则 英文名称 S 单一职责原则 Single Responsibility Principle O 开放封闭原则 Open-Closed Principle L 里氏替换原则 Liskov Substitution Principle I 接口隔离原则 Interface Segregation Principle D 依赖倒置原则 Dependency Inversion Principle 单一职责原则(Single Responsibility Principle, SRP) 定义 A class should have one, and only one, reason to change. 一个类应该只有一个引起它变化的原因。 原则解读 单一职责原则要求每个类只负责一项职责。这里的"职责"可以理解为"变化的原因"——如果你能想到多于一个的原因去改变一个类,那么这个类就有多于一个的职责。 核心思想:将不同的关注点分离,使每个模块只专注于做好一件事。 解决的问题 1. 代码耦合度高 当一个类承担多个职责时,这些职责会相互依赖、相互影响。修改其中一个职责的代码,可能会无意中破坏另一个职责的功能。 2. 难以复用 如果一个类混合了多个职责,当你只想使用其中一个职责时,不得不引入整个类及其所有依赖。 3. 维护困难 职责混杂的类通常代码量大、逻辑复杂,理解和修改都很困难。不同职责的代码交织在一起,修改一处可能产生意想不到的连锁反应。 4. 测试复杂 ...

May 2, 2026

React Native 详解

React Native 是 Meta 2015 年开源的跨平台框架,用 JavaScript/TypeScript 写一份业务代码,在 iOS 和 Android 上渲染出真正的原生控件(UIView / android.view.View),而不是像 Flutter 那样自绘,也不是像 H5 那样跑在 WebView 里。经过 2018~2024 年的"新架构"大重构,RN 在 0.76 让 New Architecture(Fabric + TurboModules + JSI + Codegen + Bridgeless)成为默认,并在 0.82(2025 年 10 月)彻底告别旧的 Bridge 时代——这是 RN 十年来最大的一次范式迁移。 对 iOS 开发者而言,RN 的定位很特殊:UI 仍然是 UIKit,但控制 UI 的大脑换成了 JS。这让它一方面拥有"原生 Look & Feel“的天然优势(iOS 26 Liquid Glass 不用等框架跟进),另一方面又背负着”JS/原生边界抖动“的原罪。在 AI Coding 时代,RN 凭借 Web 生态庞大的训练语料和 Expo 工具链成为 LLM 生成代码正确率最高的移动框架,但 JSI 的 C++ 层和 Bridgeless 的新调试链路也让"AI 修 Bug"变得比以前更难。 ...

May 2, 2026