活动介绍

Swift编程中的不透明类型与自动引用计数

立即解锁
发布时间: 2025-08-16 02:36:03 阅读量: 14 订阅数: 30 AIGC
PDF

Swift编程入门与实践指南

# Swift编程中的不透明类型与自动引用计数 ## 1. 不透明类型概述 在编程中,函数或方法的不透明返回类型会隐藏其返回值的类型信息。它不提供具体的类型作为返回类型,而是依据返回值所支持的协议来描述。这种隐藏类型信息的方式在模块与调用该模块的代码之间的边界处非常有用,因为返回值的底层类型可以保持私有。与返回协议类型的值不同,不透明类型保留了类型标识,编译器可以访问类型信息,但模块的客户端无法访问。 ### 1.1 不透明类型解决的问题 假设我们要编写一个绘制 ASCII 艺术形状的模块。ASCII 艺术形状的基本特征是有一个 `draw()` 函数,该函数返回该形状的字符串表示。我们可以将其作为 `Shape` 协议的要求: ```swift protocol Shape { func draw() -> String } struct Triangle: Shape { var size: Int func draw() -> String { var result: [String] = [] for length in 1...size { result.append(String(repeating: "*", count: length)) } return result.joined(separator: "\n") } } let smallTriangle = Triangle(size: 3) print(smallTriangle.draw()) // * // ** // *** ``` 我们可以使用泛型来实现垂直翻转形状等操作,如下所示: ```swift struct FlippedShape<T: Shape>: Shape { var shape: T func draw() -> String { let lines = shape.draw().split(separator: "\n") return lines.reversed().joined(separator: "\n") } } let flippedTriangle = FlippedShape(shape: smallTriangle) print(flippedTriangle.draw()) // *** // ** // * ``` 定义一个 `JoinedShape` 结构体来垂直连接两个形状,会导致类型信息泄露。例如,将一个翻转的三角形与另一个三角形连接会产生 `JoinedShape<FlippedShape<Triangle>, Triangle>` 这样的类型。 ```swift struct JoinedShape<T: Shape, U: Shape>: Shape { var top: T var bottom: U func draw() -> String { return top.draw() + "\n" + bottom.draw() } } let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle) print(joinedTriangles.draw()) // * // ** // *** // *** // ** // * ``` 这种暴露形状创建详细信息的方式会导致一些本不应成为 ASCII 艺术模块公共接口一部分的类型泄露,因为需要声明完整的返回类型。模块内部的代码可以通过多种方式构建相同的形状,而模块外部使用该形状的代码不应考虑这些转换列表的实现细节。像 `JoinedShape` 和 `FlippedShape` 这样的包装类型对模块的用户来说并不重要,也不应可见。模块的公共接口应包括连接和翻转形状等操作,这些操作返回另一个 `Shape` 值。 ### 1.2 返回不透明类型 不透明类型可以看作是泛型类型的反向。泛型类型允许调用函数的代码以一种与函数实现无关的方式为函数的参数和返回值选择类型。例如: ```swift func max<T>(_ x: T, _ y: T) -> T where T: Comparable { ... } ``` 调用 `max(_:_:)` 的代码选择 `x` 和 `y` 的值,这些值的类型决定了 `T` 的具体类型。调用代码可以使用任何符合 `Comparable` 协议的类型。函数内部的代码以通用的方式编写,以便处理调用者提供的任何类型。`max(_:_:)` 的实现仅使用所有 `Comparable` 类型共享的功能。 对于具有不透明返回类型的函数,角色则相反。不透明类型允许函数实现以一种与调用函数的代码无关的方式为其返回的值选择类型。例如: ```swift struct Square: Shape { var size: Int func draw() -> String { let line = String(repeating: "*", count: size) let result = Array<String>(repeating: line, count: size) return result.joined(separator: "\n") } } func makeTrapezoid() -> some Shape { let top = Triangle(size: 2) let middle = Square(size: 2) let bottom = FlippedShape(shape: top) let trapezoid = JoinedShape( top: top, bottom: JoinedShape(top: middle, bottom: bottom) ) return trapezoid } let trapezoid = makeTrapezoid() print(trapezoid.draw()) // * // ** // ** // ** // ** // * ``` `makeTrapezoid()` 函数声明其返回类型为 `some Shape`,这意味着函数返回一个符合 `Shape` 协议的给定类型的值,但不指定任何特定的具体类型。这样编写 `makeTrapezoid()` 可以表达其公共接口的基本方面——返回的值是一个形状,而不必将形状所由组成的具体类型作为其公共接口的一部分。此实现使用了两个三角形和一个正方形,但该函数可以用多种其他方式重写以绘制梯形,而不改变其返回类型。 我们还可以将不透明返回类型与泛型结合使用: ```swift func flip<T: Shape>(_ shape: T) -> some Shape { return FlippedShape(shape: shape) } func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape { JoinedShape(top: top, bottom: bottom) } let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle)) print(opaqueJoinedTriangles.draw()) // * // ** // *** // *** // ** // * ``` 这里的 `opaqueJoinedTriangles` 的值与前面泛型示例中的 `joinedTriangles` 相同。但不同的是,`flip(_:)` 和 `join(_:_:)` 将泛型形状操作返回的底层类型包装在不透明返回类型中,从而防止这些类型可见。这两个函数都是泛型的,因为它们依赖的类型是泛型的,函数的类型参数传递了 `FlippedShape` 和 `JoinedShape` 所需的类型信息。 如果具有不透明返回类型的函数从多个位置返回,所有可能的返回值必须具有相同的类型。对于泛型函数,该返回类型可以使用函数的泛型类型参数,但仍然必须是单一类型。例如,下面的形状翻转函数的无效版本包含了对正方形的特殊处理: ```swift func invalidFlip<T: Shape>(_ shape: T) -> some Shape { if shape is Square { return shape // Error: return types don't match } return FlippedShape(shape: shape) // Error: return types don't match } ``` 如果用 `Square` 调用此函数,它返回 `Square`;否则,返回 `FlippedShape`。这违反了只返回单一类型值的要求,使 `invalidFlip(_:)` 成为无效代码。修复 `invalidFlip(_:)` 的一种方法是将正方形的特殊处理移到 `FlippedShape` 的实现中,这样该函数就可以始终返回 `FlippedShape` 值: ```swift struct FlippedShape<T: Shape>: Shape { var shape: T func draw() -> String { if shape is Square { return shape.draw() } let lines = shape.draw().split(separator: "\n") return lines.reversed().joined(separator: "\n") } } ``` 始终返回单一类型的要求并不妨碍在不透明返回类型中使用泛型。例如: ```swift func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection { return Array<T>(repeating: shape, count: count) } ``` 在这种情况下,返回值的底层类型根据 `T` 而变化:无论传入什么形状,`repeat(shape:count:)` 都会创建并返回该形状的数组。不过,返回值始终具有相同的底层类型 `[T]`,因此它遵循具有不透明返回类型的函数必须只返回单一类型值的要求。 ### 1.3 不透明类型与协议类型的区别 返回不透明类型看起来与使用协议类型作为函数的返回类型非常相似,但这两种返回类型在是否保留类型标识方面有所不同。不透明类型指的是一个特定的类型,尽管函数的调用者无法看到是哪种类型;协议类型可以指任何符合该协议的类型。一般来说,协议类型在存储值的底层类型方面提供了更大的灵活性,而不透明类型可以对这些底层类型做出更强的保证。 例如,下面是一个使用协议类型作为返回类型而不是不透明返回类型的 `flip(_:)` 版本: ```swift func protoFlip<T: Shape>(_ shape: T) -> Shape { return FlippedShape(shape: shape) } ``` 这个 `protoFlip(_:)` 版本的主体与 `flip(_:)` 相同,并且总是返回相同类型的值。但与 `flip(_:)` 不同的是,`protoFlip(_:)` 返回的值不要求总是具有相同的类型,它只需要符合 `Shape` 协议。换句话说,`protoFlip(_:)` 与调用者签订的 API 契约比 `flip(_:)` 宽松得多。它保留了返回多种类型值的灵活性: ```swift func protoFlip<T: Shape>(_ shape: T) -> Shape { if shape is Square { return shape } return FlippedShape(shape: shape) } ``` 修改后的代码根据传入的形状返回 `Square` 实例或 `FlippedShape` 实例。该函数返回的两个翻转形状可能具有完全不同的类型。此函数的其他有效版本在翻转同一个形状的多个实例时可能返回不同类型的值。`protoFlip(_:)` 返回的不太具体的类型信息意味着许多依赖类型信息的操作在返回的值上不可用。例如,无法编写一个 `==` 运算符来比较该函数返回的结果: ```swift let protoFlippedTriangle = protoFlip(smallTriangle) let sameThing = protoFlip(smallTriangle) protoFlippedTriangle == sameThing // Error ``` 此示例最后一行的错误有几个原因。直接的问题是 `Shape` 协议要求中不包括 `==` 运算符。如果尝试添加一个,接下来会遇到的问题是 `==` 运算符需要知道其左右参数的类型。这种运算符通常接受 `Self` 类型的参数,与采用该协议的任何具体类型相匹配,但向协议添加 `Self` 要求不允许在将协议用作类型时发生的类型擦除。 使用协议类型作为函数的返回类型可以灵活地返回任何符合该协议的类型。然而,这种灵活性的代价是一些操作在返回的值上不可用。示例显示了 `==` 运算符不可用,因为它依赖于使用协议类型时未保留的特定类型信息。 另一个问题是这种方法的形状转换不能嵌套。翻转三角形的结果是 `Shape` 类型的值,而 `protoFlip(_:)` 函数接受一个符合 `Shape` 协议的某种类型的参数。然而,协议类型的值不符合该协议,`protoFlip(_:)` 返回的值不符合 `Shape`。这意味着像 `protoFlip(protoFlip(smallTriange))` 这样应用多个转换的代码是无效的,因为翻转后的形状不是 `protoFlip(_:)` 的有效参数。 相比之下,不透明类型保留了底层类型的标识。Swift 可以推断关联类型,这使得可以在协议类型不能用作返回值的地方使用不透明返回值。例如,下面是泛型中的 `Container` 协议的一个版本: ```swift protocol Container { associatedtype Item var count: Int { get } subscript(i: Int) -> Item { get } } extension Array: Container { } ``` 不能将 `Container` 用作函数的返回类型,因为该协议有一个关联类型。也不能将其用作泛型返回类型的约束,因为函数体外部没有足够的信息来推断泛型类型需要是什么。 ```swift // Error: Protocol with associated types can't be used as a return type. func makeProtocolContainer<T>(item: T) -> Container { return [item] } // Error: Not enough information to infer C. func makeProtocolContainer<T, C: Container>(item: T) -> C { return [item] } ``` 使用不透明类型 `some Container` 作为返回类型表达了所需的 API 契约——函数返回一个容器,但不指定容器的类型: ```swift func makeOpaqueContainer<T>(item: T) -> some Container { return [item] } let opaqueContainer = makeOpaqueContainer(item: 12) let twelve = opaqueContainer[0] print(type(of: twelve)) // Prints "Int" ``` `twelve` 的类型被推断为 `Int`,这说明了类型推断适用于不透明类型。在 `makeOpaqueContainer(item:)` 的实现中,不透明容器的底层类型是 `[T]`。在这种情况下,`T` 是 `Int`,所以返回值是一个整数数组,`Item` 关联类型被推断为 `Int`。`Container` 上的下标返回 `Item`,这意味着 `twelve` 的类型也被推断为 `Int`。 ### 1.4 不透明类型相关总结 | 类型 | 特点 | 示例 | | ---- | ---- | ---- | | 不透明类型 | 保留类型标识,调用者不可见底层类型,返回单一类型 | `func makeTrapezoid() -> some Shape` | | 协议类型 | 可指任何符合协议的类型,灵活性高,部分操作受限 | `func protoFlip<T: Shape>(_ shape: T) -> Shape` | ### 1.5 不透明类型工作流程 mermaid 图 ```mermaid graph LR A[调用函数] --> B{函数返回不透明类型} B --> C ```
corwn 最低0.47元/天 解锁专栏
赠100次下载
继续阅读 点击查看下一篇
profit 400次 会员资源下载次数
profit 300万+ 优质博客文章
profit 1000万+ 优质下载资源
profit 1000万+ 优质文库回答
复制全文

相关推荐

陆鲁

资深技术专家
超过10年工作经验的资深技术专家,曾在多家知名大型互联网公司担任重要职位。任职期间,参与并主导了多个重要的移动应用项目。
最低0.47元/天 解锁专栏
赠100次下载
百万级 高质量VIP文章无限畅学
千万级 优质资源任意下载
千万级 优质文库回答免费看

最新推荐

Tableau基础图表的创建与理解

### Tableau基础图表的创建与理解 在数据可视化领域,Tableau是一款功能强大的工具,它提供了多种类型的图表来帮助我们更好地理解和展示数据。下面将详细介绍如何在Tableau中创建几种常见的基础图表。 #### 1. 交叉表(文本表) 很多人在查看数据时,更倾向于使用熟悉的表格形式。Tableau提供了创建交叉表或文本表的功能,操作步骤如下: - 保存之前创建图表的进度。 - 若要从现有图表创建新的交叉表,在工作表标签处右键单击,选择“Duplicate as Crosstab”,即可生成一个新的文本表。 创建完成后,会发现Tableau做了一些有趣的改变: - “Regio

概率注释模型:特征添加与序列标注任务建模

### 概率注释模型:特征添加与序列标注任务建模 在数据标注领域,不同的模型有着各自的特点和适用场景。部分汇集模型在稀疏数据条件下展现出更好的适应性,它通过信息共享机制,让标注者的注释行为相互影响,从而使模型在数据有限时也能有效工作。当有足够的注释时,部分汇集模型和非汇集模型的性能可能相近,但整体而言,部分汇集模型更为通用。 #### 1. 添加特征以增强模型能力 传统的裁决模型主要依赖编码者提供的注释,但研究表明,让模型具备数据感知能力,即除了注释外,使用特征来刻画项目,能够提升模型的裁决能力。 ##### 1.1 Raykar 等人的判别模型 Raykar 等人(2010)利用特征丰

Tableau高级功能:地图与仪表盘操作指南

### Tableau高级功能:地图与仪表盘操作指南 #### 1. 高级地图功能 在使用Tableau进行数据可视化时,地图是一种非常强大的工具。从2018年起,Tableau引入了一些高级地图技术,极大地提升了地图可视化的能力。不过,在使用这些高级功能时,要确保地图能合理反映数据比例,避免数据的错误呈现。下面将详细介绍几种高级地图功能。 ##### 1.1 密度标记(Density Marks) 密度标记类型可用于查看特定区域内数据的集中程度。以查看美国大陆机场集中情况为例: - 操作步骤: 1. 双击“Origin Latitude”和“Origin Longitude”,并

数据故事创作:从理论到实践的全面指南

# 数据故事创作:从理论到实践的全面指南 ## 1. SWD工作坊:实践与提升 在工作中,我们可以组织 SWD 工作坊来提升数据故事讲述的能力。首先是前期准备工作: - 给团队发送三小时的日程邀请,并预订一个有充足桌面空间和白板的会议室。 - 准备好物资,如彩色马克笔、活动挂图和多种尺寸的便利贴(6x8 英寸的便利贴很棒,因为它们与标准幻灯片尺寸相同,可用于以低技术方式模拟整个演示文稿;同时准备一些较小的便利贴,供那些想在深入细节之前进行更高级故事板制作并关注总体主题和流程的人使用)。 为实际的工作坊指定一名计时员。在项目工作时间,计时员要留意时间,在进行到一半和还剩 20 分钟时提醒参与

预训练模型的十大关键问题探索

# 预训练模型的十大关键问题探索 ## 1. 模型安全与认知学习 ### 1.1 模型安全 在模型安全方面,具备语音知识的模型不会被“U r stupid!”这类表述所误导。因此,构建具有丰富知识的大模型是保障模型安全的可靠途径。 ### 1.2 认知学习 当前大模型的学习范式仍以数据驱动为主,无法充分反映现实世界中的潜在风险。人类能够主动与世界交互并持续获取知识,还能从“试错”过程中学习避免错误。所以,对于构建安全模型而言,从认知和交互中学习至关重要。 ### 1.3 安全与伦理挑战 安全和伦理是人工智能领域长期存在的话题,在文学和艺术作品中也有广泛讨论。面对强大机器失控的担忧,我们需

Snowflake数据平台全方位解析

# Snowflake数据平台全方位解析 ## 1. Snowflake的发布计划 Snowflake每周会进行两次计划内发布,包含以下类型: - 完整发布:除周五外的任意一天进行部署,涵盖新功能、功能增强或更新以及问题修复。 - 补丁发布 此外,每月还会进行一次行为变更发布。 ## 2. Snowpark支持的语言 Snowpark支持多种客户端开放API语言,为开发者提供了丰富的选择: - Node.js - .NET - Go - Java - Python - SQL Snowflake数据平台对开发者十分友好,允许应用开发者在多种编程语言中进行选择。 ## 3. 查询性能测

电子商务中的聊天机器人:开发、测试与未来趋势

# 电子商务中的聊天机器人:开发、测试与未来趋势 ## 1. Rasa助力电商聊天机器人开发 Rasa为电子商务提供了“零售入门包”,这本质上是一个专门用于客户服务的基础示例聊天机器人。该机器人预装了训练数据,具备多种零售客户服务技能,如查询订单状态。零售虚拟助手开发者可利用此项目创建适合在线零售的定制聊天机器人。 Rasa拥有高度可定制的开发系统,开发者能选择将关键组件(如特定语言模型)集成到项目中。此外,Rasa拥有庞大的社区,便于开发者融入其生态系统。它为电商聊天机器人开发提供了众多功能和优势,是一款出色的工具。一些选择Rasa开发虚拟助手的企业包括食品配送公司HelloFresh和

优化PowerBI体验与DAX代码的实用指南

### 优化 Power BI 体验与 DAX 代码的实用指南 在当今的数据驱动时代,Power BI 作为一款强大的商业智能工具,在数据分析和可视化方面发挥着重要作用。同时,DAX(Data Analysis Expressions)语言作为 Power BI 中进行高级计算和查询的关键,其优化对于提升整体性能至关重要。本文将详细介绍如何在 Power BI 中使用 Power Automate Visual、集成 Dynamics 365 进行数据分析,以及优化 DAX 代码的十种方法。 #### 1. 使用 Power Automate Visual 在 Power BI 中,你可以

问答与对话系统技术探索

### 问答与对话系统技术探索 #### 1. 领域阅读资源概述 问答系统是一个活跃且广泛的领域。有一些关于问答系统和问题类型的简要但实用的综述。对于受限领域和开放领域问答的更全面介绍也有相关资料。常用的问答方法包括利用结构化知识源(如知识图谱和本体)的系统、基于检索的系统、交互式问答、视觉问答以及基于深度学习的方法等。 对话系统近年来受到了很多关注,这主要得益于语音识别和自然语言理解的进步。关于对话系统有很好的入门资料,广泛接受的对话言语行为理论也有相应的发展。马尔可夫决策过程框架的基础以及部分可观测马尔可夫决策过程的讨论都有相关文献。强化学习、时间差分学习和Q学习也都有不错的讨论资料。

利用MicrosoftFairlearn实现AI系统的公平性

# 利用 Microsoft Fairlearn 实现 AI 系统的公平性 ## 1. 公平机会的概念 在美国,“公平机会”指的是每个人都应拥有平等的成功机会,不论其种族、性别或其他个人特征如何。这一概念在教育、就业和住房等多个领域都有应用,其核心信念是所有人都应得到公平对待,不应因种族或性别等因素受到歧视。 为确保所有美国人享有公平机会,人们采取了一系列举措。例如,平权行动旨在帮助那些历史上遭受歧视的群体获得教育和就业机会;禁止在教育和就业中进行歧视的法律,也有助于营造公平竞争的环境。 然而,实现公平机会并非易事。在判断某人是否拥有平等的成功机会时,对于应考虑哪些因素可能存在分歧。此外