Swift 中关于”??”操作符一些有意思的事情

lkz596 3年前
   <p><img src="https://simg.open-open.com/show/488fef456d347a9987d2e1880ba94bff.jpg"></p>    <p>Swift 的语法在保证安全和健壮的基础上,又带有很多非常灵活的特性,比如 ?? 操作符就是其中一个。大家可能已经了解它,也可能有些同学不了解它,这里给大家整理了关于这个操作符值得一看的讨论。</p>    <h2>?? 操作符简述</h2>    <p>在展开讨论之前,我们先来了解这个操作符的作用是什么。这个操作符和 Optional 相关,让我们来看一个例子:</p>    <pre>  var a:Int?  print(a ?? 2) //2</pre>    <p>?? 操作符的左边是一个 Optional 值,右边是一个普通值,它的作用就是,如果左边的 Optional 值为 nil, 那么就使用右边的普通值作为返回值,如果左边的 Optional 不为 nil,则返回左边的 Optional 解包后的值。</p>    <p>比如我们这个例子中,变量 a 的值是 nil,所以 print 语句输出的就是后面的默认值 2。</p>    <p>刚才那个例子,如果不用 ?? 操作符,那么就是这样的逻辑:</p>    <pre>  print(a == nil ? 2 : a)</pre>    <p>让我们来看看 ?? 操作符的定义:</p>    <pre>  func ??                       (optional: T?, @autoclosure defaultValue: () throws -> T) -> T          </pre>    <p>把它简化一下,就是:</p>    <pre>  func ??                       (optional: T?, defaultValue: () throws -> T) -> T          </pre>    <p>它会接受一个 T 类型的 Optional 值,和一个 T 类型的普通值作为默认值, 然后返回一个解包后的 T 类型的值。这个最终返回值是要根据第一个参数是否为空来确定。</p>    <h2>?? 操作符的一些讨论</h2>    <p>?? 操作符可以让我们更方便的处理 Optional。 但它也存在一些小问题,Swift 的官方邮件组中大家就有讨论。比如这段代码:</p>    <pre>  func test(x: ((String) -> String)? = nil) {      let qf = { (p:String) -> String in return "tt" }      let fn = x ?? qf      fn("tt")  }</pre>    <p>test 函数它接受另外一个 Optional 类型的参数,然后在它内部用 ?? 操作符判断传进来的函数 x 是否为 nil,如果是,则用内部的另外一个函数 qf 来替代它。</p>    <p>初步来看,这样的代码应该没有什么问题,?? 的左边的变量类型是 ((String) -> String)?, 而右边的变量类型是 (String) -> String, 符合 ?? 操作符的定义。</p>    <p>但这段对 ?? 操作符的使用在实际编译的时候报错了。 我们必须把 qf 也声明成 Optional 类型,才可以使用:</p>    <pre>  let qf:((String) -> String)? = { (p:String) -> String in return "tt" }  let fn = (x ?? qf)!  fn("tt")</pre>    <p>这样虽然编译通过了,但却不符合 ?? 操作符的定义了。这次两边都是 Optional 类型,而表达式的返回值也是 Optional。</p>    <p>这时关于 ?? 在 Swift 邮件组中最近的一个讨论,最后的一次回复说这是 Swift 的一个 bug, 将会在 Swift 3.0 版本中修复,这时这个回复的原文:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/fbd02e02d75422265328f38ce9f7a79e.jpg"></p>    <h2>?? 操作符两边的类型可以不一样</h2>    <p>在邮件组里,还有一个讨论,大家可以看一下这段代码:</p>    <pre>  var a:Int?  print(a ?? "test")</pre>    <p>注意哦,左边的 a 是 Int? 类型的, 而右边是一个 String 字符串。 这段代码在目前的 Swift 2.2 编译上是可以编译通过的。</p>    <p>这个话题讨论的比较多,比较合理的一个解释是 Swift 编译器会根据当前表达式所在的场景进行相应的向上转型。</p>    <p>比如作为 print 函数的参数,编译器会将它们都看做 Any 类型,因为 print 函数接受的就是 Any 类型的参数。所以这个语句可以编译通过。</p>    <p>如果这个语句用在这个场景,就会导致编译失败了:</p>    <pre>  let x = a ?? "test"</pre>    <p>这次的场景,是将它的返回值赋值给 x。 但这次编译器就无法确定他们的通用类型,而将他们看做两个不同的类型,这样就导致编译失败了。</p>    <p>同样的,这段代码就可以编译通过:</p>    <pre>  let x = a ?? NSDate()</pre>    <p>按照刚才的逻辑,那么因为后面的类型是 NSDate 它们都可以是 NSObject 的子类,所以编译器将它们都作为 NSObject 的通用类型来处理了。</p>    <p>而上面的那个例子 let x = a ?? "test" 因为后面的字符串是 Swift 中的 String 类型,编译器就无法找到它们的通用父类,就导致失败了。</p>    <p>当然这是目前的讨论和分析结果,大家也可以一起来思考,来讲你的分析与大家交流。</p>    <h2>总结</h2>    <p>这次我们一起讨论了 ?? 操作符的作用,以及它在使用中的一些要注意的点。从这几处中可以看到语言本身的很多细节。Swift 邮件组是一个非常好的资源,里面会有很多关于各个话题的深入讨论,而且都是完全开放的,大家也可以到它的主页申请访问: <a href="/misc/goto?guid=4959672804935252161" rel="nofollow,noindex"> https://lists.swift.org/mailman/listinfo/swift-users </a></p>    <p>转自: <a href="/misc/goto?guid=4959672805020870957" rel="nofollow,noindex">swiftcafe</a></p>