前言

本期是 Swift 编辑组自主整理周报的第十六期,每个模块已初步成型。各位读者如果有好的提议,欢迎在文末留言。

欢迎投稿或推荐内容。目前计划每两周周一发布,欢迎志同道合的朋友一起加入周报整理。

选择与放弃,是生活与人生处处需要面对的关口。选择了Swift社区,就拥有了一道最靓丽的风景!

周报精选

新闻和社区:印度将首次成为苹果公司自有销售地区

提案:提案 SE-0382、SE-0388、SE-0389 通过审查

Swift 论坛:提议 Observation 修订

推荐博文:两个新的开源 Swift 库:Swift CertificatesSwift ASN.1

话题讨论:

文心一言挑战 ChatGPT,谁更胜一筹?

新闻和社区

印度将首次成为苹果公司自有销售地区

【环球网科技综合报道】3 月 9 日消息,据外媒报道,苹果公司正在改变国际业务的管理方式,推动印度首次成为其自有销售区。

据悉,负责苹果印度、中东、地中海、东欧和非洲地区业务的副总裁休斯·阿斯曼(HuguesAsseman)退休后,苹果计划将印度调整成自有销售区域。而阿希什·乔杜里(AshishChowdhary)将升职,成为印度地区的负责人,直接向苹果的产品销售负责人汇报。

外媒分析称,作为世界第二大智能手机市场,印度市场对于苹果而言越来越重要。2022 年第 四 季度,苹果在印度市场的销售额创新高。

目前,苹果已开始在印度生产一些 iPhone 型号,包括 iPhone14 。此外,苹果计划于今年晚些时候在该国开设第一家零售店。

Meta第二轮裁员波及万人,苹果推迟发放奖金

硅谷的裁员、降薪潮远未结束。

3 月 14 日周二,Meta 和苹果纷纷宣布最新的人事政策。

周二,Meta 的第二轮裁员终于确定,将裁员大约一万人,同时还将关闭 5000 个额外空缺职位,以求节省开支、提高效率。这是 Meta 在六个月时间里的第二轮重大裁员行动。

Meta 的首席执行官马克·扎克伯格周二在声明中表示,该公司的目标是通过取消管理层的多层级结构来让公司变得更加扁平化。

周二,Meta 高开高走,当前股价上涨 5.93% ,报 191.62 美元。

上个月,有媒体报道称,Meta 还一直致力于扁平化其组织,向管理人员提供买断方案,并裁减其认为不必要的整个团队,此举仍在最后敲定中,可能会影响数千名员工。

知情人士称,Meta 即将到来的新一轮裁员是由财务目标推动的,与公司“扁平化”没有直接关系。知情人士说,Meta 已经看到广告收入放缓,并将重点转移到元宇宙平台,公司高层一直在要求董事和副总裁列出可以解雇的员工名单。

据知情人士透露,这一阶段的裁员最快可能会在本周完成。一位知情人士说,那些正在制定裁员计划的人希望在首席执行官扎克伯格为他的第三个孩子休育儿假之前准备好,因此这次裁员的速度可能会非常快。

App Store 的定价机制升级现已扩展至所有购买类型

在 12 月,我们宣布对 App Store 进行问世至今最全面的定价机制升级,其中包括新增价格点和按店面管理定价的全新工具。即日起,这些升级和新价格适用于所有类型的 App 和 App 内购买项目,包括付费 App 和一次性 App 内购买项目。

更为灵活的价格点。可在 900 个价格点中选择定价 — 比此前付费 App 和一次性 App 内购买项目的可选价格点数量增加了近 9 倍。这些选项也提供了更高的定价灵活度,价格点按价格区间逐渐递增 (如在 RMB 10 以下每档相差 RMB 0.5;RMB 10 到 RMB 200 之间每档相差 RMB 1 等)。

增强的全球定价机制。全球均衡价格遵循了各个国家或地区最常见的定价方式。采用全球均衡价格,你可以提供更适用于当地顾客的定价。

根据基准价格提供全球定价形式。针对付费 App 和一次性 App 内购买项目,指定你熟悉的国家或地区,以之为基础为其他 174 个国家或地区的店面以及 43 种货币生成全球均衡价格。你为这个基准店面设定的价格,Apple 不会根据税款或外汇变化进行调整。此外,你也可以按个人喜好为每个店面自行设定价格。

为上架产品提供地区性定价方案。针对不同国家和地区的店面决定 App 内购买项目 (包括订阅) 的销售范围,因此你可以为各个市场分发定制的内容和服务。

准备好迎接即将在 5 月推出的增强全球定价机制
App Store 的全球均衡价格工具为你提供了一种简单便捷的方式来管理国际市场的定价。在 2023 年 5 月 9 日,在所有 175 个 App Store 店面中的现有 App 和一次性 App 内购买项目的价格都将更新,以充分利用此次全新的增强全球定价机制。更新后的价格将根据金融数据机构提供的公开汇率信息做调整,在全球范围内与你为基准店面设定的价格保持平衡。这些价格点将跟随各个国家或地区最常见的定价方式,让价格更适用于当地顾客。

你即刻就能使用 App Store Connect 或 App Store Connect API 更新你的当前定价,以充分利用此次全新升级。在 5 月 9 日,如果你的现有 App 和一次性 App 内购买项目还没有完成价格更新,Apple 将以产品当前在美国店面的价格为基础,为它们生成相应的更新价格。如果你希望以其他价格作为基础,现在可通过更新基准店面的国家或地区,为 App 或 App 内购买项目选择新的基准店面。你还可以选择手动管理所选店面中的价格,而不使用均衡的价格。

提案

通过审查的提案

SE-0382 Expression Macros 提案通过审查。该提案已在 二十期周报 正在审查的提案模块做了详细介绍。

SE-0388 增加 Async[Throwing]Stream.makeStream 方法 提案通过审查。该提案已在 二十四期周报 正在审查的提案模块做了详细介绍。

SE-0389 Attached Macros 提案通过审查。该提案已在 二十四期周报 正在审查的提案模块做了详细介绍。

正在审查的提案

SE-0392 自定义 Actor 执行器 提案正在审查。

该提案介绍了一种用于自定义 actor 执行程序的基本机制。通过提供执行者的实例,参与者可以影响他们将在什么地方执行正在运行的一些任务。

注意: 该提案仅定义了一组 API 来自定义 actor 执行器,其他类型的执行器控制超出了该特定提案的范围。

Swift论坛

  1. 提议Observation(修订)

介绍

制作响应式应用程序通常需要能够在基础数据发生变化时更新演示文稿。 观察者模式允许一个主题维护一个观察者列表,并通知他们特定的或一般的状态变化。 这具有不直接将对象耦合在一起并允许在潜在的多个观察者之间隐式分布更新的优点。 可观察对象不需要关于其观察者的特定信息。

这种设计模式是许多语言都走过的一条很好的道路,Swift 有机会提供一个健壮的、类型安全的和高性能的实现。 该提议定义了什么是可观察引用、观察者需要遵守什么以及类型与其观察者之间的联系。

动机

Swift 中已经有一些观察机制。 其中包括键值观察 (KVO) 和 ObservableObject,但它们中的每一个都有局限性。 KVO 只能与 NSObject 后代一起使用,而 ObservableObject 需要使用 Combine,它仅限于 Darwin 平台并且不使用当前的 Swift 并发功能。 通过从这些现有系统中吸取经验,我们可以构建一个更普遍有用的功能,适用于所有 Swift 引用类型,而不仅仅是那些继承自 NSObject 的引用类型,并使其跨平台工作,并具有 async/await 等语言功能的优势。

现有系统获得了许多正确的行为和特征。 但是,有许多领域可以在安全性、性能和表现力之间提供更好的平衡。 例如,将相关的更改分组到一个独立的事务中是一项常见的任务,但是这在使用 Combine 时很复杂并且在使用 KVO 时不受支持。 在实践中,观察者想要访问交易,并能够指定如何解释交易。

注释阐明了可观察的内容,但也可能很麻烦。 例如,Combine 不仅要求类型符合 ObservableObject,还要求被观察的每个属性都标记为 @Published。 此外,无法直接观察计算出的属性。 实际上,在可观察的类型中具有非观察字段并不常见。

在整个文档中,对 KVO 和 Combine 的引用将说明哪些功能是有益的并且可以合并到新方法中,以及可以以更稳健的方式解决哪些缺点。

现有技术

KVO

Objective-C 中的键值观察很好地服务于该模型,但仅限于从 NSObject 继承的类层次结构。 API 仅提供事件拦截,这意味着更改通知位于 willSet 和 didSet 事件之间。 KVO 在事件粒度方面具有很大的灵活性,但缺乏可组合性。 KVO 观察者还必须继承自 NSObject,并依赖 Objective-C 运行时来跟踪发生的变化。 尽管 KVO 的接口已经更新为使用更现代的 Swift 强类型键路径,但在底层它的事件仍然是字符串类型的。

Combine

Combine 的 ObservableObjectwillSet/didSet 事件的前缘产生变化,所有的值都在值被设置之前传递。 虽然这很好地服务于 SwiftUI,但它对非 SwiftUI 的使用有限制,并且对于第一次遇到该限制的开发人员来说可能会感到惊讶。 ObservableObject 还要求将所有观察到的属性标记为 @Published 以与更改事件进行交互。 在大多数情况下,这一要求适用于每一个单独的属性,对开发者来说变得多余; 编写符合 ObservableObject 类型的人必须重复(几乎没有真正获得清晰度)注释每个属性。 最后,这会导致对参与项目或不参与项目的意义疲劳。

我们提出了一个名为 Observation 的新标准库模块,其中包括实现这种模式的协议、类型和宏。
基本上,一个类型可以简单地通过使用 @Observable 宏注解将自己声明为可观察的:

@Observable public final class MyObject {
    public var someProperty: String = ""
    public var someOtherProperty = 0
    fileprivate var somePrivateProperty = 1
}

@Observable 宏声明并实现与 Observable 协议的一致性,该协议包括一组处理观察的扩展方法。 在最简单的情况下,客户端可以使用 changes(for:) 方法来观察给定实例的特定属性的变化。

func processChanges(_ object: MyObject) async {
    for await value in object.values(for: \.someProperty) {
        print(value)
    }
}

这允许 Observable 类型的用户将特定值的更改或整个实例作为更改事件的异步序列进行观察。 changes(for:) 方法提供类型安全,因为它只提供对一个特定属性的更改。

object.someProperty = "hello" 
// prints "hello" in the awaiting loop
object.someOtherProperty += 1
// nothing is printed

可观察对象还可以提供分组到事务中的更改,这些事务合并了在暂停点之间进行的任何更改。 默认情况下,交易会单独交付给您提供的参与者或主要参与者。

func processTransactions(_ object: MyObject) async {
    for await change in objects.changes(for: [\.someProperty, \.someOtherProperty]) {
        print(myObject.someProperty, myObject.someOtherProperty)
    }
}

ObservableObject@Published 不同,@Observable 类型的属性不需要单独标记为可观察。 相反,所有存储的属性都是隐式可观察的。
对于只读计算属性,作者可以添加 static dependencies(of:) 方法来声明额外的关键路径作为他们观察的一部分。 这类似于 KVO 用来提供对键路径有影响的附加键路径的机制。

extension MyObject {
    var someComputedProperty: Int { 
        somePrivateProperty + someOtherProperty
    }

    nonisolated static func dependencies(
        of keyPath: PartialKeyPath<Self>
    ) -> TrackedProperties<Self> {
        switch keyPath {
        case \.someComputedProperty:
            return [\.somePrivateProperty, \.someOtherProperty]
        default:
            return [keyPath]
        }
    }
}

由于所有对观察变化的访问都是通过关键路径进行的,因此 public 和 private 等可见性关键字决定了可以观察到什么,不能观察到什么。 与 KVO 不同,这意味着只能观察到在特定范围内可访问的成员。 这一事实反映在设计中,其中事务表示为 TrackedProperties 实例,它允许查询更改的键路径,但不能查询它们的迭代。

  1. 提议在 Swift 6 中省略 some

介绍

目前,在类型位置中编写普通协议名称的默认值是 any,但其中许多隐含的 any 用法可以替换为 some,从而为它们提供更多类型信息,同时仍能正确运行。 该提议翻转了默认值,以便在编写普通协议时,类型将默认为 some 而不是 any。 使默认值 some 保证固定的底层类型,它保留与底层类型的静态类型关系,使您可以完全访问使用 Self 和关联类型的协议要求和扩展方法。

动机

一段时间以来,Swift 一直致力于改进泛型的 UI,在 Swift 5.1 中引入了 some 关键字来表示不透明类型——特定具体类型的抽象类型占位符——它在 Swift 5.7 类型中扩展到参数,这样:

func foo<T>(_ bar: T) where T: Equatable { }

// is equivalent to 

func foo(_ bar: some T) { }

Swift 5.6 引入了 explicit any,这在 Swift 6 语言模式中是必需的,以确保选择类型擦除而不是使用类型参数是明确和深思熟虑的。 引入使用显式 any 的要求并鼓励默认编写一些,这为将一些作为普通协议名称的默认值提供了机会。

通用代码已经在比您想象的更多的地方得到了简化。 以协议扩展为例。 要编写适用于任何符合协议的具体类型的通用代码,您只需编写扩展关键字和普通协议名称。 在此示例中,我们使用 Collection 协议:

// What you write to extend all concrete types conforming to 'Collection':
extension Collection { ... }

在通用代码中,通用签名表示通用参数和对这些参数的任何要求。 在协议扩展的情况下,有一个名为 Self 的隐式类型参数和单一一致性要求 Self: Collection 由编译器添加,而不需要您编写。 这允许您从符合要求的 Self 类型上的 Collection 协议访问所有协议要求、关联类型和其他扩展方法。

当存在不需要使用 where 子句内化泛型签名的垫脚石时,程序员学习泛型编程会容易得多。 程序员可以使用更直接、更直观的语法来表达同一事物。 Swift 6 可以将同样的原则应用于其他上下文中的普通协议名称。 这种做法对于学习 Swift 的初学者来说可能是无价的,它消除了在向代码中添加协议时比较某些和任何之间的权衡的心理负担。 初学者不需要在使用 some 还是 any 之间做出决定,推迟完全理解语义差异的需要,直到绝对有必要在两者之间进行选择。 即使您不是初学者,一些默认设置仍然可以通过使代码更简洁来提高代码的可读性。

  1. 讨论并发性能真的很差以及如何优化代码

内容大概:

我们正在对各种编程语言的并发性进行比较,并使用 Swift 实现一维热方程求解器。 与 Python、Rust 和 C++ 相比,swift 的性能看起来不是很好。
首先,代码最多只能扩展到三个内核,见下文

核心,总时间

10,2111.423936009407
8,2189.256893992424
5,1967.6182420253754
4,1929.6173659563065
2,3097.796007990837
1,4388.57520699501

然而,每秒的浮点运算相当低,例如在三个内核上我们每秒可以进行 500 次浮点运算。 与其他语言相比,这并不多。

所以,既然我们想发布结果,我想寻求一些帮助,因为我认为我们在 Swift 中的实现并不好。

Vote最多的回答:

这种问题对于简单的并发或多线程来说通常是一个非常糟糕的情况:

您的工作集太大。 在 2 * 10000000 * MemoryLayout(Double).size,你有一个 160MB 的工作集,它不适合缓存,所以你实际上受到内存速度的限制,而不是计算速度,这 与额外资源的扩展几乎不一样。

如果你用较小的工作集解决了这个问题,你就会被数据局部性所困扰。 您确实希望将每个 worker 固定到数据的固定部分,因此它会在下一个时间步位于与 worker 关联的缓存中。

  1. 讨论@State 没有正确初始化

  2. 讨论Swift 中的轻量级 MVVMLight 架构模式

  3. 讨论协议类型里的 Generic“where” 失效

  4. 讨论如何使用 defer 模拟 RAII

内容大概

Swift 的 defer 语句具有很好的模拟 C++ 的资源获取即初始化行为的能力,由于 ARC,我们无法做到这一点。 如果您正在使用 UnsafeMutablePointer 通过确保在退出范围时正确清理资源来执行某些操作,这可能会很方便:

import Foundation

class FileHandler {
    private var file: UnsafeMutablePointer<FILE>?

    init?(filePath: String) {
        file = fopen(filePath, "r")
        guard file != nil else {
            print("Error: Unable to open the file.")
            return nil
        }
    }

    deinit {
        if file != nil {
            fclose(file)
        }
    }

    func readAndProcessFile() {
        defer {
            if file != nil {
                fclose(file)
                file = nil
            }
        }

        // Read and process the file
    }
}

if let fileHandler = FileHandler(filePath: "path/to/your/file.txt") {
    fileHandler.readAndProcessFile()
}

在这个例子中,我们有一个管理文件的 FileHandler 类。 当调用 readAndProcessFile 方法时,我们使用 defer 块来确保在方法退出时关闭文件,无论它是正常退出还是由于错误退出。 这类似于 C++ 中的 RAII 概念,其中在对象超出范围时执行资源清理。

当然,这不是 RAII 的 1:1,但它显示了一种可以使用 defer 来实现类似效果的方法。

推荐博文

Swift 中如何使用 XCTest 框架进行性能测试

摘要: 本文介绍了如何在 Swift 中使用 XCTest 框架进行性能测试,并通过 measure 函数来测量应用程序中特定代码路径的性能。

两个新的开源 Swift 库:Swift Certificates 和 Swift ASN.1

摘要: 这篇文章介绍了两个新的开源 Swift 库:Swift Certificates 和 Swift ASN.1。这两个库共同提供了更快、更安全的 X.509 证书实现,X.509 证书是支持 TLS 安全的关键技术之一。文章解释了X.509 证书和 ASN.1 格式的概念,和为什么需要构建一个 ASN.1 库以支持完整的 X.509 库,并介绍了 Swift ASN.1 和 Swift Certificates 的功能和目标。 Swift Certificates 目前可以解析大多数符合 RFC 5280 标准和 Web PKI 中使用的 X.509 证书,支持插件式 X.509 验证策略和 OCSP 分辨率。短期目标是使用 Swift Certificates 替换 swift-nio-ssl 中的 BoringSSL 实现,以提供更高性能和更好的内存安全性。

云音乐 Swift 混编 Module 化实践

摘要: 文章介绍了网易云音乐 iOS App 在支持 Swift 混编过程中,Module 化阶段的分析与实践以及在实践过程中可能会遇到各种未知问题。

话题讨论

文心一言挑战ChatGPT,谁更胜一筹?

  1. 文心一言
  2. ChatGPT
  3. 不分伯仲

欢迎在文末留言参与讨论。

关于我们

Swift社区是由 Swift 爱好者共同维护的公益组织,我们在国内以微信公众号的运营为主,我们会分享以 Swift实战SwiftUlSwift基础为核心的技术内容,也整理收集优秀的学习资料。

特别感谢 Swift社区 编辑部的每一位编辑,感谢大家的辛苦付出,为 Swift社区 提供优质内容,为 Swift 语言的发展贡献自己的力量。

更多推荐

Swift 周报 第二十五期