前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >"if #available"与不透明结果类型

"if #available"与不透明结果类型

原创
作者头像
DerekYuYi
修改2022-11-30 20:27:13
4940
修改2022-11-30 20:27:13
举报
文章被收录于专栏:Swift-开源分析

介绍

SE-0360, Swift 5.7 已实现

SE-0244 引入以来,不透明的结果类型已成为类型级抽象的强大工具,允许库作者隐藏其API的实现细节。

根据 SE-0244 中描述的规则,返回不透明结果类型的函数必须从每个返回语句中返回与T类型相同的值,并且T必须满足不透明类型上所述的所有约束。

当前模型和实现限制了不透明结果类型作为抽象机制的有用性,因为它阻止了框架引入新类型并将其用作现有API 的基础类型。为了弥补这一可用性差距,本篇提议建议在可用条件下放宽对返回的同类型限制。

提议的动机

为了说明在不透明结果类型和可用性条件之间的交互问题,我们列举个框架例子,该框架下定义Shape协议,并且Square类型已经遵循该Shape协议,如下:

代码语言:Swift
复制
protocol Shape {
  func draw(to: Surface)
}
 
struct Square : Shape {
  ...
}

在该库的新版本中,库作者决定引入一个新的shape - Rectangle, 但是新类型被限制了可用性,在新的版本需要通过@available来区分,例如:

代码语言:Swift
复制
@available(macOS 100, *)
struct Rectangle : Shape {
  ...
}

因为RectangleSquare的变体,那么Square是可以转化为Rectangle。但是这种转化还是需要通过@available限制可用性,原因是因为Rectangle类型已经是受限制可用的。

代码语言:Swift
复制
@available(macOS 100, *)
extension Square {
  func asRectangle() -> some Shape {
     return Rectangle(...)
  }
}

新方法asRectangle()必须在可用性上下文中声明才能返回Rectangle, 这限制了它的可用性,因为asRectangle()的所有用途都封装在#avaliable代码块中。

如果框架的老版本就存在asRectangle(),不声明if #available条件,就无法使用新类型:

代码语言:Swift
复制
struct Square {
  func asRectangle() -> some Shape {
     if #available(macOS 100, *) {
        return Rectangle(...)
     }
     
     return self
  }
}

但上述这样声明是不允许的,因为asRectangle()函数主体中的所有返回语句都必须返回相同的具体类型。所以上述代码会报错:

代码语言:Swift
复制
 error: function declares an opaque return type 'some Shape', but the return statements in its body do not have matching underlying types
  func asRectangle() -> some Shape {
       ^                ~~~~~~~~~~
note: return statement has underlying type 'Rectangle'
      return Rectangle()
             ^
note: return statement has underlying type 'Square'
    return Square()
           ^

对于库作者来说,这是一个死胡同,尽管SE-0244指出,在未来版本的库/框架中可能更改基础结果类型,但这个假设是基于该类型已经存在,因此可以在所有返回语句中使用。恰巧在实际情况下,新类型往往是后续版本中新增,所以所绕进了一个死胡同。

提议解决方案

为了弥补上述可用性不足,本篇提议:放宽带有if #available函数的同类型返回限制,如果if #available条件一定会被执行,那么它可以返回与函数其余部分返回类型不同的类型。不要求必须返回同一类型。

这个提议给函数带来 2 点改变:

  • 多个if #available可以根据它的动态性返回不同类型
  • 可以安全返回一个确定类型,不受可用性限制,即使不符合任何可用性条件

由于函数中的返回类型在函数未运行时就要确认,即在声明函数时就要确认函数的返回类型,但如果把可用性条件判断(if #available)和其他条件判断(比如,guard, if, switch)混合在一起,函数就无法确定返回类型, 并且需要在if #available中返回与函数其余部分相同的类型。(意思是:在if #available内部包含的所有if, guard, switch的每个return分支上,都需要返回相同类型)下面这个例子满足这些规则:

代码语言:Swift
复制
func test() -> some Shape {
  if #available(macOS 100, *) { ✅
    return Rectangle()
  }
  
  return self
}

详细设计

无条件可用子句称为if #available子句。

if #available语句是满足下列条件的if或者else if语句:

  • 该子句是其所在函数顶层if语句的一部分
  • if条件语句之前,其所在函数还没有出现return语句
  • 子句的条件是一个#available条件
  • 该子句要么是初始的if子句,要么是紧随无条件可用性子句之后的其他if子句
  • 该子句至少包含一个return语句
  • 通过子句控制的所有结束路径都以return或者throw结束

if #available子句以外的所有返回语句必须返回彼此相同的类型,并且该类型必须与其所在函数一样可用。

所有在给定的if #available子句内的return语句必须每次返回相同的类型,这种类型必须与子句的#available条件一样可用。并且这个类型不必与子句之外的其他任何返回类型相同。

在所在函数中,必须至少有一种return语句。如果在无条件可用性子句之外,没有return语句,那么在if #available子句内部,至少存在一种返回类型与函数的返回返回相同。

函数的返回类型一般是:

  • 第一个无条件动态满足的if #available子句的return语句返回类型;
  • 所有的if #available子句之外的return语句返回类型;
  • 第一个if #available子句的return语句的返回类型,这个类型与函数返回类型一样

第一个示例是正确的,因为第一个if #available和第二个if #available都以return结束,代码如下:

代码语言:Swift
复制
func test() -> some Shape {
  if #available(macOS 100, *) { ✅
    return Rectangle()
  } else if #available(macOS 99, *) { ✅
    return Square()
  }
  return self
}

但是如果把上述条件修改下:

代码语言:Swift
复制
func test() -> some Shape {
  if cond {
    ...
  } else if #available(macOS 100, *) { ❌
    return Rectangle()
  }
  return self
}

编译器此时会报错,因为if #available和动态条件相关联。

如果if #available在可以return的动态条件之后,这种情况也是不允许的。比如在函数开始时,提前 guard并返回对象,那么这属于一个动态条件, 这种情况也会报错:

代码语言:Swift
复制
func test() -> some Shape {
  guard let x = <opt-value> else {
    return ...
  }
    
  if #available(macOS 100, *) { ❌
    return Rectangle()
  }

  return self
}

同样,如果if #available出现在某个循环内,也会报错:

代码语言:Swift
复制
func test() -> some Shape {
  for ... {
    if #available(macOS 100, *) { ❌
      return Rectangle()
    }
  }
  return self
}

下面例子中的 test() 方法符合上述规则,会通过编译器编译。该方法中if条件在它的分支内都有结果,且返回结果类型相同。if #available始终会通过return结果分支.

代码语言:Swift
复制
func test() -> some Shape {
  if #available(macOS 100, *) {
     if cond { ✅
       return Rectangle(...)
     } else {
       return Rectangle(...)
     }
  }
  return self
}

但是如果test()函数中if #availableif分支返回不同类型,则编译器无法通过:

代码语言:Swift
复制
func test() -> some Shape {
  if #available(macOS 100, *) {
     if cond { ❌
       return Rectangle()
     } else {
       return Square()
     }
  }
  return self
}

本篇提议的这种语义调整非常适合现有模型,因为它确保每个平台始终有一个通用的基础类型。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • 提议的动机
  • 提议解决方案
  • 详细设计
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档