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

移动端适配

导读 移动端适配,是我们在开发中经常会遇到的,这里面可能会遇到非常多的问题: 1px问题 UI图完美适配方案 iPhoneX适配方案 横屏适配 高清屏图片模糊问题 … 上面这些问题可能我们在开发中已经知道如何解决,但是问题产生的原理,以及解决方案的原理可能会模糊不清。在解决这些问题的过程中,我们往往会遇到非常多的概念:像素、分辨率、PPI、DPI、DP、DIP、DPR、视口等等,你真的能分清这些概念的意义吗? 本文将从移动端适配的基础概念出发,探究移动端适配各种问题的解决方案和实现原理。 一、英寸 一般用英寸描述屏幕的物理大小,如电脑显示器的17、22,手机显示器的4.8、5.7等使用的单位都是英寸。 需要注意,上面的尺寸都是屏幕对角线的长度: 英寸(inch,缩写为in)在荷兰语中的本意是大拇指,一英寸就是指甲底部普通人拇指的宽度。 英寸和厘米的换算:1英寸 = 2.54 厘米 二、分辨率 2.1 像素 像素即一个小方块,它具有特定的位置和颜色。 图片、电子屏幕(手机、电脑)就是由无数个具有特定颜色和特定位置的小方块拼接而成。 像素可以作为图片或电子屏幕的最小组成单位。 下面我们使用sketch打开一张图片: 将这些图片放大即可看到这些像素点: 通常我们所说的分辨率有两种,屏幕分辨率和图像分辨率。 2.2 屏幕分辨率 屏幕分辨率指一个屏幕具体由多少个像素点组成。 下面是apple的官网上对手机分辨率的描述: iPhone XS Max 和 iPhone SE的分辨率分别为2688 x 1242和1136 x 640。这表示手机分别在垂直和水平上所具有的像素点数。 当然分辨率高不代表屏幕就清晰,屏幕的清晰程度还与尺寸有关。 2.3 图像分辨率 我们通常说的图片分辨率其实是指图片含有的像素数,比如一张图片的分辨率为800 x 400。这表示图片分别在垂直和水平上所具有的像素点数为800和400。 同一尺寸的图片,分辨率越高,图片越清晰。 2.4 PPI PPI(Pixel Per Inch):每英寸包括的像素数。 PPI可以用于描述屏幕的清晰度以及一张图片的质量。 使用PPI描述图片时,PPI越高,图片质量越高,使用PPI描述屏幕时,PPI越高,屏幕越清晰。 在上面描述手机分辨率的图片中,我们可以看到:iPhone XS Max 和 iPhone SE的PPI分别为458和326,这足以证明前者的屏幕更清晰。 由于手机尺寸为手机对角线的长度,我们通常使用如下的方法计算PPI: iPhone 6的PPI为 ...

November 9, 2024

前端存储

什么是前端存储 开门见山。前端存储就是通过前端技术来存储一段信息,然后在同源下的不同页面中都可以获取到已存储信息的一种策略。 前端存储的作用 方便网页的加载,避免了在发送请求收到响应前页面的空白期 可以在非强制性要求实时更新时减少向服务端的请求,加快渲染速度 在网络不佳或无网时仍有离线数据可以查看 有哪些前端存储方案 如图,一共有5种前端存储方案。大致可以分为3类: Cookie WebStorage:LocalStorage、SessionStorage 数据库存储:IndexedDB、WebSQL 先做一个简单对比: 下面我们详细介绍。 Cookie Cookie 的工作流程: Cookie 的构成: 域、路径、失效时间和安全性都是服务器给浏览器的指示,它们不会随着请求发送给服务器,发送给服务器的只有名称与值的键值对。 Cookie 的生命周期: 如果设定了 Cookie 的过期时间,那么 Cookie 会在到期时自动失效 如果没有设定过期时间,那么 Cookie 就是 session 级别的,即浏览器关闭时 Cookie 自动消失 Cookie 的优缺点: 优点: 可以控制过期时间,不会永久有效,有一定的安全保障 可进行扩展,可跨域共享 通过加密与安全传输技术,可以减少 Cookie 被破解的可能性 有较高的兼容性 缺点: 存储大小最多4KB 存储数量根据浏览器或浏览器版本的不同而不同,并且每个域最多20条 请求头上的数据容易被拦截攻击 存储的数据只能是字符串类型 操作 Cookie: // 设置 Cookie // Cookie 值必须是字符串类型,并且不支持分号、逗号以及空格, // 所以有时需要先使用 encodeURIComponent() 进行编码,或者使用 JSON.stringify() 进行序列化 document.cookie = '键1=值1;键2=值2;键n=值n'; // 读取 Cookie // 有时需要使用 decodeURIComponent() 或者 JSON.parse() document.cookie // 修改 Cookie // 如果键不存在,就新增;否则就修改 document.cookie = '已经存储过的键=新值'; // 删除 Cookie document.cookie = '要删除的键=任意值;max-age=0'; WebStorage WebStorage 是 HTML5 新加的,WebStorage(Web 存储) 分为 LocalStorage(本地存储) 和 SessionStorage(会话存储)。 ...

October 1, 2024

值类型和引用类型的区别

定义 值类型 值类型是指变量直接存储自身字段或描述信息的类型。值类型具有值语义,当将一个值类型变量赋值给另一个变量时,语义上会得到一份独立的值;底层是否立即复制完整数据,取决于编译器优化和写时拷贝等实现。 引用类型 引用类型是指变量存储的是指向数据在内存中位置的引用(指针)。当将一个引用类型变量赋值给另一个变量时,两个变量指向同一块内存区域。 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

优化性能

前言 随着互联网发展至今,对于网站来说,性能显的越来越重要了,CSS作为页面渲染和内容展现的重要环节,影响着用户对整个网站的第一体验。所以,我们需要重视与CSS相关的性能优化。 项目开发初期我们可能因为各种原因(很大一部分原因是因为项目工期,产品往往把项目上线时间卡的死死的,根本不听你说的什么性能优化),怎么写的舒服就怎么来,对于性能优化我们常常在项目完成时才去考虑,经常被推迟到项目的末期,甚至到暴露出严重的性能问题时才进行性能优化。 为了更多地避免这一情况,首先要重视起性能优化相关的工作,将其贯穿到整个产品设计与开发中。其次,就是了解性能相关的内容,在项目开发过程中,自然而然地进行性能优化。 css渲染规则 想要优化CSS的性能,我们首先需要了解CSS的渲染规则,CSS选择器是从右向左进行匹配的 来看个例子🌰: .nav h3 a{font-size: 14px;} 渲染过程大概是:首先找到所有的a,沿着a的父元素查找h3,然后再沿着h3,查找.nav。中途找到了符合匹配规则的节点就加入结果集。如果找到根元素html都没有匹配,则不再遍历这条路径,从下一个a开始重复这个查找匹配(只要页面上有多个最右节点为a)。 Tips:为什么CSS选择器是从右向左匹配的? CSS中更多的选择器是不会匹配的,所以在考虑性能问题时,需要考虑的是如何在选择器不匹配时提升效率。从右向左匹配就是为了达成这一目的的,通过这一策略能够使得CSS选择器在不匹配的时候效率更高。这样想来,在匹配时多耗费一些性能也能够想的通了。 内联首屏关键CSS(Critical CSS) 性能优化中有一个重要的指标——首次有效绘制(First Meaningful Paint,简称FMP)即指页面的首要内容(primary content)出现在屏幕上的时间。这一指标影响用户看到页面前所需等待的时间,而 内联首屏关键CSS(即Critical CSS,可以称之为首屏关键CSS) 能减少这一时间。 很多人都喜欢通过link标签引用外部CSS文件。但需要知道的是,将CSS直接内联到HTML文档中能使CSS更快速地下载。而使用外部CSS文件时,需要在HTML文档下载完成后才知道所要引用的CSS文件,然后才下载它们。所以说,内联CSS能够使浏览器开始页面渲染的时间提前,因为在HTML下载完成之后就能渲染了。 但是我们不应该将所有的CSS都内联在HTML文档中,因为[初始拥塞窗口]存在限制(TCP相关概念,通常是 14.6kB,压缩后大小),如果内联CSS后的文件超出了这一限制,系统就需要在服务器和浏览器之间进行更多次的往返,这样并不能提前页面渲染时间。因此,我们应当只将渲染首屏内容所需的关键CSS内联到HTML中。 ⚠️还有一点需要注意的是内联CSS没有缓存,每次都会随HTML的加载而重新下载,但我们将内联首屏关键CSS控制在 14.6kB以内,它对性能优化还是起到正向作用的。(凡事有利也有弊) 异步加载非首屏CSS 我们需要知道两点内容:(具体可以看我之前的文章:这些浏览器面试题,看看你能回答几个?) CSS不会阻塞DOM的解析,但会阻塞DOM的渲染 CSS会阻塞JS执行,但不会阻塞JS文件的下载 由于CSS会阻塞DOM的渲染,所以我们将首屏关键CSS内联后,剩余的非首屏CSS内容可以使用外部CSS,并且异步加载,防止非首屏CSS内容阻塞页面的渲染。 CSS异步加载方式 第一种方法是动态创建 // 创建link标签 const myCSS = document.createElement( "link" ); myCSS.rel = "stylesheet"; myCSS.href = "mystyles.css"; // 插入到header的最后位置 document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling ); 第二种方法是将link元素的media属性设置为用户浏览器不匹配的媒体类型(或媒体查询) 对浏览器来说,如果样式表不适用于当前媒体类型,其优先级会被放低,会在不阻塞页面渲染的情况下再进行下载。在首屏文件加载完成之后,将media的值设为screen或all,从而让浏览器开始解析CSS。 <link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'"> 第三种方法是通过rel属性将link元素标记为alternate可选样式表 <link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'"> 第四种方法是使用rel=preload来异步加载CSS <link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'"> 注意,as是必须的。忽略as属性,或者错误的as属性会使preload等同于XHR请求,浏览器不知道加载的是什么内容,因此此类资源加载优先级会非常低。as的可选值可以参考上述标准文档。 看起来,rel="preload"的用法和上面两种没什么区别,都是通过更改某些属性,使得浏览器异步加载CSS文件但不解析,直到加载完成并将修改还原,然后开始解析。 但是它们之间其实有一个很重要的不同点,那就是使用preload,比使用不匹配的media方法能够更早地开始加载CSS。所以尽管这一标准的支持度还不完善,仍建议优先使用该方法。 CSS文件压缩 这应该是最容易想到的一个方法了,通过压缩CSS文件大小来提高页面加载速度。现在的构建工具,如webpack、gulp/grunt、rollup等也都支持CSS压缩功能。压缩后的文件能够明显减小,可以大大降低了浏览器的加载时间。 CSS层级嵌套最好不要超过3层 一般情况下,元素的嵌套层级不能超过3级,过度的嵌套会导致代码变得臃肿,沉余,复杂。导致css文件体积变大,造成性能浪费,影响渲染的速度!而且过于依赖HTML文档结构。这样的css样式,维护起来,极度麻烦,如果以后要修改样式,可能要使用!important覆盖。尽量保持简单,不要使用嵌套过多过于复杂的选择器。 删除无用CSS代码 一般情况下,会存在这两种无用的CSS代码:一种是不同元素或者其他情况下的重复代码,一种是整个页面内没有生效的CSS代码。 对于前者,在编写的代码时候,我们应该尽可能地提取公共类,减少重复。对于后者,在不同开发者进行代码维护的过程中,总会产生不再使用的CSS的代码,当然一个人编写时也有可能出现这一问题。而这些无用的CSS代码不仅会增加浏览器的下载量,还会增加浏览器的解析时间,这对性能来说是很大的消耗。所以我们需要找到并去除这些无用代码。 那么我们如何知道哪些CSS代码是无用代码呢? 谷歌的Chrome浏览器就有这种开箱即用的功能。只需转到查看>开发人员>开发人员工具,并在最近的版本中打开Sources选项卡,然后打开命令菜单。然后,点击Coverage,在Coverage analysis窗口中高亮显示当前页面上未使用的代码。 慎用*通配符 我们有时候可能会写下面这种代码来消除一些标签的默认样式或统一浏览器对标签渲染的差异化: *{ margin:0; padding:0; } 这样虽然代码量少,但它的性能可不是最佳的,我们最好还是写对应的标签选择器: ...

October 10, 2024

WebWorker

前言 先来聊聊单线程的Javascript 众所周知,js最初设计是运行在浏览器中的,为了防止多个线程同时操作DOM,带来渲染冲突问题,所以js执行器被设计成单线程。但随着前端技术的发展,js能力远不止如此,当我们遇到需要大量计算的场景时(比如图像处理、视频解码等),js线程往往会被长时间阻塞,甚至造成页面卡顿,影响用户体验。为了解决单线程带来的这一弊端,Web Worker 应运而生。 1. Web Worker 1.1 Web Worker 是什么 Web Worker 是 HTML5 标准的一部分,这一规范定义了一套 API,允许我们在 js 主线程之外开辟新的 Worker 线程,并将一段 js 脚本运行其中,它赋予了开发者利用 js 操作多线程的能力。 因为是独立的线程,Worker 线程与 js 主线程能够同时运行,互不阻塞。所以,在我们有大量运算任务时,可以把运算任务交给 Worker 线程去处理,当 Worker 线程计算完成,再把结果返回给 js 主线程。这样,js 主线程只用专注处理业务逻辑,不用耗费过多时间去处理大量复杂计算,从而减少了阻塞时间,也提高了运行效率,页面流畅度和用户体验自然而然也提高了。 1.2 Web Worker 能干些什么 虽然 Worker 线程是在浏览器环境中被唤起,但是它与当前页面窗口运行在不同的全局上下文中,我们常用的顶层对象 window,以及 parent 对象在 Worker 线程上下文中是不可用的。另外,在 Worker 线程上下文中,操作 DOM 的行为也是不可行的,document对象也不存在。但是,location和navigator对象可以以可读方式访问。除此之外,绝大多数 Window 对象上的方法和属性,都被共享到 Worker 上下文全局对象 WorkerGlobalScope 中。同样,Worker 线程上下文也存在一个顶级对象 self。 详细信息请参考:Functions and classes available to Web Workers 2. Web Worker 使用 2.1 创建 worker 创建 worker 只需要通过 new 调用 Worker() 构造函数即可,它接收两个参数 ...

October 1, 2024

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

新变化

CSS 发展史 上图是20 世纪 90 年万维网刚出现时,由于当时并没有可以装饰网页的方法,原始的 Web 页面呈现的效果,随着 CSS(层叠样式表,Cascading Style Sheets)的诞生与发展,Web页面的样式有了翻天覆地的变化。 CSS的发布过程 CSS1.0 19912月W3C发布了第一个有关样式的标准CSS1.0。这个版本中,已经包含了的相关font的相关属性、颜色与背景的相关属性、文字的相关属性、box的相关属性等。 CSS2.0 1985年5月,CSS2.0正式推出。这个版本推荐的是内容和表现效果分离的方式,并开始使用样式表结构。 CSS2.1 2004年2月,CSS2.1正式推出。它在CSS2.0的基础上略微做了改动,删除了许多不被浏览器支持的属性 CSS3 从2011年开始CSS被分为多个模块单独升级。这些模块统称为CSS3 包含有: CSS 选择器level3 CSS 媒体查询level3 CSS color level3 CSS4 设计中 CSS 标准的制定大概有下面几个步骤 下面主要介绍的是与我们日常工作有关联的一些新特性,其中一些已经在主流浏览器中得到了支持,还有一些特性还在草案阶段,可能还会有一些变化。 CSS 伪类选择器 is()和where() 在编写css时,有时会需要使用很长的选择器列表来定位具有相同样式的子元素,比如对所有H元素下的span设置为block,之前会这么写: 这样写选择器看起来很长,可读性差。 此时,可以使用is()来提高易读性,同时避免使用长选择器 浏览器支持: :is() Chrome 88/ Firefox 78/ Edge 88 /Safari 14 :where() Chrome 88/ Firefox 78/ Edge 88 /Safari 14 注意: is()和where()在使用方法上和其他的伪类选择器一样,可以任意组合排列。 is()和where()的功能大体上是一样的,他们的差异性体现在选择器的权重上 where()选择器没有权重,也就是权重是0 is()选择器的权重由它的选择器列表中的最搞权重决定 规则示例: ...

November 9, 2024

Service Worker

引言 我们经常可以听到 Worker 这个名词,比如本文要介绍的 ServiceWorker 后面就带着一个 Worker 的单词,那么 Worker 实际上指的是什么呢? 在介绍 Worker 的定义之前,先来回忆一下浏览器的渲染原理。 在一个 tab 打开的时候,浏览器会给这个 tab 创建一个新的渲染进程(renderer process),如下图: 图1:Chrome 浏览器的多进程架构 图片来源:developer.chrome.com/blog/inside… 在每一个渲染进程中,都会有一个主线程(Main Thread),负责 JavaScript 的执行以及浏览器的渲染(JavaScript 的执行与 UI 渲染是一个互相阻塞的流程)。 图2:浏览器一帧的剖析 图片来源:aerotwist.com/blog/the-an… 如果 JavaScript 执行时间过久(比如超过 33.33 ms),那么一帧内留给 UI 渲染的时间不多,如果这时候网页有正在执行的动画,那么用户就会感受到卡顿。 10 FPS:能够达成基本的视觉残留(参考:定格动画至少需要 12 帧每秒以上) 30 FPS以下:让人感觉到明显的卡顿和不适感 30-50 FPS:因人而异 50-60 FPS:流畅 一个 Worker,指的是一个可以在后台独立执行 JavaScript 脚本。它存在于一个单独的 worker 线程,即使执行一些长任务也不会阻塞主线程响应用户操作(如鼠标点击、动画等)。 另外 Worker 也指使用 new Worker() 构造函数创建的一个对象,可以用在主线程与 worker 线程的通信 图3:worker 线程独立于主线程之外 图片来源:developer.chrome.com/blog/inside… Worker 与 主线程之间可以通过 postMessage 进行通信: ...

November 8, 2024

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