【问题标题】:Shorthand for wrapping a swift variable in an optional?将 swift 变量包装在可选项中的简写?
【发布时间】:2015-06-27 19:56:27
【问题描述】:

Swift 允许我们使用简写符号str! 来解开一个可选项。但是如果我们想反其道而行之呢?

假设我有一个变量:

var str = String() // String

是否有任何简写符号可以将其转换为可选的(即String?String!)?

(例如,我想做var strOptional = ?(str)之类的事情。)

或者,如果这个符号没有简写,我如何在不明确提及其类型的情况下将其转换为可选项(例如,我不想提及 String)。

换句话说,我知道我可以使用以下任何方法将变量包装为可选项:

var strOptional = str as String?
var strOptional: String? = str
var strOptional = String?(str)

...但在每种情况下,我都必须明确写成String

如果没有速记语法,我宁愿写类似:var strOptional = str as typeof?(str)。 (这样做的好处是,如果代码库中变量的类型经常变化,那就少了一个需要更新的地方。)


就这在哪里有用的真实世界示例而言,假设我想使用 AVCaptureDevice 并使用以下代码:

let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
device.lockForConfiguration(nil)

lockForConfiguration() 将在运行时在没有摄像机的设备上崩溃,编译器不会警告我。原因是defaultDeviceWithMediaType 根据文档[1] 可能返回nil,但它被定义为返回AVCaptureDevice!

要修复这样的错误 API,最好执行以下操作:

let device = ?(AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo))

...获取AVCaptureDevice?,并让编译器捕捉我可能犯的任何错误。

目前,我必须诉诸更冗长的:

let device: AVCaptureDevice? = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)

另一个例子:

在这种情况下,我想给我的变量一个默认值,它是一个字符串,但稍后,我可能想给它分配一个nil 值。

var myString = "Hi"
// ...
if (someCondition()) {
    myString = nil // Syntax error: myString is String
}

目前我不得不求助于var myString: String? = "Hi",但像var myString = ?("Hi") 这样的东西会不那么冗长。


[1] 如果您打开 AVCaptureDevice.h,您将看到以下有关返回值的文档:“具有给定媒体类型的默认设备,如果不存在具有该媒体类型的设备,则为零。”

【问题讨论】:

  • 嗯,我删除了我的答案。您有足够的代表仍然可以看到它。我的大部分答案仍然适用,但我认为AVCaptureDevice 是一个坚持。当 Apple 首次引入 Swift 时,基本上 everything 返回隐式展开的可选项,但他们应该将所有这些转换为非可选项或常规可选项。这就是 Objective-C 可空性宏的用途。
  • @nhgrif:感谢 cmets。我添加了另一个不涉及 Apple 框架的可能有用的示例。
  • 在你的第二个例子中,我认为明确你的类型很重要。 Apple 没有将预处理器宏引入 Swift 是有原因的……
  • 我在这两点上都同意@nhgrif。 AVFoundation 还没有被移植。后者显然是let myString = Optional("Hi")(不过,这不适用于! 类型)。如果有人想出一些简单的语法,我不会反对,但我当然不会创建运算符或类似的东西。您可以轻松地将其封装在一个名为 lift 的函数中,该函数接受一个可选或非可选并返回一个可选。

标签: swift variables syntax casting optional


【解决方案1】:

Optional 只是 swift 中的一个枚举,所以你可以这样做:Optional(str)

从 swift 界面:

/// A type that can represent either a `Wrapped` value or `nil`, the absence
/// of a value.
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
    case None
    case Some(Wrapped)
    /// Construct a `nil` instance.
    public init()
    /// Construct a non-`nil` instance that stores `some`.
    public init(_ some: Wrapped)
    /// If `self == nil`, returns `nil`.  Otherwise, returns `f(self!)`.
    @warn_unused_result
    public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
    /// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
    @warn_unused_result
    public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
    /// Create an instance initialized with `nil`.
    public init(nilLiteral: ())
}

【讨论】:

    【解决方案2】:

    根据我的理解,问题已经提出,本着轻松的精神,您可以定义一个后缀运算符,该运算符使用的字符比您的理想解决方案少一个字符(一对括号和一个问号):

    postfix operator =? {}
    postfix func =? <T> (rhs: T) -> T? { return rhs }
    postfix func =? <T> (rhs: T!) -> T? { return rhs ?? nil }
    
    var x = 3=?
    x is Int? //--> true
    
    var imp: Int! = 3
    var opt = imp=?
    opt is Int? //--> true
    

    至于您提到的 Apple 的 API,因为它们已经返回一个可选项,尽管是一个隐式展开的可选项,您可以使用 nil 合并运算符 ?? 将其转换为纯可选项:

    let y: Int! = 3 // posing for a rogue API 
    
    y is Int? //--> false
    
    let z = y ?? nil
    
    z is Int? //--> true
    

    也许更有趣的使用狡猾的运算符是一个高阶运算符函数,它将T -&gt; Us 提升为T? -&gt; U?s...

    func opt <T, U> (f: T -> U) -> T? -> U? { // we'll implement this as an operator
        return { $0.map(f) }
    }
    
    postfix operator ->? {}
    postfix func ->? <T, U> (f: T -> U) -> T? -> U? {
        return { $0.map(f) }
    }
    

    现在,让我们使用它:

    let square: Int -> Int = { $0 * $0 }
    
    let i: Int? = 3
    
    square->? (i)
    

    相当于:

    opt(square)(i)
    

    相当于(但可能比它更通用):

    i.map(square)
    

    当与管道转发运算符一起使用时,这一切都更有意义:

    infix operator |> { associativity left precedence 89 }
    func |> <A, B>(a: A, f: A -> B) -> B {
        return f(a)
    }
    

    那么你可以这样做:

    i |> opt(square)
    

    i |> square->?
    

    或者我们可以将它们合并为一个可选管道转发,它也可以处理隐式展开的可选:

    infix operator ?> { associativity left precedence 89 }
    func ?> <T, U> (lhs: T?, rhs: T -> U) -> U? {
        return lhs.map(rhs)
    }
    
    let im: Int! = 5
    let op: Int? = 5
    
    im ?> square |> debugPrintln //--> Optional(25)
    op ?> square |> debugPrintln //--> Optional(25)
    

    相当于:

    debugPrintln(op.map(square))
    

    【讨论】:

      【解决方案3】:

      要包装一个变量,您可以使用运算符重载和泛型函数。例如:

      prefix operator ??? {}
      
      prefix func ??? <T> (x: T) -> T? {
          return Optional(x)
      }
      

      要将这与您的第二个示例联系起来,您将执行以下操作:

      var myString = ???"Hi" // Optional("Hi")
      
      if someCondition() {
          myString = nil
      }
      

      【讨论】:

      • 我正在研究完全相同的答案。这并不能解决函数返回显式解包选项的问题,您希望将其视为常规选项。例如,如果func test()-&gt;Int! { return 17 } 被这样调用,let a = ??test(),那么a 将具有Int!? 类型而不是Int?
      • 不是“??”一个糟糕的运算符选择,看看这如何是 nil 合并运算符的符号?
      猜你喜欢
      • 2015-12-03
      • 2015-09-13
      • 1970-01-01
      • 2015-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-17
      相关资源
      最近更新 更多