前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Swift 5.7 使用 'if let a' 替换 'if let a = a'

Swift 5.7 使用 'if let a' 替换 'if let a = a'

原创
作者头像
DerekYuYi
修改2022-06-28 08:35:54
1.3K0
修改2022-06-28 08:35:54
举报
文章被收录于专栏:Swift-开源分析

介绍:

Swift 5.6 中比较常见的可选值解包绑定是使用 if let foo = foo { ... } 来对可选值解包,从而隐藏真正的可选值。这种模式要求开发者重复引用变量标识符 2 次,这样写的缺点在于解包时表达时会显得冗余,尤其是当变量名很长时。所以我们希望为可选值解包引入一种更简短的语法,类似以下语法:

代码语言:Swift
复制
let foo: Foo? = ...

if let foo {
	// 'foo' is of type `Foo`
}

目的

减少重复定义,尤其是对特别长的变量名,让代码可读性更强。

举个例子,下面例子中对可选值someLengthyVariableNameanotherImportantVariable的解包读写性算是比较差:

代码语言:Swift
复制
let someLengthyVariableName: Foo? = ...
let anotherImportantVariable: Bar? = ...

if let someLengthyVariableName = someLengthyVariableName, let anotherImportantVariable = anotherImportantVariable {
    ...
}

解包的变量名太长了,虽然可以通过重命名变量名来它变得更短些,但是语义化就不那么明确,比如使用 a 和 b 代替:

代码语言:Swift
复制
if let a = someLengthyVariableName, let b = anotherImportantVariable {
    ...
}

这种方式描述可选变量解包之后的值就不够明确,在上下文重复调用 a 和 b 时,你并不是一直清楚 a 和 b 表示的准确含义。由于我们要隐藏可选值,那么解包之后的变量应该尽量还原本意,这种做法其实降低了语义化。语言设计准则中并不鼓励使用简短缩写的变量名称,而是应该考虑使用描述变量名称的语义化设计。这个问题将在 Swift 5.7 得到解决。

Swift5.7 中提议的解决方案:

如果我们删除解包中右边的表达式,让编译器来自动隐藏当前的变量,只保留左边的变量名,这时这些可选绑定表达会更加简洁,而且可读性更强。

代码语言:Swift
复制
let someLengthyVariableName: Foo? = ...
let anotherImportantVariable: Bar? = ...

if let someLengthyVariableName, let anotherImportantVariable {
    ...
}

这种方案既保持最清晰的语义(someLengthyVariableName),又减少了冗余的定义,因为对开发者来说,不需要写= someLengthyVariableName, 只需要写一个核心的 someLengthyVariableName, 这个扩展方案对可选绑定条件的现有语法来说,是不是特别自然?

设计细节

现在所有的条件控制都可以使用上述语法:

代码语言:Swift
复制
if let foo { ... }
if var foo { ... }

else if let foo { ... }
else if var foo { ... }

guard let foo else { ... }
guard var foo else { ... }

while let foo { ... }
while var foo { ... }

编译器会合成一个被隐藏变量的初始化表达式,例如:

代码语言:Swift
复制
if let foo { ... }

会在编译时被转为:

代码语言:Swift
复制
if let foo = foo { ... }

同时编译器也允许你显式声明类型,比如:

代码语言:Swift
复制
if let foo: Foo { ... }

会在编译时转为:

代码语言:Swift
复制
if let foo: Foo = foo { ... }

后面的模式既是一个计算表达式,也是新定义的非可选变量的标识符。此类语法的现有先例包括闭包捕获列表,其工作方式相同:

代码语言:Swift
复制
let foo: Foo
let closure = { [foo] in // `foo` is both an expression and the identifier 
    ...                  // for a new variable defined within the closure
} 

所以,这个语法上只能用合法的标志符,例如,下面这个例子就不合法:

代码语言:Swift
复制
if let foo.bar { ... } // ? unwrap condition requires a valid identifier
       ^               // fix-it: insert `<#identifier#> = `

如果支持访问对象的属性及成员,是不是更加强大?这点再下面的展望章节中也有提到。

新语法同样支持识别隐式 self,这点与现有的可选绑定一样。比如下面例子:

代码语言:Swift
复制
struct UserView: View {
	let name: String
	let emailAddress: String?
	
	var body: some View {
		VStack {
			Text(user.name)
			//  和 `if let emailAddress = emailAddress { ... }` 等价 
			// 解包 `self.emailAddress`
			if let emailAddress {
				Text(emailAddress)
			}
		}
	}
}

后续展望

Swift 5.7 已经实现了该提议,但是社区还是有不少的提议,比如支持可选转换,支持成员属性等。下面再看下这几点。

支持使用可选转换

未来可以扩展该语法,支持对可选值的类型转换,比如:

代码语言:Swift
复制
if let foo as? Bar { ... }

其实也就是等价于:

代码语言:Swift
复制
if let foo = foo as? Bar { ... }

这种在日常开发中很常见,比如在解包参数 Any?, AnyObject?,T? 等为某个确定类型.

支持 ref& 引用操作

提高Swift性能预测的路线图 中讨论了新的 refinout 引用标识,用于创建现有变量,而不需要复制变量(通过强制独占内存访问)。为了与 let / var 保持一致,支持这些新引用操作符也会变得有意义。支持之前:

代码语言:Swift
复制
if ref foo = foo {
  // if `foo` is not nil, it is borrowed and made available as a non-optional, immutable variable
}

if inout foo = &foo {
  // if `foo` is not nil, it is borrowed and made available as a non-optional, mutable variable
}

使用新语法支持之后, 我们可以这样写:

代码语言:Swift
复制
if ref foo {
  // if `foo` is not nil, it is borrowed and made available as a non-optional, immutable variable
}

if inout &foo {
  // if `foo` is not nil, it is borrowed and made available as a non-optional, mutable variable
}
支持解包对象的成员

当前提议并没有实现对其他对象里的成员进行解包简写支持。比如下面的语法会出错:

p

代码语言:Swift
复制
if let foo.bar { ... } // ?

其实有几个方式可以考虑用来支持上述这种语法访问。

第一种方式是在解包变量的内在作用域内,编译器自动合成标志符名称。比如,编译器会对 if let foo.bar 引入 一个新的名为foo 或者 fooBar 不可选变量。

另外一种方式是使用新操作符 refinout (这个概念上节提到过)。这些新操作符会让编译器独占访问变量内存,也就是直接对底层地址存储的访问,因此这种方式不需要在内部作用域上使用唯一标志符名称,也不需要进行地址复制,它将直接允许我们解包对象的成员。例如:

代码语言:Swift
复制
// `mother.father.sister` is optional

if ref mother.father.sister {
  // `mother.father.sister` is non-optional and immutable
}

if inout &mother.father.sister {
  // `mother.father.sister` is non-optional and mutable
}

其他

社区还有几个其他的备选方案来实现if let, 有兴趣可以看提议中的Alternatives considered章节。

Proposal: 0345-if-let-shorthand.md

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍:
  • 目的
  • Swift5.7 中提议的解决方案:
  • 设计细节
  • 后续展望
    • 支持使用可选转换
      • 支持 ref、& 引用操作
    • 支持解包对象的成员
    • 其他
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档