普通视图
Command SwiftCompile failed with a nonzero exit code Command SwiftGeneratePch em
Flutter 运行新建项目也报错?
先看报错提示信息:
Error (Xcode): Target aot_assembly_release failed: Exception: release/profile builds are only supported for physical devices. attempted to
build for simulator.
环境
在Mac
中,通过 flutter create daily_note
新建一个项目,在VS Code
中添加 launch.json
文件,内容如下:
{
"configurations": [
{
"name": "daily_note (debug mode)",
"request": "launch",
"type": "dart"
}
]
}
开始运行,结果出现开头的错误,非常的莫名其妙。
解决过程
首先运行 flutter doctor
[✓] Flutter (Channel stable, 3.29.0, on macOS 15.4 24E5228e darwin-arm64, locale zh-Hans-CN)
[!] Android toolchain - develop for Android devices (Android SDK version 35.0.1)
! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses
[✓] Xcode - develop for iOS and macOS (Xcode 16.0)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.1)
[✓] VS Code (version 1.98.2)
[!] Proxy Configuration
! NO_PROXY is not set
[✓] Connected device (4 available)
[✓] Network resources
! Doctor found issues in 2 categories.
在 iOS
上运行,所以 Xcode
没问题。
接着问 Trae
,直接把报错信息和配置文件发给它。然后回复结果是让我增加一行,用于指定 flutterMode
{
"configurations": [
{
"name": "daily_note (debug mode)",
"request": "launch",
"type": "dart",
"flutterMode": "debug"
}
]
}
运行还是同样的报错。于是继续把launch.json
发给Trae
,反复尝试了还是不行,有点快崩溃了。
回想之前遇到真机运行拔线就退出程序的问题,当时是通过设置FLUTTER_BUILD_MODE
解决的,于是查看现在的新工程有没有这个设置。结果,真的没有。然后在 Xcode-Build Setting-User Defined
中添加 FLUTTER_BUILD_MODE=Debug
,重新运行后可以了。
总结
Trae
很好,但不是万能的,有时候还得靠自己。想要掌握好新编程模式下的工具,还需要努力。
苹果 iOS 19 曝光,你的iPhone 还能再战一年?
❝ App Store审核问题或3.2f咨询,请公众号留言。不免费,不闲聊。「iOS研究院」 ❞
前言
近期,关于苹果iOS 19的升级机型及其功能特性备受关注。据多方爆料和开发者信息透露,iOS 19将继续支持多款老款设备,但由于硬件性能的限制,部分重磅功能可能会受到限制。以下是iOS 19升级机型及核心功能的详细解析:
一、支持机型
根据最新曝光的信息,iOS 19的升级机型列表与iOS 18基本保持一致,支持的设备范围,具体包括:
二、iOS19
iOS 19的UI界面和图标、菜单等将全面向VisionOS靠拢。具体来说,原本棱角分明的APP图标将变成圆角设计,同时还会加入半透明效果。
三、WWDC 25
苹果正式宣布,年度开发者大会将于2025年6月10日至14日(北京时间)举行。此次大会将延续自2020年以来的线上举办模式。苹果表示,WWDC 25将聚焦于Apple软件的最新发展,面向全球开发者免费开放,并提供与Apple专家交流的机会,让更多开发者深入了解新的框架、工具和功能等内容。
SwiftUI 字体系统详解
在 iOS 应用开发中,文本排版是用户界面设计的关键元素之一。SwiftUI 提供了一套丰富而灵活的字体系统,让开发者能够轻松创建既美观又符合系统设计规范的文本样式。本文将深入探讨 SwiftUI 的字体系统,以及如何有效地使用它们来提升应用的用户体验。
SwiftUI 字体系统概述
SwiftUI 的字体系统主要分为三类:系统文本样式、自定义系统字体和自定义字体。这种分层设计使开发者既可以快速应用预设样式,又能在需要时进行精细的自定义。
系统文本样式(Text Style)
系统文本样式是 SwiftUI 提供的预定义字体样式,它们会根据用户在系统设置中的偏好自动调整大小,支持动态文字大小功能(Dynamic Type)。
Text("大标题").font(.largeTitle)
Text("标题").font(.title)
Text("正文内容").font(.body)
Text("图片说明").font(.caption)
以下是主要的系统文本样式及其用途:
样式 | 用途 |
---|---|
.largeTitle |
最大的标题样式,通常用于页面的主标题 |
.title |
标准标题,用于重要区域的标题 |
.title2 |
二级标题,比 title 稍小 |
.title3 |
三级标题,比 title2 稍小 |
.headline |
段落标题,通常使用粗体 |
.subheadline |
副标题,比 headline 小 |
.body |
正文样式,是默认的字体大小 |
.callout |
标注文本,稍小于正文 |
.caption |
说明文字,小字体,常用于图片说明或次要信息 |
.caption2 |
更小的说明文字 |
.footnote |
脚注样式,比正文小 |
.preformatted |
等宽字体,适用于代码显示(iOS 17+) |
自定义系统字体
当预设样式无法满足需求时,可以使用 .system()
方法创建自定义系统字体:
// 基本自定义
Text("自定义大小").font(.system(size: 16))
// 自定义大小、粗细和设计风格
Text("圆角粗体").font(.system(size: 16, weight: .bold, design: .rounded))
字体权重选项
-
.ultraLight
- 超细 -
.thin
- 细体 -
.light
- 轻体 -
.regular
- 常规 -
.medium
- 中等 -
.semibold
- 半粗体 -
.bold
- 粗体 -
.heavy
- 重体 -
.black
- 黑体
设计风格选项
-
.default
- 默认系统字体 -
.serif
- 衬线字体 -
.rounded
- 圆角字体 -
.monospaced
- 等宽字体 -
.italic
- 斜体(iOS 16+)
自定义字体
对于品牌专属字体或特殊设计需求,SwiftUI 支持使用自定义字体:
// 基本自定义字体
Text("品牌专属字体").font(.custom("Brand Font", size: 14))
// 支持动态文字大小的自定义字体
Text("可调整大小的自定义字体").font(.custom("Brand Font", size: 14, relativeTo: .body))
使用 relativeTo
参数可以确保自定义字体也能响应系统的动态文字大小设置。
字体修饰符
除了设置整体字体样式,SwiftUI 还提供了单独修改字体特定属性的修饰符:
Text("粗体文本").fontWeight(.bold)
Text("斜体文本").italic()
Text("小型大写字母").textCase(.uppercase)
Text("下划线文本").underline()
Text("删除线文本").strikethrough()
最佳实践
- 优先使用系统文本样式:自动适应用户偏好设置,提升无障碍性
- 保持层次结构:使用不同字体样式建立清晰视觉层次
- 慎用自定义字体:确保支持动态文字大小
- 考虑本地化:不同语言可能需要不同的文本空间
- 测试极端情况:在最大和最小文字大小设置下测试界面
Swift 6.1 新特性
Swift 6.1 内置于 Xcode 16.3,这是 Swift 6 之后的首个小版本更新,新特性很少。
尾随逗号
元组、函数的参数、闭包捕获列表以及字符串插值等都可以像数组一样,在最后一个元素的后面添加,
,以便轻松地追加、删除、重新排列或者注释最后一个元素。
// 元组
(404, "Not Found",)
// 函数的参数
func sum(num1: Int, num2: Int,) -> Int {
num1 + num2
}
var vehicle = "Car"
// 闭包捕获列表
let closure = { [vehicle,] in
print("Vehicle:", vehicle)
}
// 字符串插值
"This is a \(vehicle,)"
混合开发
介绍
- 增加新的关键字
@implementation
,配合@objc
可以为 Objective-C 导入的.h
声明提供实现。 - 实现方式:在 Swift 中扩展 Objective-C 类,然后通过
@objc @implementation
实现属性与方法以替换 Objective-C 的@implementation
。
实现
- 新建一个基于 Swift 语言的 iOS 项目。
- 创建一个 Objective-C 类,此时会弹出一个提醒对话框(添加这个文件会创建一个 Objective-C 与 Swift 的混合项目,你是否希望 Xcode 自动配置一个桥接头文件来让 2 种语言的类文件相互可见?),点击
Create Bridging Header
。 - 项目中多出 3 个文件,分别为创建的 Objective-C 类文件(
.h
与.m
)与 Bridging Header 文件,修改 Objective-C 类文件如下。
// .h文件
@interface Person: NSObject
@property(nonatomic, copy) NSString *name;
-(void)eat;
@end
// .m文件
// @implementation Person
// @end
- 在 Bridging Header 文件中通过
#import "类名.h"
导入所有需要使用的 Objective-C 类的头文件。 - 在 Swift 中实现并且调用 Objective-C。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 调用
let person = Person()
person.name = "zhangsan"
person.eat() // zhangsan吃饭了
}
}
// MARK: - @objc @implementation extension Objective-C类
@objc @implementation extension Person {
// 实现属性
var name: String?
// 实现方法
func eat() {
print("\(name ?? "")吃饭了")
}
}
注意:一旦在 Swift 进行了实现,Objective-C 中的
@implementation
不需要再实现,否则会报2 duplicate symbols
的编译错误。
并发编程
- actor 允许在属性与函数上使用
nonisolated
,表示该 API 可以在任何并发上下文中安全调用。Swift 6.1 将nonisolated
支持到了类型与扩展,这样其内的所有属性与方法不需要再单独添加nonisolated
。
// 结构体
nonisolated struct Station {
}
class Train {
}
// 扩展
nonisolated extension Train {
}
-
withTaskGroup()
与withThrowingTaskGroup()
的闭包可以推断出子任务结果类型。
// Swift6.1之前
await withTaskGroup(of: Int.self) { group in
...
}
await withThrowingTaskGroup(of: String.self) { group in
...
}
// Swift6.1之后
await withTaskGroup { group in
...
}
await withThrowingTaskGroup { group in
...
}
Combine知识点switchToLatest
使用场景
- 搜索框自动补全(避免发送多个请求,只保留最后一个)
- 异步任务切换(如下载任务,视频播放切换,当用户连续点击了多个视频时,只播放最后一个,取消前几个的请求)
- 网络请求取消(当用户发起新的请求时,自动取消旧的请求)
- 用户交互事件 (点击按钮触发任务,只执行最新的点击任务)
switchToLatest解决的核心问题是:
- 自动取消旧的Publisher,只保留最新的Publisher
- 适用于“每次订阅都会创建新的Publisher”的情况
switchToLatest的定义
func switchToLatest()-> Publishers.SwitchToLatest<Upstream.Output, Upstream>
这里的Upstream指的是Publisher内部产生的Publisher,switchToLatest会从中只取最新的Publisher。
核心特点
- 适合于Publisher<Publisher<Output, Failure>, Failure>,即一个Publisher产生多个Publisher的情况
- 当新的Publiser出现时,会自动取消之前的Publisher,只执行最新的publisher。
- 只会订阅最新的Publiser并将其结果发送给下游
例1,防止重复的网络请求
场景
用户在搜索框中输入关键词时,每次输入都会触发新的API请求,我门希望只进行最新的一次请求,之前的取消。
mport Combine
import Foundation
// 模拟搜索 API
func searchAPI(query: String) -> AnyPublisher<String, Never> {
Just("搜索结果: \(query)")
.delay(for: .seconds(1), scheduler: DispatchQueue.global()) // 模拟网络请求延迟
.eraseToAnyPublisher()
}
// 用户输入的搜索关键词
let searchText = PassthroughSubject<String, Never>()
// 处理搜索请求
let cancellable = searchText
.map { query in searchAPI(query: query) } // 每次输入都会生成一个新的 Publisher
.switchToLatest() // 只保留最新的 Publisher,取消旧的请求
.sink { result in
print(result) // 只会输出最新搜索的结果
}
// 模拟用户输入
searchText.send("Swift") // 请求 1
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
searchText.send("SwiftUI") // 请求 2,取消 "Swift"
}
DispatchQueue.global().asyncAfter(deadline: .now() + 1.5) {
searchText.send("Combine") // 请求 3,取消 "SwiftUI"
}
// 输出结果
“搜索结果: Combine”
代码解析
- searchText是一个PassthroughSubject,用于模拟用户输入的关键词
- map { query in searchAPI(query: query)} 每次输入都会调用searchAPI生成新的Publisher(异步网络请求)
- swiftToLatest() 只保留最新的Publisher,如果新的请求到来,会取消旧的请求
- slink订阅最终的结果,只会收到最新的搜索返回的值
例2, 点击按钮触发异步任务
场景:
用户点击下载按钮时,每次点击都睡触发新的下载任务,我们希望只有最后一次的任务执行,之前的任务自动取消。
import Combine
import Foundation
// 模拟下载任务
func downloadTask(_ id: Int) -> AnyPublisher<String, Never> {
Just("任务 \(id) 下载完成")
.delay(for: .seconds(2), scheduler: DispatchQueue.global()) // 模拟下载延迟
.eraseToAnyPublisher()
}
// 用户点击按钮的触发器
let downloadButtonTap = PassthroughSubject<Int, Never>()
// 处理下载任务
let cancellable = downloadButtonTap
.map { id in downloadTask(id) } // 每次点击都会生成新的下载任务
.switchToLatest() // 只执行最新的任务,旧任务会被取消
.sink { result in
print(result) // 只会输出最新任务的完成结果
}
// 模拟用户点击
downloadButtonTap.send(1) // 任务 1
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
downloadButtonTap.send(2) // 任务 2,取消任务 1
}
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
downloadButtonTap.send(3) // 任务 3,取消任务 2
}
// 输出
// 任务3 下载完成
任务1 和 2 在完成之前被取消,所以他们的slink不会接收到任何输出
得物 iOS 启动优化之 Building Closure
得物一直重视用户体验,尤其是启动时长这一重要指标。在近期的启动时长跟进中,我们发现了在BuildingClosure 阶段的一个优化方式,成功的帮助我们降低了 1/5 的 BuildingClosure 阶段的启动耗时。Building Closure 并非工程的编译阶段(虽然它有一个building),Building Closure 是应用初次启动时会经历的阶段,因此它会影响应用的启动时长。
单就BuildingClosure阶段而言,我们观察到该阶段其中一个函数从 480ms 暴增到 1200ms 左右(PC 电脑端运行 dyld 调试统计耗时数据),我们通过优化,将耗时从1200ms降低到110ms。即使相比最开始的情况,也相当于从480ms降低到了110ms,由此可见Building Closure 优化是应用进行启动优化必不可少的一个重要手段。因此在这里我们也和各位读者进行分享,期望能够对各自项目有所帮助。
一、神秘的 BuildingClosure
启动优化的技术、实现方案业界有不少的文章可以参考学习,这里不再额外赘述。我们来探索下启动过程中非常神秘的 BuildingClosure。
BuildingClosure 是在 System Interface Initialization 阶段 dyld 生成的,并且我们也无法做任何的干预,另外相关的剖析文章相对较少,所以说 BuildingClosure 较为神秘,也是实至名归。
BuildingClosure 是由 dyld 在应用启动阶段执行的,所以想要了解 BuildingClosure 还是要从 dyld 开始了解。
1.1 dyld && BuildingClosure
Dyld 源码可以在 Apple GitHub 上查阅 github.com/apple-oss-d…
相信大家都应该了解过,BuildingClosure 是在 iOS 13 引入进来的,对应的 dyld 为 dyld3,目的是为了减少启动环节符号查找、Rebase、Bind 的耗时。
核心技术逻辑是将重复的启动工作只做一次,在 App 首次启动、版本更新、手机重启之后的这次启动过程中,将相关信息缓存到 Library/Caches/com.app.dyld/xx.dyld 文件中,App 在下次启动时直接使用缓存好的信息,进而优化二次启动的速度。
在 iOS 15 Dyld4 中更是引入了 SwiftConformance,进一步解决了运行时 Swift 中的类型、协议检查的耗时。
以上优化,我们都无需做任何工作即可享受 dyld 带来的启动速度的优化,可以感受到 Apple 的开发人员也在关心启动速度并为之做了大量的工作。
1.2 BuildingClosure 非常耗时
我们通过 instrument 观测到 BuildingClosure 的耗时占据了启动耗时将近 1/3 的时间。
虽然说,BuildingClosure 只会在首次启动、版本更新、手机重启的第一次启动生成和耗时,但是对用户的体验影响是非常之大的。
1.3 BuildingClosure 文件解析
我们通过对 dyld 的编译和搭建模拟手机环境,成功模拟器了 dyld 加载可执行文件的过程,也就成功解析了 BuildingClosure 文件。BuildingClosure 文件数据格式如下(数据格式、注释仅供参考,并非全部的数据格式):
BuildingClosure 文件内部结构(数据格式、注释仅供参考)
其中占用比较大的部分主要为 Loader-selectorReferencesFixupsSize SwiftTypeConformance objcSelector objcClass
二、离奇的启动耗时暴增事件
如上,我们已经对 BuildingClosure 有了基本的了解和对 dyld 的执行过程有了一定的了解。但是这份宁静在某一天突然被打破。
2.1 启动耗时暴增 200ms
在我们一个新版本开发过程中,例行对启动耗时进行跟踪测试,但是发现新版本启动耗时暴增 200ms,可以说是灾难级别的事情。
我们开始对最近的出包做了基本的耗时统计,方式为基于 instrument,统计出来启动各个阶段的耗时数据。经过对比,可以明显观测到,200ms 耗时的增加表现在 BuildingClosure 这个环节。
但是 BuildingClosure 耗时的增加既不是阶梯式增加,也不是线性增加,并且只在新版本有增加。在排除相关因素(动态库、工程配置、打包脚本、编译环境)之后,仍然没有定位明确的原因。
在以上定位工作之后,最终确定耗时确实在 dyld 的 BuildingClosure 阶段耗时,并且怀疑可能是某些代码触发了 Dyld 的隐藏彩蛋。所以我们开始了对 BuildingClosure 更深一步的研究。
2.2 BuildingClosure 耗时异常变化定位
通过使用 Instrument 对 System Interface Initialization 阶段进行堆栈分析,最终发现了耗时最高的函数:dyld4::PrebuiltObjC::generateHashTables(dyld4::RuntimeState&)
在对比了新老版本数据,耗时变化差异的函数也是此函数,我们简称为 generateHashTables。这样使得我们更加确定耗时为 dyld 过程中的 BuildingClosure 阶段。
使用 Instrument 分析 BuildingClosure 阶段耗时
三、启动优化新秘境
在发现 BuildingClosure 生成过程中耗时占比非常大,并且有异常时,起初并没有意识到有什么问题,因为这是 dyld 内的代码,并未感觉会有什么问题。但是一切都指向了该函数,于是开始撸起袖子看代码。
从代码中可以看到,此处是为了生成 BuildingClosure 中 objcSelector objcClass objcProtocol 这三个部分的 HashTable(可以参考上面的 【BuildingClosure 文件解析】部分)。
拿起 dyld 开始对耗时异常版本的可执行文件进行调试,通过对该函数和内部实现的代码逻辑阅读,以及增加耗时信息打印。最终确定,耗时的代码在 make_perfect 这个函数中,这个函数是对【输入的字符串列表】生成一个【完美 Hash 表】。
void PrebuiltObjC::generateHashTables(RuntimeState& state)
{
// Write out the class table
writeObjCDataStructHashTable(state, PrebuiltObjC::ObjCStructKind::classes, objcImages, classesHashTable, duplicateSharedCacheClassMap, classMap);
// Write out the protocol table
writeObjCDataStructHashTable(state, PrebuiltObjC::ObjCStructKind::protocols, objcImages, protocolsHashTable, duplicateSharedCacheClassMap, protocolMap);
// If we have closure selectors, we need to make a hash table for them.
if ( !closureSelectorStrings.empty() ) {
objc::PerfectHash phash;
objc::PerfectHash::make_perfect(closureSelectorStrings, phash);
size_t size = ObjCStringTable::size(phash);
selectorsHashTable.resize(size);
//printf("Selector table size: %lld\n", size);
selectorStringTable = (ObjCStringTable*)selectorsHashTable.begin();
selectorStringTable->write(phash, closureSelectorMap.array());
}
}
继续深入了解 make_perfect 这个函数的实现。
3.1 Perfect Hash
通过对研读代码逻辑和耗时分析,最终定位到耗时代码部分为PerfectHash.cpp 中 findhash 函数,这个函数也是 完美散列函数 的核心逻辑。
这里涉及到了一个概念PerfectHash,PerfectHash 的核心是完美散列函数,我们看下维基百科的解释:
对集合S的完美散列函数是一个将S的每个元素映射到一系列无冲突的整数的哈希函数
简单来讲 完美散列函数 是【对输入的字符串列表】【为每个字符串生成一个唯一整数】。
for (si=1; ; ++si)
{
ub4 rslinit;
/* Try to find distinct (A,B) for all keys */
*salt = si * 0x9e3779b97f4a7c13LL; /* golden ratio (arbitrary value) */
initnorm(keys, *alen, blen, smax, *salt);
rslinit = inittab(tabb, keys, FALSE);
if (rslinit == 0)
{
/* didn't find distinct (a,b) */
if (++bad_initkey >= RETRY_INITKEY)
{
/* Try to put more bits in (A,B) to make distinct (A,B) more likely */
if (*alen < maxalen)
{
*alen *= 2;
}
else if (blen < smax)
{
blen *= 2;
tabb.resize(blen);
tabq.resize(blen+1);
}
bad_initkey = 0;
bad_perfect = 0;
}
continue; /* two keys have same (a,b) pair */
}
/* Given distinct (A,B) for all keys, build a perfect hash */
if (!perfect(tabb, tabh, tabq, smax, scramble, (ub4)keys.count()))
{
if (++bad_perfect >= RETRY_PERFECT)
{
if (blen < smax)
{
blen *= 2;
tabb.resize(blen);
tabq.resize(blen+1);
--si; /* we know this salt got distinct (A,B) */
}
else
{
return false;
}
bad_perfect = 0;
}
continue;
}
break;
}
此时通过对比新老版本的数据(使用 dyld 分别运行新老版本的可执行文件对比打印的日志),发现:
-
老版本循环了 31 次成功生成 HashTable
-
新版本循环了 92 次成功生成 HashTable
至此,我们距离成功已经非常接近了,于是进一步研读 dyld 源码和增加了更多打印信息代码,最终找到了相互冲突的函数字符串名称。
/*
* put keys in tabb according to key->b_k
* check if the initial hash might work
*/
static int inittab_ts(dyld3::OverflowSafeArray<bstuff>& tabb, dyld3::OverflowSafeArray<key>& keys, int complete, int si)
// bstuff *tabb; /* output, list of keys with b for (a,b) */
// ub4 blen; /* length of tabb */
// key *keys; /* list of keys already hashed */
// int complete; /* TRUE means to complete init despite collisions */
{
int nocollision = TRUE;
ub4 i;
memset((void *)tabb.begin(), 0, (size_t)(sizeof(bstuff)*tabb.maxCount()));
/* Two keys with the same (a,b) guarantees a collision */
for (i = 0; i < keys.count(); i++) {
key *mykey = &keys[i];
key *otherkey;
for (otherkey=tabb[mykey->b_k].list_b;
otherkey;
otherkey=otherkey->nextb_k)
{
if (mykey->a_k == otherkey->a_k)
{
// 打印冲突的字符串
std::cout << mykey->name_k << " and " << otherkey->name_k << " has the same ak " << otherkey->a_k << " si is " << si << std::endl;
nocollision = FALSE;
/* 屏蔽此处代码,有冲突的情况下,继续执行,便于打印所有的冲突
if (!complete)
return FALSE;
*/
}
}
++tabb[mykey->b_k].listlen_b;
mykey->nextb_k = tabb[mykey->b_k].list_b;
tabb[mykey->b_k].list_b = mykey;
}
/* no two keys have the same (a,b) pair */
return nocollision;
}
根据以上信息,我们已经了解到在Building Closure阶段中,可能存在字符串的 Hash 碰撞 引发循环次数大幅增加,进而引发了启动耗时暴增。
在经过 dyld 调试的耗时数据、构建出包后验证的数据验证后,通过避免 Hash 碰撞,我们完成了启动时长的优化。
3.2 向前一步
其实从打印的冲突函数名称来看,历史代码中已经存在了 Hash 碰撞 的现象。
猜想,如果我们解决了所有的字符串的 Hash 碰撞,岂不是不仅可以修复启动耗时异常上升的问题,还可以进一步降低启动耗时,提高启动速度?
于是我们对每个有碰撞的函数名称进行修改,经过出包验证,结果与我们猜测的一致,启动耗时有明显的下降。
数据为 PC 电脑端运行 dyld 生成 BuildingClosure 的耗时数据,非手机端数据
四、总结
我们探索了 BuildingClosure 的生成过程,发现在Building Closure阶段中,可能存在字符串的 Hash 碰撞 引发循环次数大幅增加,进而引发了启动耗时暴增,进而导致启动耗时的大幅增加。
我们也发现,Building Closure Hash碰撞相关的启动耗时,其实与项目配置、编译环境、打包脚本等均无任何关系,就只是存在了字符串的Hash 碰撞 ,才引发循环次数大幅增加,进而导致启动时长增加。
往期回顾
2.从对话到自主行动:AI应用如何从 Chat 进化为 Agent?开源项目源码深度揭秘|得物技术
文 / 道隐
关注得物技术,每周一、三更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
未经得物技术许可严禁转载,否则依法追究法律责任。
Tagged Pointer:苹果工程师的内存优化艺术
WASM I/O 2025 | MoonBit获Kotlin核心开发,Golem Cloud CEO高度评价
2025 年 3 月 27 日至 28 日,为期 2 日的 WASM I/O 2025 大会在巴塞罗那圆满落幕。期间,众多 WASM 社区领袖和技术专家,共同带来了 30 余场精彩的演讲,共同探讨云原生和开源领域的前沿洞察、核心技术与最佳实践。作为一门专为 WebAssembly 优化的新兴编程语言,MoonBit 作为首个中国开发者平台受邀在本次大会上进行技术分享。
主题演讲
在本次活动中,MoonBit 负责人张宏波带来了《MoonBit & WebAssembly》主题演讲。作为一门专为 WebAssembly(Wasm)设计的编程语言,MoonBit 凭借其极致的性能优化、创新的语法设计及开发者友好的工具链,展示了在浏览器与服务器端的全栈潜力。以下是本次演讲的核心技术回顾。
张宏波开宗明义地指出,现有语言在 Wasm 生态中存在三大痛点:二进制体积臃肿、编译速度缓慢以及运行时性能不足。MoonBit从语言设计之初便围绕Wasm优化,实现了极简二进制体积、闪电编译速度、高性能运行时的突破。
MoonBit在设计时非常重视开发者体验,在工具链设计上体现了 “开发者优先” 的理念。其云 IDE 无需本地安装,集成并行类型检查、实时错误提示及基于 ChatGPT 的代码补全功能。调试支持亦是一大亮点,开发者可直接通过 Chrome DevTools 对 Wasm 模块进行断点调试和性能分析,与传统 JavaScript 工作流无缝衔接。MoonBit 与 WasmGC 的深度整合,使其在浏览器端表现尤为亮眼。通过零成本 JS 互操作和 Unicode 安全算法,在浏览器端实现无缝集成与性能突破。
针对服务器端开发,MoonBit 借助 Wasm 组件模型(Component Model)实现了跨语言协作的模块化架构。通过标准化接口定义语言(WIT),开发者可生成跨语言绑定。在编写 HTTP 服务器的实践中,MoonBit 生成的二进制大小仅为 27KB(Rust 为 100KB,Python 为 17MB)。
在未来,MoonBit 将深度整合组件模型,将 wasm-tools 直接嵌入工具链,支持 WASI 异步流(Phase 5提案)等前沿特性。
MoonBit 的潜力不仅限于 Wasm,其支持多后端编译的特性为开发者提供了灵活的选择:编译为 C 代码时可运行于嵌入式设备(MCU),而实验性 JavaScript 后端则支持 Elm 架构,用于构建全栈应用。
对于一门语言来说,生态系统和开发者体验是其获得广大社区认可的必备条件。在 2025 年,MoonBit 将达到 Beta 版本,并进一步完善语言工具链与生态,随着 Wasm 在边缘计算、微服务等领域的深化,MoonBit 或将成为开发者拥抱下一代 Web 标准的关键选择。
社区反馈
在本次演讲后,Kotlin/Wasm 作者 Zalim 在社交媒体上表示:"MoonBit 在 WebAssembly 平台实现了精彩的成果",对 MoonBit 在 WASM 方向的技术成果给予高度认可。
ZivergeTech & Golem Cloud 公司 CEO John A De Goes 表示:“与张宏波在 WASM I/O 见面后,我对 MoonBit 未来更加充满期待!MoonBit 融合了 Rust 语言的精华特性,还增加了垃圾回收(GC)机制,并优先关注工具链、性能和稳定性。欢迎大家加入我,一起参加在 LambdaConf 举办的 GolemCloud 黑客松活动,届时我会使用MoonBit!”
MoonBit 在本次 WASM I/O 上的精彩亮相吸引了海外社区诸多关注,我们期待未来 MoonBit 在全球开发者社区的无限潜力!