阅读视图

发现新文章,点击刷新页面。

SwiftUI 7 新 WebView:金蛇出洞,网页江湖换新天

在这里插入图片描述

概述

崇祯年间,华山派武学虽盛,却在应对江湖新局时渐显颓势;如今 SwiftUI 江湖亦是如此 ——WWDC 25 之前,若要在 SwiftUI 中显示网页,开发者恰似袁承志初闯江湖,纵有一身本领,却苦无称手兵刃。

在这里插入图片描述

直到那柄 "金蛇剑" 般的全新 WebView 横空出世,才让网页显示之道豁然开朗。

在本篇武学大家谈中,各位少侠将学到如下内容:

  • 一、往昔困局:华山旧功难破迷阵
  • 二、金蛇出洞:WWDC 25 的 WebView 新法
      1. 初窥门径:基础网页加载
      1. 内功心法:WebPage 状态管理
      1. 奇门绝技:JS 交互与平台适配
  • 三、江湖展望:金蛇之后再无钝剑

想得到那柄可以横扫武林的神兵利器金蛇剑吗?那还等什么?让我们马上开始寻“剑”之旅吧!

Let's go!!!;)


一、往昔困局:华山旧功难破迷阵

想当年,SwiftUI 自身并无网页显示的独门心法,开发者们只得借 UIKit 的 WKWebView 这柄 "钝剑",再辅以UIViewRepresentable为鞘,方能勉强施展。

在这里插入图片描述

这般操作,犹如袁承志在华山练剑时,需先扎三年马步 —— 基础虽牢,却失之滞涩。

且看这套 "华山入门剑法":

import SwiftUI
import WebKit

// 以UIViewRepresentable为桥,连接SwiftUI与WKWebView
struct WebViewWrapper: UIViewRepresentable {
    let url: URL?
    
    func makeUIView(context: Context) -> WKWebView {
        return WKWebView()
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        guard let url = url else { return }
        uiView.load(URLRequest(url: url))
    }
}

// 实战时需如此调用,恰似执钝剑闯敌营
struct ContentView: View {
    var body: some View {
        WebViewWrapper(url: URL(string: "https://apple.com"))
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
    }
}

这套功夫虽能御敌,却有三大弊端:

  • 其一,updateUIView反复调用时易生错乱,好比剑法中 "剑招互碍";
  • 其二,网页状态监听需另设代理,犹如练剑时还要分心护脉;
  • 其三,与 SwiftUI 状态管理结合时,常现 "内力相冲" 之象 —— 稍有不慎便会数据错乱。

在这里插入图片描述

二、金蛇出洞:WWDC 25 的 WebView 新法

正当开发者们困于旧法之时,WWDC 25 恰似一场武林大会,苹果突然亮出 "金蛇剑"——SwiftUI 原生 WebView 横空出世!此剑一出,如金蛇郎君夏雪宜重现江湖,招式灵动,浑然天成,将网页显示之道推向新境。

在这里插入图片描述

1. 初窥门径:基础网页加载

新 WebView 的基础用法,恰似袁承志初得金蛇剑时的随手一挥,看似简单却暗藏玄机:

import SwiftUI
import WebKit

struct ContentView: View {
    // 直接使用URL初始化,无需繁琐包装
    var body: some View {
        WebView(url: URL(string: "https://apple.com"))
            .navigationTitle("金蛇洞")
            .edgesIgnoringSafeArea(.bottom)
    }
}

这般代码,较之旧法省去近八成冗余,正如金蛇剑法 "险、奇、快" 之妙 —— 无需再写UIViewRepresentable的桥接代码,无需手动管理 WebView 生命周期,SwiftUI 自会料理妥当。

真乃呜呼快哉!

2. 内功心法:WebPage 状态管理

若要深入掌控网页状态,需修习 "金蛇秘籍"——WebPage类。此物如同袁承志从山洞中所得的金蛇锥谱,将网页的标题、URL、加载进度等信息尽收其中:

import SwiftUI
import WebKit
internal import Combine

// 实战运用:将心法与招式结合
struct ContentView: View {
    @State private var page = WebPage()
    @State private var id: WebPage.NavigationID?
    @State private var isLoading = false
    @State private var event: WebPage.NavigationEvent?
    
    var body: some View {
        NavigationStack {
            WebView(page)
                .navigationTitle(page.title)
                .toolbar {
                    ToolbarItem(placement: .primaryAction) {
                        Button(action: {
                            page.reload()
                        }) {
                            Image(systemName: "arrow.counterclockwise")
                        }
                    }
                    
                    ToolbarItem(placement: .topBarLeading) {
                        if isLoading {
                            ProgressView()
                        }
                    }
                }
                .onChange(of: page.isLoading) { _, new in
                    isLoading = new
                }
                .onReceive(page.currentNavigationEvent.publisher) { event in
                    guard event.navigationID == id else { return }
                    
                    switch event.kind {
                    case let .failed(underlyingError: error):
                        print(error.localizedDescription)
                    case .finished:
                        print("网页加载完毕")
                    default:
                        break
                    }
                }
                .task {
                    let request = URLRequest(url: .init(string: "https://blog.csdn.net/mydo")!)
                    id = page.load(request)
                }
        }
    }
}

此处关键在于@Published属性 —— 当网页标题变化时,导航栏会如响应内力般自动更新;进度条则像金蛇吐信般实时菊花旋转:

在这里插入图片描述

这般状态同步,较旧法中手动绑定NotificationCenter的操作,可谓 "化繁为简,返璞归真"。

在这里插入图片描述

3. 奇门绝技:JS 交互与平台适配

新 WebView 最令人称道之处,莫过于对 JavaScript 交互的 "化骨绵掌" 式处理。

在这里插入图片描述

昔日要在 Swift 与 JS 间传递数据,需写十数行桥接代码,如同袁承志在华山与温家五老缠斗时的狼狈;如今却能一剑破局:

// 实战运用:将心法与招式结合
struct ContentView: View {
    @State private var page = WebPage()
    @State private var id: WebPage.NavigationID?
    @State private var isLoading = false
    @State private var event: WebPage.NavigationEvent?
    @State private var titleFromJS: String?
    
    var body: some View {
        NavigationStack {
            WebView(page)
                .navigationTitle(page.title)
                .toolbar {
                    
                    ToolbarItemGroup {
                        
                        Button {
                            Task {
                                if let jsResult = try? await page.callJavaScript(
                                    """
                                    return document.title;
                                    """
                                ), let title = jsResult as? String {
                                    titleFromJS = title
                                }
                            }
                        } label: {
                            Image(systemName: "figure.run")
                        }
                        
                        Button {
                            Task {
                                try? await page.callJavaScript(
                                    """
                                    document.body.style.backgroundColor = 'gold'
                                    """
                                )
                            }
                        } label: {
                            Image(systemName: "figure.cricket")
                        }
                        
                        Button(action: {
                            page.reload()
                        }) {
                            Image(systemName: "arrow.counterclockwise")
                        }
                    }
                    
                    
                    ToolbarItem(placement: .topBarLeading) {
                        if isLoading {
                            ProgressView()
                        }
                    }
                }
                .onChange(of: page.isLoading) { _, new in
                    isLoading = new
                }
                .onReceive(page.currentNavigationEvent.publisher) { event in
                    guard event.navigationID == id else { return }
                    
                    switch event.kind {
                    case let .failed(underlyingError: error):
                        print(error.localizedDescription)
                    case .finished:
                        print("网页加载完毕")
                        
                    default:
                        break
                    }
                }
                .task {
                    let request = URLRequest(url: .init(string: "https://blog.csdn.net/mydo")!)
                    id = page.load(request)
                }
                .safeAreaInset(edge: .top) {
                    if let title = titleFromJS {
                        Text(title)
                            .font(.title2)
                            .padding()
                            .background(.thinMaterial.opacity(0.66), in: RoundedRectangle(cornerRadius: 15))
                    }
                }
        }
    }
}

在这里插入图片描述

在上面的代码中,我们用 JavaScript 做了两件事:

  1. 动态实时获取到了网页的标题;
  2. 将网页背景设置为金色;

此等操作,恰似金蛇郎君以金蛇锥破敌甲胄 —— 直接穿透 Swift 与 JS 的壁垒。

在这里插入图片描述

更妙者,新 WebView 对各平台特性的适配,如 visionOS 的 "看向滚动" 功能,只需一行修饰符即可大功告成:

WebView(webView: page.webView)
#if os(VisionOS)
// 开启 VisionOS 滚动输入”通天眼“
        .webViewScrollInputBehavior(.enabled, for: .look)
    #endif
        .scrollBounceBehavior(.basedOnSize) // 滚动反馈如"踏雪无痕"

三、江湖展望:金蛇之后再无钝剑

回首 SwiftUI 的网页显示之道,恰似袁承志的武学进阶:从华山派的循规蹈矩,到金蛇剑法的灵动不羁,再到融会贯通自成一派。

WWDC 25 推出的新 WebView,不仅解决了旧法中的 "招式沉冗" 之弊,更将 SwiftUI 的声明式编程理念推向新高度。

在这里插入图片描述

正如金蛇剑在袁承志手中终成一代传奇,这套新 WebView API 亦将成为开发者闯荡网页江湖的不二之选。

毕竟,真正的好功夫,从来都是 "大道至简,大巧若拙"—— 能以三两行代码办妥之事,何必耗费十数行力气?此乃 WWDC 25 留给 SwiftUI 开发者的最大启示,亦是江湖不变之真理。

那么,各位少侠看到这里又作何感想呢?

感谢宝子们的观看,再会啦!8-)

Swift 6.2:江湖再掀惊涛浪,新功出世震四方

在这里插入图片描述

概述

江湖代有才人出,各领风骚数百年。

自 Swift 语言横空出世,便在 iOS 开发的武林中搅动风云。如今 WWDC 25 之上,Apple 闭门三年炼就的《Swift 6.2 真经》终见天日,书中所载新功个个精妙绝伦,足以让开发者们的代码功力更上一层楼。

在这里插入图片描述

在本篇武林秘闻中,各位少侠将领悟到如下奥义:

  1. 并发迷局终得解:"nonisolated (nonsending)" 与 "defaultIsolation" 双剑合璧 1.1 第一式:"nonisolated (nonsending)" 调和阴阳 1.2 第二式:"defaultIsolation" 定鼎乾坤
  2. "@concurrent" 破界令牌:独辟蹊径的旁门绝学
  3. 内存管理新心法:InlineArray 与 Span 的 "缩骨功" 3.1 InlineArray:栈上藏兵,招之即来 3.2 Span:内存视图,隔岸观火
  4. 严格内存安全的 “金钟罩”:让内存错误无处遁形
  5. 语言互操新经脉:Swift 与 C++ "打通任督二脉"
  6. 与 Java 的跨界合作
  7. 结语:新功在手,江湖我有

今日便由老夫为诸位少侠拆解其中奥秘,且看这新功如何改写江湖格局。 Let's go!!!;)


1. 并发迷局终得解:"nonisolated (nonsending)" 与 "defaultIsolation" 双剑合璧

往昔江湖,并发编程堪称开发者的 "鬼门关"。

多少英雄好汉在此折戟沉沙 —— 同步函数与异步函数如同正邪两道,运行规则大相径庭;主 Actor 调用时更是冲突不断,轻则编译器怒目相向,重则数据走火入魔,当真令人闻风丧胆。

Swift 6.2 携 "nonisolated (nonsending)" 与 "defaultIsolation" 两大神功而来,恰似倚天屠龙双剑合璧,专破这经脉错乱之症。

1.1 第一式:"nonisolated (nonsending)" 调和阴阳

此功专为理顺函数调用的 "阴阳二气" 所创。

标注此功的函数,既能保持独立姿态(nonisolated),又不会随意发送数据(nonsending),恰似一位守礼的侠客,既不依附门派,又不轻易出手伤人:

// 旧制:异步函数常因隔离问题"走火入魔"
actor DataManager {
    var value: Int = 0
    
    // 欲调用此函数,需先过编译器"三关"
    func fetchData() async -> Int {
        return value
    }
}

// 新功:nonisolated (nonsending) 让函数"独善其身"
actor DataManager {
    var value: Int = 0
    
    nonisolated(nonsending) func fetchData() async -> Int {
        // 可安全访问非隔离数据,或执行独立逻辑
        return 42
    }
}

这般写法,如同给函数戴上 "君子剑",既保持独立风骨,又不伤邻里(其它 Actor),实乃解决隔离冲突之必备良药。

1.2 第二式:"defaultIsolation" 定鼎乾坤

此功堪称主 Actor 门派的 "盟主令"—— 可在包级别(package)定下规矩:凡未明确 "叛逃" 的代码,皆默认归入主 Actor 麾下

这便如武林盟主昭告天下:"未投他派者,皆听我号令",瞬间省去无数手动标注的繁琐:

// 在Package.swift中启用盟主令
swiftSettings: [
    .defaultIsolation(MainActor.self), // 立主Actor为盟主
    .enableExperimentalFeature("NonisolatedNonsendingByDefault")
]

// 此后代码自动归入主Actor,无需再画蛇添足
class ViewModel {
    func updateUI() async {
        // 默认为主Actor内运行,可安心操作UI
        print("UI更新无虞")
    }
}

有了这盟主令,UI 相关代码自归其位,后台任务也能各安其职,当真 "物各有主,井然有序"。

在这里插入图片描述

2. "@concurrent" 破界令牌:独辟蹊径的旁门绝学

江湖之大,总有需要 "特立独行" 之时。

Swift 6.2 推出的 "@concurrent" 令牌,恰如一张 "通关文牒",持此令牌者,可脱离调用者的经脉,另辟新的隔离语境,堪称 "破界而行" 的旁门绝学。

此令牌虽威力无穷,却有铁律约束:仅能授予 "nonisolated" 函数

若将其用于 Actor 门派的招式,除非该招式已明确 "脱离门派"(标注 nonisolated),否则便是 "违规练功",日后必遭编译器反噬:

// 正道:nonisolated函数持令牌,名正言顺
actor NetworkClient {
    @concurrent
    nonisolated func fetchImage() async throws -> UIImage {
        // 脱离NetworkClient的隔离,另起炉灶
        let data = try await URLSession.shared.data(from: url)
        return UIImage(data: data.0)!
    }
}

// 禁忌:未脱离门派却持令牌,必遭反噬
actor NetworkClient {
    @concurrent // 编译器怒喝:"此等叛逆,当诛!"
    func fetchImage() async throws -> UIImage {
        // 此乃禁忌招式,万万不可学
    }
}

这令牌的妙用在于:当你需要一个 "临时工"(独立隔离的函数),又不想让它沾染主 Actor 的 "门派气息" 时,只需授予此令,便能让其 "独来独往,自成一派"。

3. 内存管理新心法:InlineArray 与 Span 的 "缩骨功"

内存管理向来是秃头少侠们的 "内功根基",根基不牢地动山摇,招式再花也难成大器。

Swift 6.2 推出的 InlineArray 与 Span,恰似两套 "缩骨功",能将内存占用压缩到极致,运行速度却如离弦之箭。

在这里插入图片描述

3.1 InlineArray:栈上藏兵,招之即来

寻常数组(Array)如同 "堆上营寨",虽容量可观,却需耗费时间搭建(堆内存分配)。

InlineArray 则是 "栈上藏兵",固定大小,随用随取,省去了营寨搭建的功夫:

// 声明一个可容纳5名"士兵"(Int)的栈上营寨
var inlineArray: InlineArray<Int, 5> = [1, 2, 3, 4, 5]

// 直接取用,无需等待营寨搭建
inlineArray[2] = 100
print(inlineArray) // [1, 2, 100, 4, 5]

此功最适合 "小股特种暗杀部队"(固定大小、数量不多的数据),如游戏中的坐标点、传感器的实时数据等,调用时快如闪电,绝不拖泥带水。

3.2 Span:内存视图,隔岸观火

Span 堪称 "内存望远镜"—— 它不持有内存,仅提供一片连续内存的 "视图",既能安全访问,又不占用额外空间,恰似隔岸观火,知全局而不添柴也:

let buffer: [UInt8] = [0x01, 0x02, 0x03, 0x04]

// 用望远镜观察内存,从索引1开始,看3个元素
let span = buffer[1..<4].withSpan { $0 }
print(span.count) // 3
print(span[0]) // 0x02

在解析二进制数据(如网络协议、文件格式)时,Span 能让你 "按图索骥",无需复制数据即可精准操作,实乃 "事半功倍" 之法。

4. 严格内存安全的 “金钟罩”:让内存错误无处遁形

内存安全问题一直是 iOS 开发中的一个 “心腹大患”,稍有不慎就可能导致程序崩溃、数据丢失等严重后果。

Swift 6.2 引入了严格内存安全特性,就像是为程序穿上了一层坚固的 “金钟罩”,能够有效地抵御各种内存错误的侵袭。

在以往的开发中,指针操作、内存分配与释放等操作常常隐藏着许多危险,少侠们需要花费大量的精力去确保内存的正确使用。而现在,启用严格内存安全特性后,编译器会对代码进行更加严格的检查,一旦发现潜在的内存安全问题,就会及时发出警告。

在这里插入图片描述

例如,在 Xcode 中,我们可以在项目的构建设置中将 “Strict Memory Safety” 设置为 “yes” 来启用这一特性。重新构建后,编译器会仔细检查代码中的每一处内存操作,如是否存在悬空指针、是否有内存泄漏等问题。

在这里插入图片描述

如果发现问题,编译器会给出详细的错误提示,帮助微秃少侠们及时修复,就像在江湖中,有了一位明察秋毫的武林前辈时刻提醒我们招式中的破绽,让我们能够及时修正,避免陷入东方不败的危险境地。

5. 语言互操新经脉:Swift 与 C++ "打通任督二脉"

江湖之中,门派林立,Swift 与 C++ 便如两大武学世家,各有精妙却隔阂甚深。

Swift 6.2 新修的 "互操经脉",终于让两派高手得以 "切磋武艺,互通有无"。

在这里插入图片描述

如今在 Swift 中调用 C++ 代码,恰似 "少林高僧学武当太极",招式转换自然流畅:

// C++中的"铁砂掌"函数
// int strike(int strength, int times);

// Swift中直接施展,无需翻译
import CppMartialArts

let damage = strike(100, 3) // 调用C++函数,如探囊取物
print("造成伤害:\(damage)")

更妙的是,C++ 的类也能在 Swift 中 "返璞归真",仿佛戴上 "易容面具",外观是 Swift 类,内里却是 C++ 的筋骨:

// C++的"Sword"类在Swift中可用
let mySword = CppSword(length: 1.2)
mySword.sharpen() // 调用C++方法
let damage = mySword.cut(target: "enemy")

这般互通,恰似武林大会上各派高手同台竞技,取长补短,当真 "海纳百川,有容乃大"。

6. 与 Java 的跨界合作

Swift 6.2 还为与 Java 的互操作性提供了更好的支持。

在这里插入图片描述

在一些跨平台开发场景中,Swift 与 Java 的交互需求日益增长。现在,Swift 6.2 使得 Swift 代码与 Java 代码之间的通信和协作变得更加容易,仿佛在两个不同的武林世界之间搭建了一座坚固的桥梁。

例如,在某些需要与 Java 后端服务进行交互的 iOS 应用中,Swift 6.2 的新特性可以帮助开发者更高效地实现数据传输和功能调用,大大提升了开发效率,让微秃少侠们能够在不同语言的 “江湖” 中自由穿梭,实现更强大的应用功能,并且希望少掉几根头发。

7. 结语:新功在手,江湖我有

Swift 6.2 的诸位新功,或解并发之困,或强内存之基,或通语言之隔,恰如为开发者打通了 "任督二脉",从此代码之路再无阻塞。

江湖路远,挑战常新,但只要手握这些新功秘籍,便能 "运筹帷幄之中,决胜千里之外"。诸位少侠,何不即刻闭关修炼,待功成之日,便是横行代码江湖之时!

在这里插入图片描述

记住,真正的高手,从不困于招式,而是善用利器。Swift 6.2 这柄神兵已交你手,接下来,便看你如何在开发的江湖中,写下属于自己的传奇!

感谢各位秃头少侠们的观赏,我们青山不改、绿水长流,江湖再见、后会有期!8-)

WWDC 25 风云再起:SwiftUI 7 Charts 心法从 2D 到 3D 的华丽蜕变

在这里插入图片描述

概述

在 iOS 开发这个波谲云诡的江湖中,SwiftUI 可谓是一位后起之秀,以其简洁明快的招式迅速在 UI 框架领域中崭露头角。

在这里插入图片描述

而其中的 Charts 框架,更是如同江湖中的 “数据可视化宝典”那样,让各位秃头少侠们能够轻松将复杂的数据转化为直观易懂的图表。

在本篇武林秘籍中,列位少侠将会领悟如下招式:

    1. 江湖起源——原有 Charts 框架的武学修炼
    1. 江湖变革——SwiftUI Charts 3D 图表来袭
    1. 轻功飞行——SurfacePlot 打造梦幻曲面图
    1. 握剑诀——交互与视角
    1. 3D 图表修炼的注意事项
    • 5.1 避免数据过载
    • 5.2 谨慎选择图表类型
    • 5.3 性能优化不容忽视
    1. 尾声:3D 图表开启数据可视化新纪元

那还等什么呢?各位初入江湖的豪客和“大虾”们,请随老夫一起进入图表的美妙世界吧!Let's go!!!;)


1. 江湖起源——原有 Charts 框架的武学修炼

何曾几时,WWDC 2020(SwiftUI 4.0,iOS 16)江湖风起云涌,数不清的英雄豪杰和兵器纷繁复杂,数据可视化在其中占据了举足轻重的地位。

而正是在这样的背景下,SwiftUI 的 Charts 框架横空出世,犹如一位初出茅庐的少年剑客,虽然招式简单,却也灵活迅捷,迅速得到了武林各大门派的认可。

在这里插入图片描述

初入江湖的菜鸟侠客们可以通过它迅速绘制出柱状图、折线图和饼状图等基础武学,简单易懂,入门容易。记得那年,当 18 岁的列位秃头少侠们在代码中添加 Chart 时,心中便涌起一股非凡的成就感,仿佛宝子们也成为了数据江湖中的一员。

如下便是一个简单的柱状图的代码示例,犹如初入武林的少年,凭借一把利剑便能出奇制胜:

import SwiftUI
import Charts

struct SimpleBarChart: View {
    let data: [Double] = [2, 3, 5, 7, 11, 13]

    var body: some View {
        Chart(data) { value in
            BarMark(x: .value("Value", value))
        }
        .chartXAxis {
            AxisMarks()
        }
    }
}

这就是“基本剑法”,以简洁利落见长,正如初出茅庐的剑客,刚刚踏入这个江湖那样的飘逸洒脱:

在这里插入图片描述

随着修炼的深入,少侠们会逐渐意识到,这种图表只能为大家在江湖中打下些许基础,但它也暴露出了一些不足。

在这里插入图片描述

虽然二十步之内,便可知敌人风吹草动,然而一旦对手修炼到更高的境界,单靠这种平凡的武学便显得不足以应对复杂的数据纷争。

2. 江湖变革——SwiftUI Charts 3D 图表来袭

谁能想象,江湖中的一场风云变幻,竟会让 Charts 框架焕发新生。

在WWDC 25 上,苹果总舵主为这门熟悉的武功奥义又注入了新的活力,推出了 Chart3D。这不再是寻常的剑法,而是进入了三维的殿堂。就像一位绝世大侠,早已超越了平面世界的束缚,开始在三维空间中闪展腾挪。

在这里插入图片描述

这时的 Chart3D,犹如一位有了深厚内力的高手,能在三维空间中挥洒自如。宝子们不再仅仅是直线和曲线的过客,而是能在空间中将数据点、曲面、视角交织成一幅立体图景。

无论是点的组合,还是面的铺展,或是交互的旋转,每一处都透露着数据与现实世界的紧密联系。

快看这段代码,仿佛是大侠抬手之间,便可将复杂的数据织入眼前的三维世界那样畅快淋漓:

struct ContentView: View {
    let data = [(2,5), (3,7), (5,11), (7,8), (11,20), (13,10)]

    var body: some View {
        NavigationStack {
            Chart3D(data.indices, id: \.self) { index in
                let item = data[index]
                PointMark(
                    x: .value("X", item.0),
                    y: .value("Y", item.1),
                    z: .value("Z", index)
                )
                .foregroundStyle(.red.gradient)
            }
            .chartXAxisLabel("X轴")
            .chartYAxisLabel("Y轴")
            .chartZAxisLabel("Z轴")
            .navigationTitle("Charts 心法展示")
        }
    }
}

此时,数据不仅停留在纸面上,而是跃然于三维空间之中,犹如剑客挥舞长剑,刺破苍穹:

在这里插入图片描述

现在,列为微秃小少侠们可以随心所欲地操控每一个数据点的位置,仿佛掌握了整个空间的节奏,就问你们赞不赞呢?

3. 轻功飞行——SurfacePlot 打造梦幻曲面图

在武林中,总有一些高手以轻盈如风的身法著称,他们步伐矫健,时隐时现。而 SurfacePlot 就如同这般,能够以平滑的曲面将两个变量之间的关系展现得淋漓尽致。

在这里插入图片描述

它能让小伙伴们不再拘泥于线条和点,而是化繁为简,把复杂的数据关系化作一张优美的曲面,轻盈地在三维空间中随意漂浮。

如果宝子们想描绘一条像“乾坤大挪移”般自由流畅的曲线,那便可以借助 SurfacePlot 来用数学函数得偿所愿。

下面的代码犹如一套行云流水的剑法,将数学的深奥与图形的简洁相结合,点滴之间尽显工艺之精妙:

import SwiftUI
import Charts

struct SurfacePlotChart: View {
    var body: some View {
        NavigationStack {
            Chart3D {
                SurfacePlot(x: "X", y: "Y", z: "Z") { x, z in
                    let h = hypot(x, z)
                    return sin(h) / h * 2
                }
                .foregroundStyle(.heightBased)
            }
            .chartXScale(domain: -10...10)
            .chartZScale(domain: -10...10)
            .chartYScale(domain: -0.23...10)
            .navigationTitle("Chart3D 心法传功")
        }
    }
}

这就像是武林中的一招“飞燕回旋”,优雅、流畅,同时又极具威力:

在这里插入图片描述

当各位微秃侠客们把数学函数转化为三维曲面,它就不再只是抽象的公式,而是化身为一场精彩的武林争斗,令人叹为观止。

4. 握剑诀——交互与视角

然而,江湖变幻莫测,如何在纷繁复杂的数据中保持清醒的视角,便成为了每个剑客面临的严峻挑战。

Chart3D 的强大之处不仅仅在于它能够描绘出三维的世界,更在于它提供了丰富的交互功能,能够根据需要调整视角,让宝子们从不同角度观看同一数据的图表,仿佛随时可以恣意改变剑法招式那般美妙。

在这里插入图片描述

以下代码示范了如何通过手势控制图表的旋转角度,并且设置初始的视角与相机投影,这种灵活性犹如大侠挥剑的自由度,让小伙伴们在数据的世界里遨游无碍:

struct ContentView: View {
    @State var data = [(Int,Int)]()
    
    private func createData() {
        for _ in 0...99 {
            let x = Int.random(in: 0...100)
            let y = Int.random(in: 0...100)
            data.append((x, y))
        }
    }
    
    @State private var pose = Chart3DPose(
        azimuth: .degrees(20),    // 水平角度
        inclination: .degrees(15) // 垂直角度
    )

    var body: some View {
        NavigationStack {
            Chart3D(data.indices, id: \.self) { index in
                let item = data[index]
                PointMark(
                    x: .value("X", item.0),
                    y: .value("Y", item.1),
                    z: .value("Z", index)
                )
                .foregroundStyle(.red.gradient)
            }
            .task {
                createData()
            }
            .chartXAxisLabel("X轴")
            .chartYAxisLabel("Y轴")
            .chartZAxisLabel("Z轴")
            .chart3DPose($pose)
            .navigationTitle("Charts 心法展示")
        }
    }
}

宝子们可以像大侠操控剑气那般,调整图表的角度与视野,每一次变化,都能带来全新的观感与体验:

在这里插入图片描述

同样我们略施小计,之前的曲面图也可以如法炮制:

在这里插入图片描述

从此,数据的世界也因此变得不再单调,而是充满了无限可能。

5. 3D 图表修炼的注意事项

5.1 避免数据过载

虽然 3D 图表能够展示丰富的数据信息,但在使用时也要注意避免数据过载。过多的数据点或过于复杂的数据维度,会让图表变得混乱不堪,就像武林高手在战斗中面对过多的敌人,反而会陷入困境。开发者需要对数据进行筛选和精简,突出重点,确保图表清晰可读性。

5.2 谨慎选择图表类型

不同的图表类型适用于不同的数据展示场景,在使用 3D 图表时,要根据数据的特点和分析目的,谨慎选择合适的图表样式。

例如,3D 柱状图适合用于对比数据,3D 散点图适合分析数据之间的关系,而 3D 饼图则不太适合这些场景,因为在三维空间中,饼图的角度和比例可能会让人产生视觉误解。

选择合适的图表类型,就像武林高手选择了合适的兵器,才能发挥出最大威力。

5.3 性能优化不容忽视

由于 3D 图表的渲染和计算量较大,容易对应用的性能产生影响。因此,在开发过程中,要注重性能优化。

可以采用异步加载数据、使用高效的数据结构和算法、合理利用缓存等方法,确保图表的加载和交互丝一般流畅顺滑,不给用户带来卡顿体验。否则,就像内力不足的武林高手,招式施展起来也会大打折扣。

6. 尾声:3D 图表开启数据可视化新纪元

从最初的 2D 图表到如今的 3D 图表,SwiftUI 7 的 Charts 框架在数据可视化的江湖中不断进化,为开发者们提供了越来越强大的工具。

3D 图表的出现,不仅让数据可视化变得更加生动、直观,也为武林高手们开辟了一片全新的江湖。

在这里,宝子们可以凭借自己的智慧和技艺,运用 3D 图表这一绝世神功,将数据的魅力展现得淋漓尽致,为用户带来前所未有的数据探索体验。

在这里插入图片描述

在未来的 iOS 开发江湖中,3D 图表必将成为开发者们手中的一把利器,助力他们在数据可视化领域中披荆斩棘,创造出更多令人惊叹的应用。而每一位开发者,都将在这个充满机遇与挑战的江湖中,书写属于自己的传奇故事。让我们怀揣着对技术的热爱和追求,勇敢地踏入这片新江湖,探索 3D 图表的无限未来吧!

此时,数据的江湖,已经不再是一个简单的平面,而是充满了三维空间的无限元宇宙,正如你们已然成为了这片江湖中举世无双的大侠一样,棒棒哒!

那么,感谢各位少侠的观赏!再会啦!8-)

Swift 数学计算:用 Accelerate 框架让性能“加速吃鸡”

在这里插入图片描述

🍎 概述

大家都知道,我对 Swift 语言中算法这套玩意儿乐此不疲,几乎把 Apple 所有内置框架搞了个翻天覆地——为了图表、统计、集合、并发啥的,时不时还要补上一堆自定义计算。

在这里插入图片描述

今天,我们要聊聊隐藏在苹果设备底层的“神器”——Accelerate 框架,堪称锦上添花

在本篇文章中,您将学到如下内容:

⚡ 1. 什么是 Accelerate? ➕ 2. 求和:sum 📏 3. 平均值:mean 📉 4. 标准差:standard deviation 🧠 5. 更多魔法:矩阵、FFT、ML ✅ 6. 总结

那还等什么呢?让我们马上开始速度与激情之旅吧! Let's go!!!;)


⚡ 1. 什么是 Accelerate?

早在多年前,Apple 就端出了 Accelerate 框架,现在几乎各个平台都能享用它的妙处。

在这里插入图片描述

此乃是专门给 大数据量和高性能准备的神兵利器:利用底层的向量处理能力,帮你 又快又省电,堪称 一箭双雕

Accelerate 中有一个高效 API 集合叫做 vDSP,专治各种数字信号处理的各种不服,还内置了许多超优化函数。

现在,就让我们从最常用的几个亮点函数说起吧👇

➕ 2. 求和:sum

我们先从最基础的操作“求和”说起。普通 Swift 代码可能这样写:

let values = [6,6,8,7,8,10,10.0]
let sum = values.reduce(0.0, +)

小数据貌似没啥问题,但倘若上来就是“千军万马”时,就会捉襟见肘。我们可以用 vDSP 来轻松搞定它:

import Accelerate
let sum = vDSP.sum(values)

一行搞定,省时省力又省心

📏 3. 平均值:mean

接下来是“平均值”。

传统写法是:

let mean = values.reduce(0.0, +) / Double(values.count)

而用 vDSP 我们可以这么写:

let mean = vDSP.mean(values)

简洁到令人发指,性能上也是真刀真枪地快!vDSP,你怎么可以这样无理取闹呢?

📉 4. 标准差:standard deviation

接下来轮到标准差登场——也就是判断我们生活是否“离谱”的神器。如果周一到周五宝子们睡 6-7 小时,周末一觉睡到 10 小时,平均 8 看着不错,但其实差距将会天壤之别,根本就是作息放飞自我,有种“月亮不睡我不睡,我是秃头小宝贝”的感觉。

然而,vDSP 标准差的算法会是下面这个样子的:

let sd = vDSP.standardDeviation(values)
if sd > 0.5 {
    print("improve your bedtime routine!")
} else {
    print("you have a good sleep schedule!")
}

不过注意,这个方法在较老系统上不可用。如果你要兼容旧系统,那就“拆东墙补西墙”,亲手搞个简化版来“尝尝鲜”:

extension vDSP {
    static func sd(_ vector: some AccelerateMutableBuffer<Double>) -> Double {
        guard vector.count > 1 else { return 0.0 }
        let mean = vDSP.mean(vector)
        let meanVector = Array(repeating: mean, count: vector.count)
        let differences = vDSP.subtract(vector, meanVector)
        return vDSP.rootMeanSquare(differences)
    }
}

靠着 meansubtractrootMeanSquare 三大法宝,咱们就能举重若轻地算出标准差,性能也如风驰电掣般流畅!很赞哦!

🧠 5. 更多魔法:矩阵、FFT、ML

当然啦,Accelerate 的江湖地位不止于此,它还内含了很多深藏功与名的模块,比如矩阵乘法、傅里叶变换、甚至机器学习支持等……如果小伙伴们经常处理大量数据,又希望节能高效,那它绝对是如虎添翼的存在。

建议大家逆水行舟,不进则退,有空多看看文档,说不定哪天你就写出了下一个爆款 App。


我决定用 Accelerate 写一个超越迅雷的下载 App,就叫掩耳下载吧,因为“迅雷不及掩耳”。;)


✅ 6. 总结

  • vDSP.sumvDSP.meanvDSP.standardDeviation 一行代码搞定复杂计算,堪称效率爆表
  • ✅ 旧平台也能手动组装方法,水到渠成
  • ✅ Accelerate 是 Apple 官方提供的“压箱底”级高性能库,用得好,一鸣惊人指日可待!

希望这篇“Accelerate”能让宝子们醍醐灌顶茅塞顿开,同时还能笑出声 😆。

如有疑问,记得去找我私信,咱们下回再聊,不见不散

再会啦!8-)

SwiftUI 7(iOS 26)中玻璃化工具栏的艺术

在这里插入图片描述

🌊 Liquid Glass 简介:苹果设计美学的新纪元

苹果在 WWDC25 上大刀阔斧推出了全新设计语言 Liquid Glass(液态玻璃),全面影响旗下所有平台。其核心特点是界面元素的玻璃质感与光影通透效果,实现真正意义上的“虚实结合,界面如水”。

在这里插入图片描述

在上一篇博文中,我们聚焦于 Tabs 的玻璃化,这里我们将深入探讨另一关键控件的变革——工具栏(Toolbars) 的玻璃化蜕变。

在本篇博文中,您将学到如下内容:

  • 🌊 Liquid Glass 简介:苹果设计美学的新纪元
  • 🍸 工具栏新体验:不改代码,也能自动变身
  • 📦 基础示例:新旧兼容的优雅演绎
  • 🎭 保持兼容旧系统:labelStyle 定制样式
  • 🧱 自定义 LabelStyle:向下兼容神器
  • ✨ Placement 的魔法:位置决定样式
  • 示例:
  • 完整示例代码:
  • 🧰 Toolbar 分组与 Spacer 控制
  • 示例:
  • 📌 总结:Liquid Glass 下的 Toolbar 重构指南
  • 📮 写在最后

那小伙伴们还等什么呢?让我们马上开始 iOS 26 液态玻璃工具栏大冒险吧! Let's go!!!;)


🍸 工具栏新体验:不改代码,也能自动变身

Liquid Glass 对工具栏的设计带来了如下革新:

  • 背景呈现出半透明玻璃质感;
  • 支持 多组分布,将按钮逻辑分组显示;
  • 自动应用新风格,无需修改旧代码。

不过,为了更上一层楼,我们依然可以使用新 API 进行更为细致的定制。


📦 基础示例:新旧兼容的优雅演绎

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                Text("Item 1")
                Text("Item 2")
                Text("Item 3")
            }
            .navigationTitle("Items")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel", systemImage: "xmark") {}
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("Done", systemImage: "checkmark") {}
                }
            }
        }
    }
}

运行结果如下图所示:

在这里插入图片描述

🧠 技术解析:
该示例依旧采用经典 .toolbar 修饰器,并结合 ToolbarItem 类型。Liquid Glass 推崇图标优先,因此按钮建议使用图文并茂样式,而不是纯文字。


🎭 保持兼容旧系统:labelStyle 定制样式

若宝子们仍需兼容旧版本的系统(例如 iOS 18 及之前),则我们可以保留文字工具栏的样式:

.labelStyle(.toolbar)

完整示例如下所示:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                Text("Item 1")
                Text("Item 2")
                Text("Item 3")
            }
            .navigationTitle("Items")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel", systemImage: "xmark") {}
                        .labelStyle(.toolbar)
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("Done", systemImage: "checkmark") {}
                        .labelStyle(.toolbar)
                }
            }
        }
    }
}

🧱 自定义 LabelStyle:向下兼容神器

上面的 Label 自定义样式 .toolbar 是如何做到的呢?下面我们就为宝子们揭晓答案:

struct ToolbarLabelStyle: LabelStyle {
    func makeBody(configuration: Configuration) -> some View {
        if #available(iOS 26, *) {
            Label(configuration)
        } else {
            Label(configuration)
                .labelStyle(.titleOnly)
        }
    }
}

@available(iOS, introduced: 18, obsoleted: 26, message: "Remove this property in iOS 26")
extension LabelStyle where Self == ToolbarLabelStyle {
    static var toolbar: Self { .init() }
}

📚 技术说明:

  • 为不同系统设置条件样式;
  • iOS 26 起设置为自动废弃,减少技术欠债;
  • labelStyle(.toolbar) 作为语义扩展点,增强代码整洁性。

✨ Placement 的魔法:位置决定样式

在 Liquid Glass 影响之下,工具栏中的 ToolbarItemPlacement 实参不仅决定按钮位置,也影响其风格!

.labelStyle(.toolbar)
.tint(.red) // 改变按钮颜色
.badge(3)   // 显示角标

示例:

ToolbarItem(placement: .confirmationAction) {
    Button("Done", systemImage: "checkmark") {}
        .labelStyle(.toolbar)
        .badge(3)
}

完整示例代码:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                Text("Item 1")
                Text("Item 2")
                Text("Item 3")
            }
            .navigationTitle("Items")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel", systemImage: "xmark") {
                        
                    }
                    .labelStyle(.toolbar)
                    .tint(.red)
                }
                
                ToolbarItem(placement: .confirmationAction) {
                    Button("Done", systemImage: "checkmark") {
                        
                    }
                    .labelStyle(.toolbar)
                    .badge(3)
                }
            }
        }
    }
}

在 Xcode 26 中,预览运行结果如下所示:

在这里插入图片描述

💡 温馨提示:
虽然 tintbadge 炫酷抢眼,但苹果建议少量使用,依旧推荐通过 placement 实现布局分离。因为让液态玻璃自行决定大显身手的场景才是“你好,她也好!”。


🧰 Toolbar 分组与 Spacer 控制

在 Liquid Glass 中,库克为我们引入了两个新能力:

  • ToolbarItemGroup:将按钮按逻辑进行组内布局;
  • ToolbarSpacer:在 toolbar 中灵活插入空隙,支持 .fixed.flexible 宽度。

示例:

struct ToolsToolbar: ToolbarContent {
    var body: some ToolbarContent {
        ToolbarItem(placement: .cancellationAction) {
            Button("Cancel", systemImage: "xmark") {}
        }

        ToolbarItemGroup(placement: .primaryAction) {
            Button("Draw", systemImage: "pencil") {}
            Button("Erase", systemImage: "eraser") {}
        }

        ToolbarSpacer(.flexible)

        ToolbarItem(placement: .confirmationAction) {
            Button("Save", systemImage: "checkmark") {}
        }
    }
}

🛠 工程亮点:

  • 更符合实际业务分工:主操作、次操作、取消操作分组;
  • Spacer 提供更强自适应布局能力,排兵布阵,自由挥洒

在这里插入图片描述


📌 总结:Liquid Glass 下的 Toolbar 重构指南

亮点 解说
自动玻璃化 工具栏无需更改代码,即刻享受 Liquid Glass 风格
图标优先 建议使用 systemImage + 文本
可扩展性 自定义 LabelStyle 支持老版本兼容
可分组 ToolbarItemGroup 组织逻辑结构
自适应布局 ToolbarSpacer 实现弹性间距
样式控制 支持 .badge.tint 等视觉修饰

📮 写在最后

工具栏的“玻璃化”不仅是视觉提升,更是对 UI/UX 一次脱胎换骨的优化。它让工具栏不再是生硬的控制面板,而是一个融入设计,功能与美感并存的现代化模块。

感谢观赏,再会啦!8-)

Swift 6.2 并发江湖:两大神功破局旧制,代码运行经脉革新(下)

在这里插入图片描述

楔子

江湖风云变幻,Swift 武林近日再掀波澜。

传闻 Apple 于密室推演三月,终得《Swift 6.2 并发新篇》,扬言要破解困扰开发者多年的 "经脉错乱" 之症 —— 那便是异步函数与同步函数运行规则不一、主 Actor 调用常生冲突之陈年旧疾。

在这里插入图片描述

想当年,多少英雄好汉折戟于 GCD 到 Swift 并发的转型之路:明明是同门函数,同步者循调用者经脉而行,异步者却偏要另辟蹊径,轻则编译器怒目相向,重则数据走火入魔。

如今 6.2 版本携 "nonisolated (nonsending)" 与 "defaultIsolation" 两大神功而来,声称要让代码运行如行云流水,再无经脉冲突之虞。

在本篇博文,您将学到如下内容:

  1. @concurrent:破界而行的旁门秘籍
  2. 何时启用 NonisolatedNonsendingByDefault?非必要不增乱
  3. defaultIsolation:主 Actor 门派的包级新规矩
  4. defaultIsolation 实战:隐式标注,逆势需明
  5. 逆势而行:如何脱离主 Actor?
  6. 该不该启用 defaultIsolation?利弊权衡,因地制宜

今日列位武林好汉且随老夫执剑开卷,一探这两门新功究竟有何玄妙,又将如何改写 Swift 并发的江湖格局吧。

Let‘s go!!!;)


6. @concurrent:破界而行的旁门秘籍

与 “nonisolated (nonsending)” 一同出现的,还有 “@concurrent” 这枚破界令牌。

有了这枚令牌,函数的行为就能和 Swift 6.1 旧制一样 —— 脱离调用者的经脉,建立新的隔离语境(专属经脉)。

请看示例:

@MainActor
class NetworkingClient {
    @concurrent
    nonisolated func loadUserPhotos() async throws -> [Photo] {
        return [Photo()]
    }
}

给函数标注@concurrent,就如同立下誓言:必定脱离调用者的 Actor,创建专属的隔离语境。

这枚令牌有一条铁律:只能用于 “nonisolated” 函数。如果将其用于 Actor 门派的招式,除非该招式明确标注 “nonisolated”,否则就是违规练功:

actor SomeGenerator {
    // 此为禁忌,不可如此
    @concurrent
    func randomID() async throws -> UUID {
        return UUID()
    }

    // 此为正道,可如此行
    @concurrent
    nonisolated func randomID() async throws -> UUID {
        return UUID()
    }
}

需要注意的是,在撰写本文时,这两种写法暂时都能运行,而且未标注 “nonisolated” 的@concurrent函数在运行时似乎没有隔离 —— 但根据《SE-0461 秘籍》的规定,这是 Swift 6.2 工具链的一个临时疏漏,日后定会修正,大家千万不要效仿前者,以免走火入魔。

在这里插入图片描述

7. 何时启用 NonisolatedNonsendingByDefault?非必要不增乱

在老夫看来,启用 “NonisolatedNonsendingByDefault” 令牌实乃明智之举。

它带来了一种新的工作方式:nonisolated 异步函数会跟随调用者的 Actor 运行,而非在独立的隔离语境中运行。实际上,这能减少很多编译器错误,而且根据本人的尝试,还能省去不少主 Actor 标注。

老夫一直主张减少应用中的并发量,只在确实需要时才引入 —— 这一令牌恰好能帮助宝子们实现这一点。在决定给应用中的所有内容都标注@concurrent以确保安全之前,请先问问自己是否真的有此必要。

其实,很可能根本没必要,而且从整体来看,并非所有代码都并发运行,这会让代码及其执行过程更容易理解。

当同时采用 Swift 6.2 的第二项主要特性 “defaultIsolation” 时,情况更是如此。

8. defaultIsolation:主 Actor 门派的包级新规矩

Swift 6.2 的另一大绝招 “defaultIsolation”,堪称主 Actor 门派的包级(package)新规。

在 Swift 6.1 中,代码只有在被@MainActor标注(或遵循标有@MainActor的协议)时,才会归入主 Actor—— 就像只有加入主 Actor 门派,才能学习主经脉的功法一样。

在这里插入图片描述

给代码标注@MainActor是解决编译器错误的常用方法,而且大多数情况下都是正确的做法。并非所有代码都需要在后台线程异步执行。那样做成本相对较高,往往对性能没有提升,还会让代码难以理解。

在采用 Swift 并发之前,各位秃头少侠们不会到处使用,那样做成本相对较高,往往对性能没有提升,反而还会让代码难以理解。

在采用 Swift 并发之前,小伙伴们不会到处使用DispatchQueue.global(),那现在又何必做类似的事情呢?

言归正传,在 Swift 6.2 中,我们可以在包级别(package)设置默认在主 Actor 上运行代码。这是《SE-0466 秘籍》引入的特性。

这意味着,UI 包、应用目标和模型包等,都能自动在主 Actor 上运行代码,除非通过@concurrent或自定义 Actor 明确选择不在主 Actor 上运行。

要启用这一特性,可在swiftSettings中设置defaultIsolation,或作为编译器参数传入:

swiftSettings: [
    .defaultIsolation(MainActor.self),
    .enableExperimentalFeature("NonisolatedNonsendingByDefault")
]

不必将defaultIsolationNonisolatedNonsendingByDefault一起使用,但在老夫的实验中,同时使用这两个选项效果很好。

在这里插入图片描述

9. defaultIsolation 实战:隐式标注,逆势需明

目前,可以将MainActor.self作为默认隔离传入,使所有代码默认在主 Actor 上运行;也可以传入nil以保持现有行为(或者根本不传入该设置,同样保持现有行为)。

在这里插入图片描述

启用这一特性后,Swift 会推断所有对象都带有@MainActor标注,除非明确指定其他情况:

@Observable
class Person {
    var myValue: Int = 0
    let obj = TestClass()

    // 如果 defaultIsolation 设置为主 Actor,此函数始终在主 Actor 上运行
    func runMeSomewhere() async {
        MainActor.assertIsolated()
        // 执行一些操作、调用异步函数等
    }
}

这段代码包含一个 nonisolated 异步函数。这意味着,默认情况下,它会继承调用runMeSomewhere的 Actor。如果从主 Actor 调用,它就在主 Actor 上运行;如果从另一个 Actor 或非 Actor 处调用,它就不在主 Actor 上运行。

这很可能完全不是预期的结果。

或许我们编写异步函数只是为了能调用其他需要等待的函数。如果runMeSomewhere不执行任何繁重的处理,我们可能希望Person在主 Actor 上。它是一个可观察类,很可能用于驱动 UI,这意味着几乎所有对该对象的访问都应该在主 Actor 上进行。

defaultIsolation设置为MainActor.self时,Person会被隐式标注@MainActor,因此Person的所有工作都在主 Actor 上运行。

10. 逆势而行:如何脱离主 Actor?

假设我们想给Person添加一个不在主 Actor 上运行的函数。可以像往常一样使用 nonisolated:

// 此函数将在调用者的 Actor 上运行
nonisolated func runMeSomewhere() async {
    MainActor.assertIsolated()
    // 执行一些操作、调用异步函数等
}

如果想确保函数绝对不在主 Actor 上运行则可以这样做:

// 此函数将在调用者的 Actor 上运行
@concurrent
nonisolated func runMeSomewhere() async {
    MainActor.assertIsolated()
    // 执行一些操作、调用异步函数等
}

对于每个想要设置为 nonisolated 的函数或属性,都需要明确选择不使用主 Actor 推断;不能为整个类型进行这样的设置。

当然,自定义的 Actor 不会突然开始在主 Actor 上运行,而且标注了其它全局 Actor 的类型也不会受到这一变化的影响。

11. 该不该启用 defaultIsolation?利弊权衡,因地制宜

这个问题很难回答。老夫的初步想法是 “应该启用”。

对于应用目标、UI 包和主要存放视图模型的包,默认在主 Actor 上运行无疑是正确的选择。

在这里插入图片描述

不过,列位仍然可以在需要的地方引入并发,而且会比以往更具目的性。

那么本篇的内容就要告一段落了!大家学“废”了吗?

最后,感谢大家的观看,再会啦!8-)

❌