普通视图

发现新文章,点击刷新页面。
今天 — 2025年11月17日iOS
昨天 — 2025年11月16日iOS

老司机 iOS 周报 #358 | 2025-11-17

作者 ChengzhiHuang
2025年11月16日 20:28

ios-weekly
老司机 iOS 周报,只为你呈现有价值的信息。

你也可以为这个项目出一份力,如果发现有价值的信息、文章、工具等可以到 Issues 里提给我们,我们会尽快处理。记得写上推荐的理由哦。有建议和意见也欢迎到 Issues 提出。

新闻

objc4-950 源码更新

Runtime 的源码发布新版本,主要更新的是 Xcode26 的 objc_storeStrong 的逻辑,有兴趣可以自行查看。

App Store Mini App Partner Program 隆重推出

参与计划的开发者在销售符合条件的 App 内购买项目时,可享受 15% 的收益抽成减免。需要适配 Advanced Commerce API (ACA) 。微信已经发公告预计会进行接入,期待后续更多中国公司都能同样谈成优惠,让更多增值业务在 iOS 端上线。

文章

🌟 🐢 Optimize your app's speed and efficiency | Meet with Apple

@Smallfly:该视频围绕应用性能优化展开,结合苹果开发中心的实践场景,系统讲解了从 Swift UI 渲染到数据流管理的多维度优化策略。核心内容包括:

  • 工具与调试:通过 Xcode 实时观察界面渲染状态(黄色闪烁标记),使用功耗分析工具量化优化效果;推荐 Instruments 分析视图更新效率,明确性能瓶颈。
  • 技术实践:针对 Swift UI 提出减少视图更新的关键方法——避免存储闭包、初始化时预计算视图结果;利用 Observable 类优化数据流,降低卡片视图对状态变量的依赖。
  • 性能指标:强调监控启动时间、镜头应用延迟等关键指标,通过自动化测试发现问题;结合 Snap 的实践案例,说明团队协作与指标导向对维护应用健康的重要性。

视频通过代码演示与数据对比,为开发者提供了从工具使用到工程实践的全链路优化指南,特别提到 Liquid Glass 和 SwiftUI 的优化,推荐有兴趣的同学按需观看。

🐕 何时组建计算机性能工程团队(2025 年)第 1 部分(共 2 部分

@含笑饮砒霜:作者在文章中分享了非厂商性质技术密集型企业组建性能工程团队的核心建议,指出该团队能通过基础设施成本节约、延迟降低、可扩展性与可靠性提升、工程速度加快实现显著投资回报(如初期可减半基础设施成本,长期每年目标 5%-10% 成本节约),详细说明了性能工程师在新产品测试调优、内部工具开发、瓶颈分析、参数优化等十大核心职责,并给出组建时机与团队规模的参考规则(基础设施年支出超 100 万美元配 1 名工程师,后续每 1000-2000 万美元增 1 名;团队支出应不低于可观测性监测支出;延迟或可靠性阻碍增长时需组建),同时提及部分企业已有相关专职人员可纳入考量,后续第二部分将补充职位描述、潜在陷阱等内容。

🐕 TN3193: Managing the on-device foundation model’s context window

@JonyFang: Apple 在这篇技术说明中系统讲解了设备端基础模型的上下文窗口限制以及开发者应如何应对。文章强调:本地模型不会自动截断超长输入,超过窗口会直接报错,因此必须在应用设计中主动“预算”与管理上下文。Apple 建议使用三类策略来保持对话连续性同时不溢出窗口:

  • 滑动窗口:只保留最新内容,旧内容逐步丢弃;
  • 机会型摘要:上下文接近上限时触发自动总结,把详细内容压缩成短摘要继续对话;
  • 选择性保留 / 层级压缩:按重要性保留关键信息,把次要内容丢弃或按主题分层压缩,需要时再检索。

整体来看,TN3193 的核心信息是:Apple 设备端模型的上下文有限,开发者必须自行设计“记忆管理策略”,否则会遇到输入过长错误。通过“滑动 + 摘要 + 保留”组合策略,可在有限窗口内维持长对话与复杂任务的质量。

🐕 Demystifying AI Coding Agents in Swift

@Cooper Chen:这篇文章不仅手把手带你构建了一个可工作的 Swift AI Coding Agent,更重要的是,它用非常清晰、务实的方式揭开了 "AI 编码助理" 背后的底层原理。作者强调:这些看似强大的智能行为,其实都是从「语言模型 + 工具 + 循环」这三件极其简单的事组合而成,让人一下子从使用者变成真正理解者。

文章最大的价值在于 去魅 + 实操。它不讲虚的,不堆概念,而是用不到 300 行的 Swift 代码,就实现了一个能读文件、写代码、重构逻辑、与用户来回对话的 Coding Agent,让读者第一次意识到 Cursor、Claude Code 这类产品背后并没有不可思议的魔法。
与此同时,作者也展示了真实工程中会遇到的问题:上下文膨胀、循环保护、安全、错误处理、工具设计等,让内容不仅能“跑起来”,还具备工程实用性。

如果你想理解 Coding Agent 的本质,或者想自己打造一个轻量但功能完整的 Swift Agent,这篇文章绝对值得一读。它让复杂的概念变得透明,让看似神秘的 AI 能力真正变成可掌握、可自行构建的技术。

🐢 Roadmap for Improving the Type Checker

@AidenRao:你是否也曾被 the compiler is unable to type-check this expression in reasonable time 的错误困扰?Swift 编译器团队最近发布了一份详细的路线图,旨在系统性地解决这一由来已久的问题。文章深入浅出地解释了类型检查慢的根源——由类型推导和重载解析带来的指数级复杂度。
路线图不仅展示了近期 Swift 6.2 和 6.3 在编译速度上取得的显著成果(真实项目检查时间从 42 秒降至 10 秒),还规划了未来的改进方向:包括加速大型集合字面量检查、移除历史性能 Hack,乃至引入更先进的 SAT 求解技术。如果你对 Swift 编译性能的未来走向感兴趣,这篇文章值得一读。

🐎 来了解一下,为什么你的 Flutter WebView 在 iOS 26 上有点击问题?

@david-clang:iOS 26 上 WebView 点击失效,核心仍是 iOS 18.2 起 WKWebView 的手势状态缓存问题。

当 WebView 被 Flutter overlay 遮挡时,Flutter 通过 delayingGestureRecognizer 延迟 overlay 下方的 UIKit recognizer,使其暂时不触发,从而让 overlay 接管触摸。但 iOS 18.2 起 WKWebView 的手势状态缓存问题导致 overlay 消失后,WKWebView 内部的点击 recognizer 状态仍停留在延迟状态,未能恢复到 recognized,tap 或 JS click 无法派发,元素只能高亮却无法响应点击。

解决方案:

  • 短期规避:使用 pointer_interceptor 在 WebView 上方覆盖一个透明层,阻止 overlay 引发的手势中断,从而避免点击失效。
  • 长期方案:Flutter 官方正在弃用 delayingRecognizer,改为基于 hitTest + FFI 同步判断 的手势体系,在触点处直接判断是否应拦截手势,从根本解决 WebView、AdMob 等 PlatformView 的手势冲突问题。

代码

🐢 Swift Binary Parsing

@阿权:Apple 官方开源的二进制解析库,使用纯 Swift 编写,旨在构建安全、高效的二进制解析。该库提供了一系列用于安全解析二进制数据的工具,同时管理类型和内存安全,以消除常见的基于值的未定义行为,例如类型溢出。

Swift 一直致力于将不安全的内存操作尽可能安全地让用户访问、修改,此库跟 Swift 的思想如出一辙,本来之前 Apple 强推用 Swift 替代 C/C++ 直接操作内存,包括嵌入式也是这个切入点,此库一出如同如虎添翼了,也算是给内存操作提供一套完整的最佳实践了。

内推

重新开始更新「iOS 靠谱内推专题」,整理了最近明确在招人的岗位,供大家参考

具体信息请移步:https://www.yuque.com/iosalliance/article/bhutav 进行查看(如有招聘需求请联系 iTDriverr)

关注我们

我们是「老司机技术周报」,一个持续追求精品 iOS 内容的技术公众号,欢迎关注。

关注有礼,关注【老司机技术周报】,回复「2024」,领取 2024 及往年内参

同时也支持了 RSS 订阅:https://github.com/SwiftOldDriver/iOS-Weekly/releases.atom

说明

🚧 表示需某工具,🌟 表示编辑推荐

预计阅读时间:🐎 很快就能读完(1 - 10 mins);🐕 中等 (10 - 20 mins);🐢 慢(20+ mins)

昨天以前iOS

微信与苹果就小程序支付达成和解,iOS用户有望在小程序内直接使用苹果支付

作者 CocoaKier
2025年11月14日 19:19
今日,苹果公司正式发布《小程序合作伙伴计划》,为长期悬而未决的iOS小程序支付问题画上句号。这一官方公告,标志着小程序在苹果生态中的地位获得正式认可,同时也为开发者(宿主App)指明了清晰的合规路径。

Grow on iOS 26:UIKit + SwiftUI 混合架构下的 Liquid Glass 适配实战

作者 Fatbobman
2025年11月12日 22:12

Grow 是一款在 173 个国家和地区获得 App Store 编辑推荐、拥有超过 18 万五星评价的健康管理应用。在适配 iOS 26 的 Liquid Glass 设计语言时,团队遇到了不少挑战:如何在 UIKit + SwiftUI 混合架构下实现原生的 morph 效果?如何精确控制 Scroll Edge Effect?如何处理自定义导航栏元素的动态尺寸?我邀请了 Grow 的开发者之一 Shuhari,分享团队在这次适配过程中的实战经验。文章涵盖 Sheet、Navigation、Popover 等场景的改造方案,深入探讨 UIBarButtonItem 尺寸计算、CABackdropLayer 副作用处理等底层细节,还展示了如何利用 Core Text 创造“玻璃文字”效果。

SwiftUI 导航

作者 littleplayer
2025年11月11日 17:49

SwiftUI 提供了多种导航方式,让我为你详细介绍主要的导航模式和相关组件。

1. NavigationStack (iOS 16+)

基本用法

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("前往详情页", value: "详情内容")
                NavigationLink("设置", value: "设置页面")
            }
            .navigationDestination(for: String.self) { value in
                DetailView(content: value)
            }
        }
    }
}

struct DetailView: View {
    let content: String
    
    var body: some View {
        Text("详情: \(content)")
            .navigationTitle("详情页")
    }
}

多类型导航

enum Route: Hashable {
    case product(Int)
    case category(String)
    case settings
}

struct ContentView: View {
    var body: some View {
        NavigationStack {
            VStack {
                NavigationLink("产品123", value: Route.product(123))
                NavigationLink("电子产品", value: Route.category("electronics"))
                NavigationLink("设置", value: Route.settings)
            }
            .navigationDestination(for: Route.self) { route in
                switch route {
                case .product(let id):
                    ProductView(productId: id)
                case .category(let name):
                    CategoryView(category: name)
                case .settings:
                    SettingsView()
                }
            }
        }
    }
}

2. NavigationView (iOS 13-16)

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: DetailView()) {
                    Label("详情页面", systemImage: "star")
                }
                NavigationLink(destination: SettingsView()) {
                    Label("设置", systemImage: "gear")
                }
            }
            .navigationTitle("主页面")
        }
    }
}

3. 编程式导航

使用 NavigationPath

struct ContentView: View {
    @State private var navigationPath = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $navigationPath) {
            VStack(spacing: 20) {
                Button("跳转到产品页") {
                    navigationPath.append(Route.product(456))
                }
                
                Button("跳转到分类页") {
                    navigationPath.append(Route.category("books"))
                }
                
                Button("多层级跳转") {
                    navigationPath.append(Route.category("electronics"))
                    navigationPath.append(Route.product(789))
                }
                
                Button("返回根页面") {
                    navigationPath.removeLast(navigationPath.count)
                }
                
                Button("上一步") {
                    guard !navigationPath.isEmpty else { return }
                    navigationPath.removeLast()
                }
            }
            .navigationDestination(for: Route.self) { route in
                // 路由处理...
            }
        }
    }
}

4. Sheet 和 FullScreenCover

模态展示

struct ContentView: View {
    @State private var showingSheet = false
    @State private var showingFullScreen = false
    
    var body: some View {
        VStack(spacing: 20) {
            Button("显示 Sheet") {
                showingSheet = true
            }
            
            Button("全屏显示") {
                showingFullScreen = true
            }
        }
        .sheet(isPresented: $showingSheet) {
            SheetView()
        }
        .fullScreenCover(isPresented: $showingFullScreen) {
            FullScreenView()
        }
    }
}

struct SheetView: View {
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        NavigationView {
            Text("这是 Sheet 视图")
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button("完成") {
                            dismiss()
                        }
                    }
                }
        }
    }
}

5. TabView 标签导航

struct MainTabView: View {
    var body: some View {
        TabView {
            HomeView()
                .tabItem {
                    Label("首页", systemImage: "house")
                }
            
            SearchView()
                .tabItem {
                    Label("搜索", systemImage: "magnifyingglass")
                }
            
            ProfileView()
                .tabItem {
                    Label("我的", systemImage: "person")
                }
        }
    }
}

6. 复杂导航示例

带导航栏的完整示例

struct MainView: View {
    @State private var navigationPath = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $navigationPath) {
            ProductListView()
                .navigationTitle("产品列表")
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button("设置") {
                            navigationPath.append(AppRoute.settings)
                        }
                    }
                }
                .navigationDestination(for: AppRoute.self) { route in
                    route.destination
                }
        }
    }
}

enum AppRoute: Hashable {
    case productDetail(Product)
    case category(String)
    case settings
    case profile
    
    @ViewBuilder
    var destination: some View {
        switch self {
        case .productDetail(let product):
            ProductDetailView(product: product)
        case .category(let category):
            CategoryView(category: category)
        case .settings:
            SettingsView()
        case .profile:
            ProfileView()
        }
    }
}

自定义导航栏

struct CustomNavigationView: View {
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        NavigationView {
            Text("自定义导航栏")
                .navigationTitle("标题")
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    // 左侧按钮
                    ToolbarItem(placement: .navigationBarLeading) {
                        Button("取消") {
                            dismiss()
                        }
                    }
                    
                    // 右侧按钮
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button("保存") {
                            // 保存操作
                        }
                        .bold()
                    }
                }
        }
    }
}

7. 导航状态管理

class NavigationManager: ObservableObject {
    @Published var path = NavigationPath()
    
    func navigateToProduct(_ id: Int) {
        path.append(Route.product(id))
    }
    
    func navigateToCategory(_ name: String) {
        path.append(Route.category(name))
    }
    
    func popToRoot() {
        path.removeLast(path.count)
    }
}

struct AppView: View {
    @StateObject private var navManager = NavigationManager()
    
    var body: some View {
        NavigationStack(path: $navManager.path) {
            ContentView()
                .navigationDestination(for: Route.self) { route in
                    // 路由处理
                }
        }
        .environmentObject(navManager)
    }
}

主要特点总结

  1. NavigationStack: iOS 16+ 推荐使用,支持类型安全的路由
  2. 编程式导航: 通过状态管理控制导航流程
  3. 模态展示: Sheet 和 FullScreenCover 用于临时内容
  4. 标签导航: TabView 用于主要功能模块切换
  5. 灵活的路由系统: 支持复杂导航逻辑和深度链接

这些导航方式可以组合使用,创建出符合你应用需求的完整导航体验。

Swift 并发:我到底该不该用 Actor?——一张决策图帮你拍板

作者 unravel2025
2025年11月11日 08:20

Actor 是什么?(一句话版)

Actor = 自带大门的房间:一次只能进一个人,进门要“等钥匙”(await)。

它存在的唯一理由:保护非 Sendable 的可变状态。

Actor vs Class:只差一个隔离域

维度 Class Actor
引用语义
继承
隔离域 ❌(谁都能同步访问) ✅(必须 await进门)
线程安全 手动锁/队列 编译器保证
同步调用 任意 外部禁止

把 Actor 想成“远程服务”: 数据在“服务器”里,你要发请求(await)才能读写。

决策三要素:缺一不可!

只有同时满足下面 3 条,才值得上 Actor:

  1. 有非 Sendable 状态

    (纯 Sendable 结构体/类 → 无需保护)

  2. 操作必须原子性

    (读-改-写必须打包,不能中途被插)

  3. 这些原子操作

    不能在现有 Actor 上完成(如 MainActor)

缺一条 → 用别的方式 原因
只有 ① 缺 ② 用 Sendable+ 值类型即可
有 ①② 但能在 MainActor 做 直接标 @MainActor,还能同步访问 UI
为了“避开主线程”而造 Actor 反模式!用 @concurrentTask.detached即可

反例集合:这些 Actor 都“师出无名”

❌ 网络客户端 Actor

actor APIClient {
    // 全是 Sendable:URLSession、tokenString
    func request() async -> Data { ... }
}
  • 状态已 Sendable → 无需保护
  • 副作用只是“不想跑主线程”→ 用 @concurrent 函数即可
  • 结果:人为加锁,解码都无法并发

❌ “看不懂并发报错”就套 Actor

@globalActor actor MyRandomActor {
    // 空状态,只为消 Sendable 警告
}

→ 永远别用 Actor 当创可贴!

先理解警告,再选工具(Sendable@MainActor@concurrent)。

正例:真正需要 Actor 的场景

✅ 本地非 Sendable 缓存

actor ImageCache {
    private var store: [URL: UIImage] = [:]   // UIImage 非 Sendable
    func image(for url: URL) async -> UIImage? {
        if let img = store[url] { return img }
        let data = try await URLSession.shared.data(from: url).0
        let img = UIImage(data: data)!
        store[url] = img
        return img
    }
}
  • 状态非 Sendable
  • 读-写-缓存必须原子
  • MainActor 不适合(网络+解码耗时)

✅ 协议强制 Sendable

protocol DataSource: Sendable {
    func fetch() async -> [Item]
}
  • 实现层含非 Sendable 状态 → 只能用 Actor 满足 Sendable

决策流程图

需要共享可变状态?
├─ 否 → 用 struct / class(Sendable)
├─ 是 → 状态 Sendable?
│   ├─ 是 → 用 Sendable 值类型或锁自由类
│   └─ 否 → 操作必须原子?
│       ├─ 否 → 拆成 Sendable 片段
│       └─ 是 → 能在 MainActor 完成?
│           ├─ 是 → @MainActor
│           └─ 否 → **上 Actor** ✅

口诀:

“Sendable 先,MainActor 其次,新 Actor 最后。”

同步访问红线:Actor = “远程服务”

外部调用必须异步:

actor Counter {
    func increment() { value += 1 }
}

// 外部
await counter.increment()   // ✅
counter.increment()         // ❌ 编译失败

→ 如果你无法容忍这种异步接口(例如实时音频回调),

根本不该用 Actor —— 考虑锁、原子类或 @concurrent 函数。

常见误解速答

误解 真相
“Actor 让并发更快” 它更安全而非更快;异步排队可能更慢
“把类改成 actor 就能消并发警告” 治标不治本;先理解 Sendable 要求
“网络层必须 actor” 若状态 Sendable,用 @concurrent函数/任务即可
“actor 里所有代码都异步” 内部可完全同步;只有外部调用需 await

一句话总结

“Actor 是保护‘非 Sendable 可变状态’的昂贵保险箱—— 确认你真的有宝贝,且别处放不下,再把它请回家。”

记住三要素:

  1. 非 Sendable 状态
  2. 必须原子操作
  3. 现有 Actor 帮不上

同时满足 → 用 Actor;缺一条 → 找更简单的工具。

让 Actor 留在真正需要串行大门的地方,别把远程服务的复杂度,带进本可并行的小花园。

❌
❌