协议本身不符合?

2024-05-12

为什么这段 Swift 代码无法编译?

protocol P { }
struct S: P { }

let arr:[P] = [ S() ]

extension Array where Element : P {
    func test<T>() -> [T] {
        return []
    }
}

let result : [S] = arr.test()

编译器说:“输入P不符合协议P”(或者,在 Swift 的更高版本中,“不支持使用 'P' 作为符合协议 'P' 的具体类型。”)。

为什么不?不知何故,这感觉就像语言中的一个洞。我意识到问题源于声明数组arr作为数组协议类型的,但这是否是一件不合理的事情?我认为协议的存在正是为了帮助提供具有类型层次结构之类的结构?


为什么协议不符合自身?

在一般情况下允许协议遵守自身是不合理的。问题在于静态协议要求。

这些包括:

  • static方法和属性
  • 初始化器
  • 关联类型(尽管这些当前阻止将协议用作实际类型)

我们可以通过通用占位符访问这些要求T where T : P– 然而我们cannot在协议类型本身上访问它们,因为没有具体的一致类型可以转发。因此我们不能允许T to be P.

考虑一下如果我们允许以下示例中会发生什么Array扩展适用于[P]:

protocol P {
  init()
}

struct S  : P {}
struct S1 : P {}

extension Array where Element : P {
  mutating func appendNew() {
    // If Element is P, we cannot possibly construct a new instance of it, as you cannot
    // construct an instance of a protocol.
    append(Element())
  }
}

var arr: [P] = [S(), S1()]

// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
arr.appendNew()

我们不可能打电话appendNew() on a [P], 因为P (the Element) 不是具体类型,因此无法实例化。它must在具有具体类型元素的数组上调用,其中该类型符合P.

这是一个类似的故事,具有静态方法和属性要求:

protocol P {
  static func foo()
  static var bar: Int { get }
}

struct SomeGeneric<T : P> {

  func baz() {
    // If T is P, what's the value of bar? There isn't one – because there's no
    // implementation of bar's getter defined on P itself.
    print(T.bar)

    T.foo() // If T is P, what method are we calling here?
  }
}

// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
SomeGeneric<P>().baz()

我们不能谈论SomeGeneric<P>。我们需要静态协议要求的具体实现(注意如何有no的实施foo() or bar在上面的例子中定义)。尽管我们可以在P扩展,这些仅为符合以下条件的具体类型定义P– 你仍然不能打电话给他们P本身。

正因为如此,Swift 完全不允许我们使用协议作为符合自身的类型——因为当该协议有静态要求时,它就没有静态要求。

实例协议要求没有问题,因为您must在符合协议的实际实例上调用它们(因此必须已实现要求)。因此,当调用实例上的需求时,键入为P,我们可以将该调用转发到该需求的底层具体类型的实现上。

然而,在这种情况下对规则进行特殊例外可能会导致通用代码处理协议的方式出现令人惊讶的不一致。话虽这么说,但情况并没有太大不同associatedtype要求——(当前)阻止您使用协议作为类型。当协议具有静态要求时,限制您无法将协议用作符合自身的类型,这可能是该语言未来版本的一个选项

Edit:正如下面所探讨的,这看起来确实是 Swift 团队的目标。


@objc协议

事实上,实际上就是这样exactly语言如何对待@objc协议。当他们没有静态要求时,他们就会遵守自己的要求。

以下编译得很好:

import Foundation

@objc protocol P {
  func foo()
}

class C : P {
  func foo() {
    print("C's foo called!")
  }
}

func baz<T : P>(_ t: T) {
  t.foo()
}

let c: P = C()
baz(c)

baz要求T符合P;但我们可以替换为P for T因为P没有静态要求。如果我们添加一个静态要求P,该示例不再编译:

import Foundation

@objc protocol P {
  static func bar()
  func foo()
}

class C : P {

  static func bar() {
    print("C's bar called")
  }

  func foo() {
    print("C's foo called!")
  }
}

func baz<T : P>(_ t: T) {
  t.foo()
}

let c: P = C()
baz(c) // error: Cannot invoke 'baz' with an argument list of type '(P)'

因此,解决此问题的一种方法是使您的协议@objc。诚然,在许多情况下这并不是一个理想的解决方法,因为它强制您的一致性类型成为类,并且需要 Obj-C 运行时,因此无法使其在非 Apple 平台(例如 Linux)上可行。

但我怀疑这种限制是该语言已经实现“没有静态要求的协议符合自身”的主要原因之一@objc协议。编译器可以显着简化围绕它们编写的通用代码。

为什么?因为@objc协议类型的值实际上只是类引用,其需求是使用objc_msgSend。另一方面,非@objc协议类型的值更加复杂,因为它们同时携带值表和见证表,以便管理其(可能间接存储的)包装值的内存并确定分别针对不同需求调用哪些实现。

由于这种简化表示@objc协议,此类协议类型的值P可以与某些通用占位符类型的“通用值”共享相同的内存表示T : P, 想必使 Swift 团队能够轻松实现自我一致性。对于非同样的情况则不然@objc然而,协议作为通用值当前不携带值或协议见证表。

不过这个功能is有意为之,并希望能够推广到非@objc协议,由 Swift 团队成员 Slava Pestov 确认回应您的询问(由这个问题 https://stackoverflow.com/q/46103802/2976878):

马特·纽伯格 (Matt Neuburg) 添加了一条评论 - 2017 年 9 月 7 日 1:33

这确实可以编译:

@objc protocol P {}
class C: P {}

func process<T: P>(item: T) -> T { return item }
func f(image: P) { let processed: P = process(item:image) }

Adding @objc使其编译;删除它会使其无法再次编译。 Stack Overflow 上的一些人对此感到惊讶,并希望 知道这是故意的还是有问题的边缘情况。

Slava Pestov 添加了一条评论 - 2017年9月7日 1:53

这是故意的——解除这个限制就是这个错误的目的。 就像我说的,这很棘手,我们还没有任何具体计划。

所以希望有一天语言能够支持非@objc协议也是如此。

但目前有哪些解决方案可以解决非@objc协议?


使用协议约束实现扩展

在 Swift 3.1 中,如果您想要一个带有约束的扩展,即给定的泛型占位符或关联类型必须是给定的协议类型(而不仅仅是符合该协议的具体类型)——您可以简单地使用==约束。

例如,我们可以将数组扩展编写为:

extension Array where Element == P {
  func test<T>() -> [T] {
    return []
  }
}

let arr: [P] = [S()]
let result: [S] = arr.test()

当然,这现在阻止我们在具有符合以下条件的具体类型元素的数组上调用它:P。我们可以通过定义一个额外的扩展来解决这个问题Element : P,然后转发到== P扩大:

extension Array where Element : P {
  func test<T>() -> [T] {
    return (self as [P]).test()
  }
}

let arr = [S()]
let result: [S] = arr.test()

然而值得注意的是,这会将数组执行 O(n) 转换为[P],因为每个元素都必须装在一个存在的容器中。如果性能是一个问题,您可以通过重新实现扩展方法来简单地解决这个问题。这不是一个entirely令人满意的解决方案——希望该语言的未来版本将包含一种表达“协议类型”的方法or符合协议类型的约束。

在 Swift 3.1 之前,实现此目的的最通用方法是:正如罗布在他的回答中所表明的那样 https://stackoverflow.com/a/33524927/2976878,就是简单地为 a 构建一个包装类型[P],然后您可以在其上定义扩展方法。


将协议类型实例传递给受约束的通用占位符

考虑以下情况(人为的,但并不罕见):

protocol P {
  var bar: Int { get set }
  func foo(str: String)
}

struct S : P {
  var bar: Int
  func foo(str: String) {/* ... */}
}

func takesConcreteP<T : P>(_ t: T) {/* ... */}

let p: P = S(bar: 5)

// error: Cannot invoke 'takesConcreteP' with an argument list of type '(P)'
takesConcreteP(p)

我们无法通过p to takesConcreteP(_:),因为我们目前无法替代P对于通用占位符T : P。让我们看一下解决这个问题的几种方法。

1. 开放存在主义

而不是试图替代P for T : P,如果我们能够深入研究底层的具体类型会怎样?P输入的值被包装并替代它?不幸的是,这需要一个称为开放存在主义 https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#opening-existentials,目前用户无法直接使用。

然而,斯威夫特does访问存在值(协议类型值)时隐式打开它们上的成员(即,它挖掘出运行时类型并使其以通用占位符的形式进行访问)。我们可以在协议扩展中利用这个事实P:

extension P {
  func callTakesConcreteP/*<Self : P>*/(/*self: Self*/) {
    takesConcreteP(self)
  }
}

注意隐式泛型Self扩展方法采用的占位符,用于键入隐式self参数 – 这发生在所有协议扩展成员的幕后。当对协议类型值调用此类方法时P,Swift 挖掘出底层的具体类型,并用它来满足Self通用占位符。这就是为什么我们能够调用takesConcreteP(_:) with self– 我们很满意T with Self.

这意味着我们现在可以说:

p.callTakesConcreteP()

And takesConcreteP(_:)使用通用占位符调用T满足底层具体类型(在本例中S)。请注意,这不是“符合自身的协议”,因为我们正在替换具体类型而不是P– 尝试向协议添加静态要求,看看从内部调用它时会发生什么takesConcreteP(_:).

如果 Swift 继续不允许协议遵守自身,那么下一个最佳替代方案是在尝试将它们作为参数传递给泛型类型的参数时隐式打开存在性 - 有效地完成我们的协议扩展 Trampoline 所做的事情,只是没有样板。

但请注意,开放存在并不是解决协议不符合自身问题的通用解决方案。它不处理协议类型值的异构集合,这些值可能都有不同的底层具体类型。例如,考虑:

struct Q : P {
  var bar: Int
  func foo(str: String) {}
}

// The placeholder `T` must be satisfied by a single type
func takesConcreteArrayOfP<T : P>(_ t: [T]) {}

// ...but an array of `P` could have elements of different underlying concrete types.
let array: [P] = [S(bar: 1), Q(bar: 2)]

// So there's no sensible concrete type we can substitute for `T`.
takesConcreteArrayOfP(array) 

出于同样的原因,一个函数有多个T参数也会有问题,因为参数必须采用相同类型的参数 - 但是如果我们有两个P值,我们无法在编译时保证它们都具有相同的底层具体类型。

为了解决这个问题,我们可以使用橡皮擦。

2. 构建一个橡皮擦

As Rob says https://stackoverflow.com/a/33524927/2976878, a 类型橡皮擦 http://robnapier.net/erasure,是解决协议不符合自身问题的最通用的解决方案。它们允许我们通过将实例需求转发到底层实例,将协议类型的实例包装在符合该协议的具体类型中。

所以,让我们构建一个转发的类型擦除盒P的实例要求到符合以下条件的底层任意实例P:

struct AnyP : P {

  private var base: P

  init(_ base: P) {
    self.base = base
  }

  var bar: Int {
    get { return base.bar }
    set { base.bar = newValue }
  }

  func foo(str: String) { base.foo(str: str) }
}

现在我们可以谈论AnyP代替P:

let p = AnyP(S(bar: 5))
takesConcreteP(p)

// example from #1...
let array = [AnyP(S(bar: 1)), AnyP(Q(bar: 2))]
takesConcreteArrayOfP(array)

现在,考虑一下为什么我们必须构建那个盒子。正如我们之前讨论的,对于协议有静态要求的情况,Swift 需要一个具体的类型。考虑如果P有一个静态需求 - 我们需要在AnyP。但它应该被实施为什么呢?我们正在处理符合以下条件的任意实例P在这里 – 我们不知道它们的底层具体类型如何实现静态要求,因此我们无法有意义地表达这一点AnyP.

因此,这种情况下的解决方案仅在以下情况下才真正有用:instance协议要求。一般情况下,我们还是不能治疗P作为符合以下条件的具体类型P.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

协议本身不符合? 的相关文章

  • Ios Swift制作字体切换粗体、斜体、boldItalic、正常而不改变其他属性

    我很惊讶 在 Swift 中简单地为现有字体设置粗体和斜体是如此复杂 我只是想通过在字体类上使用以下方法来简化事情 我希望将以下方法添加到已设置字体系列和字体大小的现有字体中 我需要保留这些并仅更改以下内容 setBold Shud 保留斜
  • 快速将阴影绘制到 uibezierpath

    我有一个奇怪的问题 尽管我确实阅读了很多有关如何执行此操作的教程 但最终结果仅显示贝塞尔线 而不显示任何阴影 我的代码非常简单 let borderLine UIBezierPath borderLine moveToPoint CGPoi
  • 斯威夫特/iOS。从导航堆栈中删除一些视图控制器

    这是我想做的 但我不确定这是否是正确的方法 所以请给我建议如何去做 我有初始 VC 和导航 VC 我从中推送第一个 VC 从中推送第二个 VC 接下来我介绍 来自第二个 VC 的 NavigationController 第三个 VC 现在
  • WKWebView:无需 Javascript 即可缩放PageToFit 行为

    如何获得scalesPageToFit显示在 HTML 内容中的行为WKWebView 不使用 JavaScript 在我们的例子中需要禁用 JS 抱歉没有发布任何代码 但我不知道如何实现这一目标 我所知道的和我找到的所有解决方案都是基于J
  • Java 中通用方法参数的 getClass()

    以下 Java 方法无法编译
  • AudioPlayer 和锁屏/控制中心控制 Swift [关闭]

    Closed 这个问题需要调试细节 help minimal reproducible example 目前不接受答案 我是斯威夫特的新手 我写信是因为我想问一个问题 我和我的朋友正在开发一个音频播放器 但我们遇到了问题 播放器还可以在后台
  • Java 泛型与类和接口 - 一起

    我想要一个 Class 对象 但我想强制它代表的任何类扩展类 A 并实现接口 B 我可以 Class
  • 为什么我的自定义类没有出现在 Interface Builder 的下拉列表中?

    我正在使用 Interface Builder 和 Storyboards 来构建我的应用程序 我正在尝试将我的源代码连接到我的UIViewController在 Storyboard 中 但我的类都没有显示在 自定义类 下拉菜单中 这种情
  • SwiftUI:为表单中的单元格添加动画

    我正在尝试动画化我的Form或者更确切地说是其中的细胞 我的问题是 下面的代码给了我一个很好的插入动画 但是对于删除 单元格在看起来很丑陋的延迟后突然被删除 import SwiftUI struct ContentView View St
  • 在 swift 中发送自定义 HTTP 标头

    我设法从服务器获取 json 但现在我想通过 http 标头添加额外的安全性 这就是我的代码现在的样子 let urlPath http www xxxxxxxx com let url NSURL string urlPath let s
  • 如何改善 ARKit 3.0 中的人物遮挡

    我们正在开发一个使用 ARKit 中的人物遮挡的演示应用程序 因为我们想在最终场景中添加视频 所以我们使用SCNPlanes 使用 a 渲染视频SCNBillboardConstraint以确保他们面向正确的方向 这些视频也是部分透明的 使
  • 依赖于 pod 的 Swift 通用框架

    我正在开发一个依赖于 Alamofire 的小型 Swift 框架 我将它用作属于同一工作区的应用程序的嵌入式框架 并且它运行良好 当我想构建一个具有总体目标的通用框架时 问题就出现了 然后 当执行脚本生成框架时 它失败并显示消息No su
  • 如何使用 swift 在 UITabBarController 中以编程方式添加选项卡?

    如何以编程方式从 UIViewController 扩展的任何类创建选项卡 class DashboardTabBarController UITabBarController override func viewDidLoad here
  • 如何跟踪 SwiftUI 应用程序中的所有触摸

    我正在尝试在 SwiftUI 应用程序中实现锁屏 我需要跟踪每个事件才能重新启动锁定计时器 在 UIKit 应用程序中 我使用了这种方法 重写 UIApplication 它允许了解应用程序中的任何事件 override func send
  • SpriteKit - 对多个 SKNode 上运行的多个 SKAction 进行排序

    我非常了解 SKAction API 但在多个节点上运行顺序代码时我无法获得良好的代码 这是示例代码 简化 import SpriteKit class GameScene SKScene weak var node1 SKNode wea
  • Swift 结构类型集

    说我有一个struct 可以是任何东西 struct Cube var x Int var y Int var z Int var width Int 然后我该如何创建一个Set这些点中 是否存在两个具有相同属性的对象 let points
  • 在 Safari 中快速打开链接

    我目前正在我的应用程序中打开链接WebView 但我正在寻找一个打开链接的选项Safari反而 它不是 融入 Swift 但你可以使用标准UIKit方法来做到这一点 看看 UIApplication 的openUrl https devel
  • UIImageWriteToSavedPhotosAlbum 选择器语法问题

    努力让 UIImageWriteToSavedPhotosAlbum 快速工作https developer apple com library ios documentation UIKit Reference UIKitFunction
  • 如何删除以前的 ViewController

    我是一名学生 对编程还很陌生 我正在尝试在业余时间学习 Objective C Swift 我使用 spriteKit 和 swift 制作了一个游戏 有多个菜单 场景 我正在尝试从一个视图控制器转换到另一个视图控制器 为此 我使用了以下代
  • Swift:UICollectionViewCell didSelectItemAtIndexPath 更改背景颜色

    我可以轻松更改单元格的背景颜色CellForItemAtIndexPath method func collectionView collectionView UICollectionView cellForItemAtIndexPath

随机推荐

  • 指定的 sqlite3 gem 未加载

    虽然我对 Ruby on Rails 比较陌生 但我开发应用程序已经有一段时间了 我似乎遇到的问题是 当我创建一个新的 Rails 应用程序 本地 使用 c9 时 当我启动 apache 服务器时 我似乎收到此错误 Specified sq
  • 无法仅在控制台中启动 androidstudio

    你好 我的问题是下一个 我下载了Android Studio如果我去 路径 android studio bin 我执行studio sh 我收到以下错误 No JDK found Please validate either STUDIO
  • 无法使用 wxPython 打开在 folium 中生成的本地 HTML 文件

    我目前正在尝试将 GPS 坐标绘制为地图上的标记 并在 wxPython 中显示结果 我使用 folium 绘制坐标标记并生成 HTML 文件 import folium fmap folium Map 43 5321 172 6362 z
  • Docker 无法解析主机名

    我需要知道在同一台机器上运行的某些容器的主机名 或 IP 地址 正如我已经评论过的here https stackoverflow com questions 26269870 how do docker containers resolv
  • 返回早期概念在 PHP 中有何用处

    我已经在以下链接中了解了最佳实践https pear php net manual en standards bestpractices php https pear php net manual en standards bestprac
  • 通过管道连接到 findstr 的输入

    我有一个文本文件 其中包含宏名称列表 每行一个 我的最终目标是打印宏名称在当前目录的文件中出现的次数 宏的名称位于C temp macros txt type C temp macros txt在命令提示符下可以正常打印列表 现在我想将该输
  • 如何在 Apache Spark 中通过 DStream 使用特征提取

    我有通过 DStream 从 Kafka 到达的数据 我想进行特征提取以获得一些关键词 我不想等待所有数据的到达 因为它是可能永远不会结束的连续流 所以我希望以块的形式执行提取 如果准确性会受到一点影响 对我来说并不重要 到目前为止 我整理
  • 纯函数怎么能做IO呢?

    我最近了解到莫纳德随机数 http hackage haskell org package MonadRandom 0 1 13 docs Control Monad Random Class html t 3aMonadRandom图书馆
  • 什么是堆栈随机化以及它如何防止缓冲区溢出攻击?

    我从一本书上读到缓冲区溢出可能被用作注入攻击系统的漏洞代码的一种方式 和堆栈随机化是防止此类攻击的有效方法之一 我不明白是什么堆栈随机化以及它如何防止这些攻击 代替堆栈随机化克服 或更难 堆栈或缓冲区溢出的技术称为地址空间布局随机化 ASL
  • 枚举器上的 [[maybe_unused]]

    查看规格 maybe unused http en cppreference com w cpp language attributes 它指出 出现在类 typedef 变量 非静态数据成员 函数 枚举或枚举器的声明中 如果编译器对未使用
  • 多个资源文件夹

    我正在尝试在我的 Android 项目中添加一个资源文件夹 我创建了一个新文件夹额外分辨率所以我的项目结构如下所示 src main res layout etc extra res layout 所以我将其添加到构建 gradle and
  • 如何找到查询结果的大小

    我在 Rails 中有以下查询 records Record select y id source where source gt source y id gt y id group y id source having count 1 如
  • 在 HTML 电子邮件中嵌入附加图像

    如果我将图像附加到电子邮件中 如何将其放置在 HTML 内容中 我尝试仅使用文件名作为图像源 但这似乎不起作用 更具体地说明如何构建 HTML 邮件消息 结果将是一条多部分 MIME 消息 其中包含 text html 部分 如果您确实使用
  • React setState回调返回值

    我是 React 新手 我希望实现这种流程 set the state execute a function f an async one which returns a promise set the state again return
  • 使用mysql数据按高低价格排序

    这是我所拥有的以及我想做的 我的 MySql 数据库中有 12 个项目 4 个产品为 4 99 4 个产品为 3 99 4 个产品为 2 99 我意识到我可以像这样查询数据库 它会给我一个该价格的产品列表
  • xCode 7.1 中警报的 UITesting

    我正在 xCode 7 1 中编写 UITests 并且在测试警报时遇到问题 在我的情况下允许通知 创建测试时 xCode 会写入以下代码 app alerts U201cAppName U201d Would Like to Send Y
  • 套接字:监听积压并接受

    listen sock backlog 在我看来 参数backlog限制连接数量 这是我的测试代码 server initialize the sockaddr of server server sin family AF INET ser
  • MarionetteJS:应用程序区域与布局[重复]

    这个问题在这里已经有答案了 我正在阅读最新版本 2 3 0 的文档 它说应用程序区域现已被弃用 应用领域 警告 已弃用 此功能已弃用 而不是使用 应用程序作为视图树的根 您应该使用布局 看法 要将布局视图的范围限制为整个文档 您可以设置 它
  • NumPy 根据另一个数组中的值对第三个数组中的每个匹配元素求和一个数组

    我有两个 numpy 数组 一个包含值 另一个包含每个值类别 values np array 1 2 3 4 5 6 7 8 9 10 valcats np array 101 301 201 201 102 302 302 202 102
  • 协议本身不符合?

    为什么这段 Swift 代码无法编译 protocol P struct S P let arr P S extension Array where Element P func test