在Swift中将可选项向下转换:使用as? Type还是as! Type?

103

在 Swift 中给定以下内容:

var optionalString: String?
let dict = NSDictionary()

以下两个语句有何实际区别:

optionalString = dict.objectForKey("SomeKey") as? String

对比

optionalString = dict.objectForKey("SomeKey") as! String?

1
另请参阅来自苹果的as!运算符 - mfaani
9个回答

156

实际上的区别在于:

var optionalString = dict["SomeKey"] as? String

optionalString将会是一个类型为String?的变量。 如果底层类型不是String,那么这只会无害地将nil分配给可选项。

var optionalString = dict["SomeKey"] as! String?

这句话表示,我知道这个东西是一个String?。使用第二种方式也会导致optionalString的类型为String?但是,如果底层类型不是字符串,它将会崩溃。

然后,第一种样式与if let一起使用,以安全地展开可选项:

if let string = dict["SomeKey"] as? String {
    // If I get here, I know that "SomeKey" is a valid key in the dictionary, I correctly
    // identified the type as String, and the value is now unwrapped and ready to use.  In
    // this case "string" has the type "String".
    print(string)
}

1
第一个方法不是总是更好吗?两个方法都返回类型为String的可选项?似乎第二个方法做的事情与第一个方法相同,但如果向下转换失败可能会崩溃。那么为什么要使用它呢? - Sikander
7
是的,@Sikander,第一个选项始终更好。我永远不会使用第二个选项。 - vacawama

15

as? Types - 表示向下转型过程是可选的。该过程可能成功,也可能失败(如果向下转型失败,则系统将返回nil)。无论如何,如果向下转型失败,都不会崩溃。

as! Type? - 在这里,向下转型的过程应该是成功的(!表示),结尾的问号表示最终结果是否可以为nil。

关于“!”和“?”的更多信息

我们来看两个例子

  1. 考虑:

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell
    

    在这里,我们不知道将标识符为“Cell”的单元格向下转换为UITableViewCell的结果是成功还是失败。如果失败,则返回nil(因此我们避免了崩溃)。我们可以按如下方式操作。

    if let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell {
        // If we reached here it means the down casting was successful
    }
    else {
        // unsuccessful down casting
    }
    

    那么让我们这样记住 - 如果?,意味着我们不确定值是否为nil(问号表示当我们不知道事情时)。

  2. 相比之下:

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell. 
    

    在这里,我们告诉编译器向下转换应该成功。如果失败了,系统将崩溃。因此,当我们确定值为非空时,我们使用 !


11

为了澄清vacawama所说的,这里有一个例子...

Swift 3.0:

import UIKit

let str_value:    Any   = String("abc")!
let strOpt_value: Any?  = String("abc")!
let strOpt_nil:   Any?  = (nil as String?)
let int_value:    Any   = Int(1)
let intOpt_value: Any?  = Int(1)
let intOpt_nil:   Any?  = (nil as Int?)

// as String
//str_value     as String // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//strOpt_value  as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//strOpt_nil    as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//int_value     as String // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//intOpt_value  as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//intOpt_nil    as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?

// as? String
  str_value     as? String // == "abc"
  strOpt_value  as? String // == "abc"
  strOpt_nil    as? String // == nil
  int_value     as? String // == nil
  intOpt_value  as? String // == nil
  intOpt_nil    as? String // == nil

// as! String
  str_value     as! String // == "abc"
  strOpt_value  as! String // == "abc"
//strOpt_nil    as! String // Run-Time Error: unexpectedly found nil while unwrapping an Optional value.
//int_value     as! String // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
//intOpt_value  as! String // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
//intOpt_nil    as! String // Run-Time Error: unexpectedly found nil while unwrapping an Optional value.

// as String?
//str_value     as String? // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//strOpt_value  as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//strOpt_nil    as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//int_value     as String? // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//intOpt_value  as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//intOpt_nil    as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?

// as? String?
//str_value     as? String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  strOpt_value  as? String? // == "abc"
  strOpt_nil    as? String? // == nil
//int_value     as? String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  intOpt_value  as? String? // == nil
  intOpt_nil    as? String? // == nil

// as! String?
//str_value     as! String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  strOpt_value  as! String? // == "abc"
  strOpt_nil    as! String? // == nil
//int_value     as! String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
//intOpt_value  as! String? // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
  intOpt_nil    as! String? // == nil

// let _ = ... as String
//if let _ = str_value    as String { true } // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_value as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_nil   as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = int_value    as String { true } // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_value as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_nil   as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?

// let _ = ... as? String
if let _ = str_value    as? String { true } // true
if let _ = strOpt_value as? String { true } // true
if let _ = strOpt_nil   as? String { true } // false
if let _ = int_value    as? String { true } // false
if let _ = intOpt_value as? String { true } // false
if let _ = intOpt_nil   as? String { true } // false

// let _ = ... as! String
//if let _ = str_value    as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = strOpt_value as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = strOpt_nil   as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = int_value    as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = intOpt_value as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = intOpt_nil   as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'

// let _ = ... as String?
//if let _ = str_value    as String? { true } // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//if let _ = strOpt_value as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_nil   as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = int_value    as String? { true } // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//if let _ = intOpt_value as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_nil   as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?

// let _ = ... as? String?
//if let _ = str_value    as? String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = strOpt_value as? String? { true } // true
  if let _ = strOpt_nil   as? String? { true } // true
//if let _ = int_value    as? String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = intOpt_value as? String? { true } // false
  if let _ = intOpt_nil   as? String? { true } // true

// let _ = ... as! String?
//if let _ = str_value    as! String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = strOpt_value as! String? { true } // true
  if let _ = strOpt_nil   as! String? { true } // false
//if let _ = int_value    as! String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
//if let _ = intOpt_value as! String? { true } // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
  if let _ = intOpt_nil   as! String? { true } // false

Swift 2.0:

import UIKit

let str:    AnyObject   = String("abc")
let strOpt: AnyObject?  = String("abc")
let strNil: AnyObject?  = (nil as String?)
let int:    AnyObject   = Int(1)
let intOpt: AnyObject?  = Int(1)
let intNil: AnyObject?  = (nil as Int?)

str    as? String // == "abc"
strOpt as? String // == "abc"
strNil as? String // == nil
int    as? String // == nil
intOpt as? String // == nil
intNil as? String // == nil

str    as! String? // Compile-Time Error: Cannot downcast from 'AnyObject' to a more optional type 'String?'
strOpt as! String? // == "abc"
strNil as! String? // == nil
int    as! String? // Compile-Time Error: Cannot downcast from 'AnyObject' to a more optional type 'String?'
intOpt as! String? // Run-Time Error: Could not cast value of type '__NSCFNumber' to 'NSString'
intNil as! String? // == nil

你的例子很好,但是你能用相同的例子解释一下在 downcasting 时使用 as! 替代 as 吗?当使用 let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell 时,我猜 as? 已经足够了,为什么需要 as! 呢? - LC 웃
让cell = tableView.dequeueReusableCellWithIdentifier("Cell")作为UITableViewCell类型的可选对象。- 在这里,我们不知道将标识符“Cell”下转换为UITableViewCell的结果是否为空。如果为空,则返回nil(因此我们在此避免崩溃)。 - jishnu bala
有趣的是,intNil as! String? // ==nil 不会导致崩溃!!!???因为 Optional<Int>.None 与 Optional<String>.None 是不同的。 - onmyway133
为什么你要将 as? 转换成 String?为什么不将其转换成 String??为什么不将 as! 转换成 String - mfaani
尝试在Swift 3中完成这个playground,但你必须使用Any而不是AnyObject - mfaani

9
  • as 用于向上转型和类型转换为桥接类型
  • as? 用于安全转换,如果失败则返回nil
  • as! 用于强制转换,如果失败则崩溃

注意:

  • as! 无法将原始类型转换为可选类型

示例:

let rawString: AnyObject = "I love swift"
let optionalString: AnyObject? = "we love swift"
let nilString: AnyObject? = (nil as String?)

let rawInt: AnyObject = Int(3)
let optionalInt: AnyObject? = Int(3)
let nilInt: AnyObject? = (nil as Int?)

示例

var age: Int? = nil
var height: Int? = 180

通过在数据类型后面立即添加“?”告诉编译器变量可能包含数字,也可能不包含。很棒!请注意,定义可选常量并没有什么意义 - 你只能设置它们的值一次,因此你可以判断它们的值是否为空。
何时使用“?”和“!”?
我们假设有一个基于UIKit的简单应用程序。 我们在视图控制器中有一些代码,并希望在其上方呈现新的视图控制器。 我们需要决定是否使用导航控制器将新视图推送到屏幕上。
众所周知,每个ViewController实例都有一个navigation controller属性。如果你正在构建基于导航控制器的应用程序,则应用程序的主视图控制器的此属性会自动设置,您可以使用它来推送或弹出视图控制器。 如果您使用单个应用程序项目模板 - 将不会为您自动创建导航控制器,因此您的应用程序的默认视图控制器将不具有存储在navigationController属性中的任何内容。
我相信你已经猜到这正是一个可选数据类型的情况。如果你检查UIViewController,你会发现该属性被定义为:
var navigationController: UINavigationController? { get }

让我们回到我们的使用案例。如果您确定您的视图控制器始终会有导航控制器,那么可以强制对其进行解包:

controller.navigationController!.pushViewController(myViewController, animated: true)

当您在属性名称后面加上!时,您告诉编译器:“我不关心此属性是否为可选项,我知道当此代码执行时,总会有一个值存储,因此请将此可选项视为普通数据类型。” 好极了,但是如果您的视图控制器没有导航控制器呢? 如果您建议navigationController中总会存储一个值是错误的呢? 您的应用程序将崩溃。就这么简单而且难看。

因此,仅在101%确定安全时使用!

如果您不确定是否始终会有导航控制器,则可以使用?而不是!:
controller.navigationController?.pushViewController(myViewController, animated: true)

在属性名称后面加上问号告诉编译器“我不知道这个属性是否包含空值或有值,因此:如果它有值,请使用它;否则就将整个表达式视为 nil。” 问号的作用是让你只在有导航控制器的情况下使用该属性。不需要任何 if 语句或任何类型的转换。当你不关心是否有导航控制器,只想在有时执行某些操作时,这种语法是完美的选择。
特别感谢Fantageek

8

在Swift中,Downcasting有两种不同的形式。

(as?),也称为条件形式,返回你试图向下转换的类型的可选值。

当你不确定向下转换是否成功时,可以使用它。这个运算符的这种形式总是返回一个可选值, 如果向下转换不可能,该值将为nil。这使得你可以检查向下转换是否成功。


(as!),也称为强制形式,尝试进行向下转换,并将结果作为单个复合操作进行强制解包。

只有当你确信向下转换总是成功时才应该使用它。如果尝试向下转换到错误的类类型,则此运算符的这种形式将触发运行时错误

有关更多详细信息,请查看Apple文档中的Type Casting部分。


4
也许这个代码示例可以帮助某些人理解原理:
var dict = [Int:Any]()
dict[1] = 15

let x = dict[1] as? String
print(x) // nil because dict[1] is an Int

dict[2] = "Yo"

let z = dict[2] as! String?
print(z) // optional("Yo")
let zz = dict[1] as! String // crashes because a forced downcast fails


let m = dict[3] as! String?
print(m) // nil. the forced downcast succeeds, but dict[3] has no value

另外,让 z2 = dict[2] as! String // "Yo"(非可选项) - Jay

0

针对这些运算符,最容易记住的方法可能是在Swift中使用以下模式:!意味着“这可能会导致错误”,而?表示“这可能为空值”。

参考: https://developer.apple.com/swift/blog/?id=23



-1

我是Swift的新手,写这个例子是为了尝试解释我对“可选项”的理解。如果我有错误,请纠正我。

谢谢。


class Optional {

    var lName:AnyObject! = "1"

    var lastName:String!
}

let obj = Optional()

print(obj.lName)

print(obj.lName!)

obj.lastName = obj.lName as? String

print(obj.lastName)

(1) : obj.lastName = obj.lName as! String

vs

(2) : obj.lastName = obj.lName as? String

答案:(1) 程序员确信"obj.lName"包含字符串类型对象。所以只需将该值赋给"obj.lastName"

现在,如果程序员正确,则代表"obj.lName"是字符串类型对象,就没有问题。"obj.lastName" 将设置为相同的值。

但是,如果程序员错误,即"obj.lName"不是字符串类型对象,例如它包含其他类型的对象,例如"NSNumber"等,则会导致崩溃(运行时错误)。

(2) 程序员无法确定"obj.lName"包含字符串类型对象还是其他任何类型的对象。因此,只有在字符串类型时才将该值设置为"obj.lastName"

现在,如果程序员正确,则代表"obj.lName"是字符串类型对象,就没有问题。"obj.lastName"将设置为相同的值。

但是如果程序员出错了,意味着obj.lName不是字符串类型的对象,即它包含其他类型的对象,如"NSNumber"等。那么“obj.lastName”将被设置为nil值。因此,没有崩溃(开心:)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接