SIL(Swift Intermediate Language)

SIL(Swift Intermediate Language,Swift 中间语言)是 Swift 编译器在编译过程中生成的一种中间表示(Intermediate Representation,IR)。它位于 Swift 源码与 LLVM IR 之间,是 Swift 编译器架构中至关重要的一层。 SIL 的设计目标是: 对 Swift 语言的高级语义进行精确建模 在高层抽象的基础上执行强大的优化 保留足够的类型信息,用于诊断和安全检查 作为 Swift 特有优化的载体,弥补 LLVM IR 无法直接表达 Swift 语义的不足 SIL 并不是给开发者日常编写的语言,而是编译器的内部表示。理解它有助于深入理解 Swift 的编译过程、性能优化原理和内存管理机制。 Swift 编译流程概览 在了解 SIL 之前,需要先理解 Swift 的整体编译流程: flowchart TD A[Swift 源代码 .swift] --> B[词法分析 Lexer] B --> C[语法分析 Parser] C --> D[语义分析 Sema] D --> E[SILGen: 生成 Raw SIL] E --> F[SIL 优化 Guaranteed Passes] F --> G[SIL 优化 General Passes] G --> H[IRGen: 生成 LLVM IR] H --> I[LLVM 优化] I --> J[机器码 .o] J --> K[链接器 Linker] K --> L[可执行文件 / 动态库] 各阶段简述 词法分析(Lexer) 编译的第一步。Lexer 逐字符扫描 .swift 源文件,将其切分为一系列Token(词法单元),例如关键字 func、标识符 add、运算符 +、字面量 42、左右括号等。Token 是后续所有分析的最小输入单位。这一步会剥离注释和空白,但保留它们的位置信息以便后续生成精确的诊断信息(错误提示的行号和列号)。 ...

May 2, 2026

Transformer架构原理

ChatGPT、Claude、Gemini——这些模型的核心都是同一个架构:Transformer。它在2017年由Google的论文"Attention Is All You Need"提出,彻底改变了自然语言处理(NLP)领域,随后席卷了计算机视觉、语音、蛋白质折叠等几乎所有AI领域。 我们从最直觉的角度出发,一步步搞清楚Transformer到底在做什么。 一、从序列到序列:Transformer要解决什么问题 1.1 语言的本质是序列 一句话是一个词的序列: "我 今天 很 开心" → [我, 今天, 很, 开心] 语言模型的核心任务是:给定前面的词,预测下一个词。 "我 今天 很" → ?(开心?难过?忙碌?) 但词与词之间的关系非常复杂。“它"指代谁?“银行"是河岸还是金融机构?这取决于上下文中其他词的信息。 1.2 RNN的困境 在Transformer之前,处理序列的主流方法是循环神经网络(RNN)。RNN像一个人从左到右逐词阅读,用一个"记忆状态"来记住看过的内容: graph LR X1["我"] --> H1["h₁"] H1 --> H2["h₂"] X2["今天"] --> H2 H2 --> H3["h₃"] X3["很"] --> H3 H3 --> H4["h₄"] X4["开心"] --> H4 问题在于: 长距离依赖困难:信息需要经过很多步才能从序列开头传到末尾,容易丢失 无法并行:必须逐步处理,前一步完成才能开始后一步,训练很慢 Transformer的核心洞察是:不需要逐步处理,让序列中的每个位置直接与所有其他位置交互。这就是"注意力(Attention)“机制。 二、词嵌入:给词语一个数学身份 2.1 从独热编码到词向量 计算机不认识"猫”、“狗"这样的文字。我们需要把词转换成数字。 最朴素的方式是独热编码(One-Hot Encoding):假设词汇表有50,000个词,每个词用一个50,000维的向量表示,只有对应位置是1,其余全是0。 "猫" → [0, 0, ..., 1, ..., 0, 0] (第3721个位置是1) "狗" → [0, 0, ..., 0, 1, ..., 0, 0] (第5042个位置是1) 问题:在这种表示下,“猫"和"狗"的距离与"猫"和"经济学"的距离一样远——完全丢失了语义信息。 ...

May 2, 2026

观察者模式

定义 观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一对多依赖关系,当一个对象(主题/被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会收到通知并自动更新。 观察者模式也被称为发布-订阅模式(Publish-Subscribe Pattern)。 为什么需要观察者模式 观察者模式要解决的核心问题是:当一个对象的状态发生变化时,如何自动通知所有依赖它的对象。 问题场景:假设我们正在开发一个电商App,用户登录后需要更新多个页面的状态(首页显示用户名、购物车同步、个人中心更新等)。 最直接的方式可能是这样: class LoginService { func login(username: String, password: String) { // 登录成功后... let user = User(name: username) // 直接调用各个需要更新的对象 HomeViewController.shared.updateUser(user) CartViewController.shared.syncCart(for: user) ProfileViewController.shared.refreshProfile(user) // 如果还有其他需要通知的地方,继续添加... } } 这种方式有什么问题? 紧耦合:LoginService必须知道所有需要通知的对象,依赖关系错综复杂 难以维护:每新增一个需要监听登录事件的页面,都要修改LoginService 违反开闭原则:对扩展不开放,每次扩展都要修改已有代码 单例滥用:为了让LoginService能访问到各个页面,不得不使用大量单例 观察者模式的解决思路: 被观察者(Subject)只负责"广播"状态变化,不关心谁在监听;观察者(Observer)自己注册感兴趣的事件: // 使用NotificationCenter实现 class LoginService { func login(username: String, password: String) { let user = User(name: username) // 只负责发送通知,不关心谁在监听 NotificationCenter.default.post( name: .userDidLogin, object: nil, userInfo: ["user": user] ) } } // 各个页面自己注册监听 class HomeViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver( self, selector: #selector(handleUserLogin), name: .userDidLogin, object: nil ) } @objc func handleUserLogin(_ notification: Notification) { if let user = notification.userInfo?["user"] as? User { updateUI(with: user) } } } 观察者模式的好处: ...

May 2, 2026

代码分割工程化

什么是 Bundle Splitting? Bundle Splitting(代码分割)是一种将前端资源拆分成更小、独立的模块的技术。传统上,前端资源(如 JavaScript、CSS 和图片等)被打包成单个文件,通常称为“bundle”。这种方式存在一个问题,即无论用户需要哪些功能,都必须下载整个 bundle。而 Bundle Splitting 技术通过将代码拆分为更小的模块,按需加载,可以有效解决这个问题。 Bundle Splitting 的优点 减少初始加载时间 通过将代码拆分为多个小块,可以减少初始加载时间。当用户首次访问网页时,只需下载当前页面所需的资源,而无需等待整个应用程序的加载完成。这可以极大地提高页面的响应速度,并减少用户的等待时间。 提升用户体验 通过将代码分割成模块,可以使应用程序更具响应性。当用户与应用进行交互时,只需加载所需的代码块,而不是整个应用。这将减少不必要的资源消耗,并使用户感觉应用更加流畅和快速。 优化缓存策略 拆分代码还能优化缓存策略。当应用程序发生更改时,只需重新加载发生变化的模块,而不必重新下载整个应用。这减少了不必要的网络请求,提高了应用的更新效率,并减轻了服务器的负载。 并行加载资源 通过将代码拆分为多个模块,可以实现并行加载资源。浏览器能够同时下载多个文件,从而减少整体加载时间。这对于大型应用程序和复杂的前端框架特别有益,能够更好地利用浏览器的并行下载能力,提高资源的加载效率。 代码复用和维护 通过将代码分割成模块,可以更好地实现代码的复用和维护。不同的模块可以根据需求进行加载和升级,使得代码结构更加清晰、模块化。这也有助于团队合作,不同开发者可以并行工作在不同的模块上,提高开发效率和代码质量。 Bundle Splitting 的缺点 增加了网络请求 拆分代码会增加应用程序的网络请求次数。每个模块都需要进行一次独立的网络请求,这可能会导致一些额外的延迟和性能损失。在网络条件较差的情况下,这可能会对用户体验产生一定影响。 增加了开发复杂性 使用 Bundle Splitting 技术需要对应用程序的依赖关系进行深入分析和管理。开发人员需要仔细考虑代码的拆分点,以及如何在运行时动态加载模块。这增加了开发复杂性,并要求开发人员具备较高的技术水平和经验。 潜在的代码冗余 当模块之间存在共享的代码片段时,可能会出现潜在的代码冗余问题。如果不合理地进行代码拆分,可能会导致多个模块中存在相同的代码,增加了资源的下载和维护成本。因此,在进行代码拆分时,需要仔细考虑代码复用和代码冗余的问题。 需要额外的工具和配置 Bundle Splitting 需要使用额外的工具和配置来实现代码的拆分和动态加载。这可能需要对构建工具进行配置,如 Webpack、Rollup 等,以及对模块加载器进行调整。这增加了一些学习成本和开发成本,尤其是对于新手来说。 Bundle Splitting 的适用场景 Bundle Splitting 技术适用于大型的 Web 应用程序,特别是那些具有复杂功能和大量依赖的应用。以下是一些适合使用 Bundle Splitting 的场景: 大型单页应用:对于由多个模块组成的单页应用,使用 Bundle Splitting 可以提高初始加载速度,提升用户体验。 按需加载:对于需要动态加载不同功能模块的应用,Bundle Splitting 可以根据用户的需求按需加载模块,减少不必要的资源消耗。 公共库和框架:对于使用公共库或框架的应用,可以将这些库和框架拆分成单独的模块,以便在多个页面中共享和复用。 国际化支持:对于需要支持多种语言的应用,可以将不同语言的资源文件拆分成独立的模块,根据用户的语言偏好进行加载。 代码分支管理:对于大型团队开发的项目,使用 Bundle Splitting 可以更好地实现代码的分支管理和并行开发,提高开发效率。 Bundle Splitting 在知名项目中的应用 示例 1:React.lazy 和 Suspense React.js 是一个广泛使用的 JavaScript 库,用于构建用户界面。React 提供了 React.lazy 和 Suspense API 来实现 Bundle Splitting。 ...

August 7, 2025

WebSocket相关

一、前言 因为项目中使用到了 WebSocket ,面试官在深挖项目经验的时候,也难免提到 WebSocket 相关的知识点,通过这篇文章再做一下总结。 二、什么是WebSocket WebSocket 是一种在单个TCP连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。 在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。 WebSocket本质上一种计算机网络应用层的协议,用来弥补http协议在持久通信能力上的不足。 WebSocket 协议在2008年诞生,2011年成为国际标准。现在最新版本浏览器都已经支持了。 它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。 WebSocket 的其他特点包括: (1)建立在 TCP 协议之上,服务器端的实现比较容易。 (2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。 (3)数据格式比较轻量,性能开销小,通信高效。 (4)可以发送文本,也可以发送二进制数据。 (5)没有同源限制,客户端可以与任意服务器通信。 (6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。 ws://example.com:80/some/path 为什么需要 WebSocket? 我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处? 因为 HTTP 协议有一个缺陷:通信只能由客户端发起,不具备服务器推送能力。 举例来说,我们想了解查询今天的实时数据,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用“轮询”:每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。 在 WebSocket 协议出现以前,创建一个和服务端进双通道通信的 web 应用,需要依赖HTTP协议,进行不停的轮询,这会导致一些问题: 服务端被迫维持来自每个客户端的大量不同的连接 大量的轮询请求会造成高开销,比如会带上多余的header,造成了无用的数据传输。 http协议本身是没有持久通信能力的,但是我们在实际的应用中,是很需要这种能力的,所以,为了解决这些问题,WebSocket协议由此而生,于2011年被IETF定为标准RFC6455,并被RFC7936所补充规范。 并且在HTML5标准中增加了有关WebSocket协议的相关api,所以只要实现了HTML5标准的客户端,就可以与支持WebSocket协议的服务器进行全双工的持久通信了。 WebSocket 与 HTTP 的区别 WebSocket 与 HTTP的关系图: 相同点: 都是一样基于TCP的,都是可靠性传输协议。都是应用层协议。 ...

July 28, 2025

受控组件和非受控组件

前端开发经常会涉及表单的处理,或者其他一些用于输入的组件,比如日历组件。 涉及到输入,就绕不开受控模式和非受控模式的概念。 什么是受控,什么是非受控呢? 想一下,改变表单值只有两种情况: 用户去改变 value 或者代码去改变 value。 如果不能通过代码改表单值 value,那就是非受控,也就是不受我们控制。 但是代码可以给表单设置初始值 defaultValue。 代码设置表单的初始 value,但是能改变 value 的只有用户,代码通过监听 onChange 来拿到最新的值,或者通过 ref 拿到 dom 之后读取 value。 这种就是非受控模式。 反过来,代码可以改变表单的 value,就是受控模式。 注意,value 和 defaultValue 不一样: defaultValue 会作为 value 的初始值,后面用户改变的是 value。 而一旦你给 input 设置了 value,那用户就不能修改它了,可以输入触发 onChange 事件,但是表单的值不会变。 用户输入之后在 onChange 事件里拿到输入,然后通过代码去设置 value。 这就是受控模式。 其实绝大多数情况下,非受控就可以了,因为我们只是要拿到用户的输入,不需要手动去修改表单值。 但有的时候,你需要根据用户的输入做一些处理,然后设置为表单的值,这种就需要受控模式。 或者你想同步表单的值到另一个地方的时候,类似 Form 组件,也可以用受控模式。 value 由用户控制就是非受控模式,由代码控制就是受控模式。 我们写代码试一下: npx create-vite 创建 vite + react 的项目。 去掉 main.tsx 的 index.css 和 StrictMode: ...

December 17, 2024

浏览器的同源策略

概述 本文从以下三个问题,循序渐进,一步步拆解,何为浏览器的同源策略和跨域问题: 什么是同源策略和跨域问题? 怎样才算同源? 同源策略下会有什么限制? 什么是同源策略和跨域问题? 同源政策是 1995 年由 Netscape 公司(就是创造了 JS 的那家网景公司)引入浏览器的一种策略。 想要理解同源策略的话,可以先假设如果没有同源策略会发生什么事情。接下来我们通过一则恐怖故事代入理解一下没有同源策略的世界: 在某个深夜,小明独自一人在家浏览网页,无意间打开了一些特殊网站,而恰巧的是,小明之前刚好打开过某些重要支付平台的网站且还没关闭,这时特殊网站里面可能有一些特殊 JS 脚本,在偷偷获取小明支付平台上的信息,并且偷偷利用了这些信息再结合某些手段,成功把小明支付平台上的钱转走了,小明上完厕所回来后,突然收到支付平台转账成功的短信,然后然后然后瘫坐在地上,泣不成声,仰天长啸,大喊一声:“还我攒了半年的 9.99~” 恐怖故事听完了,我们来先上个概念,什么是同源策略? 借用 MDN 上的描述: 同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。 怎么理解这个概念呢?举个栗子,随着汽车数量的上升,现在很多城市都有各种限行政策,就拿广州来说,如果不是 粤A 的车牌进入城区的话会有开四停四的限行限制。 同源策略跟汽车限行政策有一点点像,同源策略规定在“同源”下的脚本、文档等资源访问不受限(有点像 粤A 牌的汽车在广州内行驶不会被限行),如果不是“同源”则无法互相访问脚本、文档等资源(非 粤A 牌的汽车在广州市区内受开四停四政策限制) 理解了同源策略后,对于跨域就很好理解了,同源策略是好,我们也需要同源策略,但有些时候我们确实需要不同源的两个网页互相读取信息,这是就产生了跨域问题。 额外补充,同源策略是浏览器的行为,是浏览器的行为,是浏览器的行为(对于这点的解析可以参考:同源策略下,服务器会收到浏览器的请求吗?) 怎样才算同源? 所谓“同源”,就是以下三者必须都相同: 协议相同 协议相同的两个域名:(都是 https 协议) https://juejin.cn https://juejin.cn 协议不同的两个域名:(一个是 http 协议,一个是 https 协议) http://juejin.cn https://juejin.cn 域名相同 域名相同的两个域名: https://juejin.cn https://juejin.cn 域名不同的两个域名:(一个是二级域名,一个是三级域名)(可能这个是最容易记错的) https://juejin.cn https://www.juejin.cn 端口相同 端口相同的两个域名: https://juejin.cn https://juejin.cn 端口不同的两个域名: https://juejin.cn https://juejin.cn:8081 再重复一遍, 同协议、同域名、同端口,才算同源 同协议、同域名、同端口,才算同源 同协议、同域名、同端口,才算同源 同源策略下会有什么限制? 在同源策略下,如果非同源,会有以下三个方面的行为受到限制: DOM 如果非同源,其 JS 脚本,不能互相对 DOM 进行读写操作 数据 如果非同源,不能互相读写 Cookies、LocalStorage、SessionStorage 和 IndexedDB 网络请求 如果非同源,不能通过 AJAX 的方式互相发送、接收数据 为了方便理解,接下来我们展开说说这三条规则 ↓↓↓ ...

December 17, 2024

z-index

大家可能觉得,z-index 是一个很简单的属性,你给它设置哪个值,元素就会位于 y 轴的哪个位置。但它实际上并没有我们想象的这么简单,这个属性背后是一系列决定元素所在层级的规则。 在进行今天的介绍前,我们先列出三个问题,如果你能一眼看出它们的解决方案,那么恭喜你掌握了z-index,也就不需要阅读本文了;如果不行,那么耐心看完本文,相信能找到答案。 三个自测问题 问题一:为什么z-index 数值更大,但 Content 没有在 Box 2 之上? <div id="box1"> Box 1 </div> <div id="content"> Content <br/> z-index: 2; </div> <div id="box2"> Box 2 <br/> z-index: 1; </div> div { padding: 25px; font-size: larger; } #box1 { background-color: chocolate; width: 200px; height: 100px; margin-bottom: -50px; } #content { background-color: gold; width: 300px; height: 200px; z-index: 2; } #box2 { background-color: cyan; width: 200px; height: 100px; margin-top: -50px; z-index: 1; } 问题二:明明 z-index 数值更小,为什么 Content 这次反而在Box 2 之上了? <div id="box1"> Box 1 </div> <div id="content"> Content <br/> transform: rotate(90deg); <br/> z-index: 1; </div> <div id="box2"> <br/> Box 2 <br/> z-index: 2; </div> div { padding: 25px; font-size: larger; } #box1 { background-color: chocolate; width: 200px; height: 100px; margin-bottom: -10px; } #content { background-color: gold; width: 250px; height: 200px; z-index: 1; transform: rotate(90deg); } #box2 { background-color: cyan; width: 200px; height: 100px; margin-top: -10px; z-index: 2; } ...

November 9, 2024

Canvas 和 SVG

1.Canvas Canvas 是 HTML5 中新增的一个元素,它提供了一种通过 JavaScript 来绘制图形的方式,使用笔刷绘制2D图案。通过 Canvas,我们可以在网页中动态地绘制出各种图形、图表、动画等内容,实现更丰富的交互效果。 工作方式 Canvas 的工作方式是,我们首先在 HTML 页面中添加一个 <canvas> 元素,并指定其宽度和高度,然后在 JavaScript 中获取该元素的上下文对象,并通过调用它的方法来绘制图形。Canvas 的上下文对象有多种,包括 2D 上下文、WebGL 上下文等,我们可以根据需求选择相应的上下文对象来进行绘制。 常用属性 属性 效果 fillStyle 填充颜色。默认为黑色 strokeStyle 描边颜色。默认为黑色 lineWidth 线条宽度。默认为 1 像素 lineCap 线条末端的样式,可选值有 butt、round 和 square。默认为 butt lineJoin 线条相交处的样式,可选值有 miter、round 和 bevel。默认为 miter。 miterLimit 线条相交处的最大斜接长度。默认为 10 像素 globalAlpha 绘制的透明度。默认为 1(不透明) shadowColor 阴影颜色。默认为黑色 shadowOffsetX、shadowOffsetY 阴影的偏移量和模糊程度 shadowBlur 模糊程度 优缺点 优点 基于矢量绘制,图形可以自由缩放而不失真 具有良好的跨浏览器兼容性 通过 JavaScript 动态生成图形,可以更加灵活地控制图形的显示和交互 缺点 无法处理复杂的交互事件、不支持跨域图像等 使用实例 这个实例中,我们使用了一个 HTML5 的 Canvas 元素,它的宽度和高度分别为 200 像素。在 JavaScript 中,我们获取了这个元素,并通过 getContext 方法获取了它的上下文对象。然后,我们使用 fillStyle 属性设置了矩形的填充颜色为红色,并使用 fillRect 方法绘制了一个起点坐标为 (50,50),宽度为 100 像素,高度为 100 像素的矩形。 ...

October 9, 2024

v-if/v-show

v-show原理 export const vShow: ObjectDirective<VShowElement> = { beforeMount(el, { value }, { transition }) { el._vod = el.style.display === 'none' ? '' : el.style.display if (transition && value) { transition.beforeEnter(el) } else { setDisplay(el, value) } }, mounted(el, { value }, { transition }) { if (transition && value) { transition.enter(el) } }, updated(el, { value, oldValue }, { transition }) { // ... }, beforeUnmount(el, { value }) { setDisplay(el, value) } } 有transition就执行transition,没有transition就直接设置display。不管初始条件是什么,元素总是会被渲染。 v-if原理 export const transformIf = createStructuralDirectiveTransform( /^(if|else|else-if)$/, (node, dir, context) => { return processIf(node, dir, context, (ifNode, branch, isRoot) => { // ... return () => { if (isRoot) { ifNode.codegenNode = createCodegenNodeForBranch( branch, key, context ) as IfConditionalExpression } else { // attach this branch's codegen node to the v-if root. const parentCondition = getParentCondition(ifNode.codegenNode!) parentCondition.alternate = createCodegenNodeForBranch( branch, key + ifNode.branches.length - 1, context ) } } }) } ) 返回一个node节点,render函数通过表达式的值来决定是否生成DOM ...

October 4, 2024