ios Swift ! and ?

swift ?和!之间区别:

Swift 引入的最不一样的可能就是 Optional Value 了。在声明时,我们可以通过在类型后面加一个? 来将变量声明为 Optional 的。如果不是 Optional 的变量,那么它就必须有值。而如果没有值的话,我们使用 Optional 并且将它设置为 nil 来表示没有值。

//num 不是一个 Int
var num: Int?  
//num 没有值
num = nil  //nil  
//num 有值
num = 3    //{Some 3}  

Apple 在 Session 上告诉我们,Optinal Value 其实就是一个盒子,你盒子里可能装着实际的值,也可能什么都没装。

我们看到 Session 里或者文档里天天说 Optional Optional,但是我们在代码里基本一个 Optional 都没有看到,这是为什么呢?而且,上面代码中给 num 赋值为 3 的时候的那个输出为什么看起来有点奇怪?其实,在声明类型时的这个 ? 仅仅只是 Apple 为了简化写法而提供的一个语法糖。实际上我们是有 Optional 类型的声明,就这里的 num 为例,最正规的写法应该是这样的:

//真 Optional 声明和使用
var num: Optional<Int>  
num = Optional<Int>()  
num = Optional<Int>(3)  

没错,num 不是 Int 类型,它是一个 Optional 类型。到底什么是 Optional 呢,点进去看看:

enum Optional<T> : LogicValue, Reflectable {  
    case None
    case Some(T)
    init()
    init(_ some: T)

    /// Allow use in a Boolean context.
    func getLogicValue() -> Bool

    /// Haskell‘s fmap, which was mis-named
    func map<U>(f: (T) -> U) -> U?
    func getMirror() -> Mirror
}

你也许会大吃一惊。我们每天和 Swift 打交道用的 Optional 居然是一个泛型枚举 enum,而其实我们在使用这个枚举时,如果没有值,我们就规定这个枚举的是 .None,如果有,那么它就是Some(value)(带值枚举这里不展开了,有不明白的话请看文档吧)。而这个枚举又恰好实现了LogicValue 接口,这也就是为什么我们能使用 if 来对一个 Optinal 的值进行判断并进一步进行 unwrap 的依据。

var num: Optional<Int> = 3  
if num {       //因为有 LogicValue,  
               //.None 时 getLogicValue() 返回 false
               //.Some 时返回 true
   var realInt = num!
   realInt     //3
}

既然 var num: Int? = nil 其实给 num 赋的值是一个枚举的话,那这个 nil 到底又是什么?它被赋值到哪里去了?一直注意的是,Swift 里的 nil 和 objc 里的 nil 完全不是一回事儿。objc 的 nil 是一个实实在在的指针,它指向一个空的对象。而这里的 nil 虽然代表空,但它只是一个语意上的概念,确是有实际的类型的,看看 Swift 的 nil 到底是什么吧:

/// A null sentinel value.
var nil: NilType { get }  

nil 其实只是 NilType 的一个变量,而且这个变量是一个 getter。Swift 给了我们一个文档注释,告诉我们 nil 其实只是一个 null 的标记值。实际上我们在声明或者赋值一个 Optional 的变量时,? 语法糖做的事情就是声明一个 Optional<T>,然后查看等号右边是不是 nil 这个标记值。如果不是,则使用init(_ some: T) 用等号右边的类型 T 的值生成一个 .Some 枚举并赋值给这个 Optional 变量;如果是 nil,将其赋为 None 枚举。

所以说,Optional背后的故事,其实被这个小小的 ? 隐藏了。

我想,Optional 讨论到这里就差不多了,还有三个小问题需要说明。

首先,NilType 这个类型非常特殊,它似乎是个 built in 的类型,我现在没有拿到关于它的任何资料。我本身逆向是个小白,现在看起来 Swift 的逆向难度也比较大,所以关于 NilType 的一些行为还是只能猜测。而关于 nil 这一 NilType 的类型的变量来说,猜测的话,它可能是 Optional.None 的一种类似多型表现,因为首先它确实是指向 0x0 的,并且与 Optional.None 的 content 的内容指向一致。但是具体细节还要等待挖掘或者公布了。

其次,Apple 推荐我们在 unwrap 的时候使用一种所谓的隐式方法,即下面这种方式来 unwrap:

var num: Int? = 3  
if let n = num {  
    //have a num
} else {
    //no num
}

最后,这样隐式调用足够安全,性能上似乎应该也做优化(有点忘了..似乎说过),推荐在 unwrap 的时候尽可能写这样的推断,而减少直接进行 unwrap 这种行为。

最后一个问题是 Optional 的变量也可以是 Optinal。因为 Optional 就相当于一个黑盒子,可以知道盒子里有没有东西 (通过 LogicValue),也可以打开这个盒子 (unwrap) 来拿到里面的东西 (你要的类型的变量或者代表没有东西的 nil)。请注意,这里没有任何规则限制一个 Optional 的量不能再次被 Optional,比如下面这种情况是完全 OK 的:

var str: String? = "Hi"         //{Some "Hi"}  
var anotherStr: String?? = str  //{{Some "Hi"}}  

这其实是没有多少疑问的,很完美的两层 Optional,使用的时候也一层层解开就好。但是如果是 nil 的话,在这里就有点尴尬...

var str: String? = nil  
var anotherStr: String?? = nil  

 

? 那是什么??,! 原来如此!!

问号和叹号现在的用法都是原来 objc 中没有的概念。说起来简单也简单,但是背后也还是不少玄机。原来就已经存在的用法就不说了,这里把新用法从浅入深逐个总结一下吧。

首先是 ?

  • ? 放在类型后面作为 Optional 类型的标记

这个用法上面已经说过,其实就是一个 Optional<T> 的语法糖,自动将等号后面的内容 wrap 成 Optional。给个用例,不再多说:

var num: Int? = nil        //声明一个 Int 的 Optional,并将其设为啥都没有  
var str: String? = "Hello" //声明一个 String 的 Optional,并给它一个字符串  
  • ? 放在某个 Optional 变量后面,表示对这个变量进行判断,并且隐式地 unwrap。比如说
foo?.somemethod()  

相比起一般的先判断再调用,类似这样的判断的好处是一旦判断为 nil 或者说是 false,语句便不再继续执行,而是直接返回一个 nil。上面的写法等价于

if let maybeFoo = foo {  
    maybeFoo.somemethod()
}

这种写法更存在价值的地方在于可以链式调用,也就是所谓的 Optional Chaining,这样可以避免一大堆的条件分支,而使代码变得易读简洁。比如:

 

if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {  
    println("John‘s uppercase building identifier is \(upper).")
}

 

注意最后 buildingIdentifier 后面的问号是在 () 之后的,这代表了这个 Optional 的判断对象是buildingIdentifier() 的返回值。

  • ? 放在某个 optional 的 protocol 方法的括号前面,以表示询问是否可以对该方法调用

这中用法相当于以前 objc 中的 -respondsToSelector: 的判断,如果对象响应这个方法的话,则进行调用。例子:

delegate?.questionViewControllerDidGetResult?(self, result)  

 

中的第二个问号。注意和上面在 () 后的问号不一样,这里是在 () 之前的,表示对方法的询问。

其实在 Swift 中,默认的 potocol 类型是没有 optional 的方法的,因为基于这个前提,可以对类型安全进行确保。但是 Cocoa 框架中的 protocol 还是有很多 optional 的方法,对于这些可选的接口方法,或者你想要声明一个带有可选方法的接口时,必须要在声明 protocol 时再其前面加上 @objc 关键字,并在可选方法前面加上 @optional

@objc protocol CounterDataSource {
    @optional func optionalMethod() -> Int
    func requiredMethod() -> Int
    @optional var optionalGetter: Int { get }
}

 

然后是 ! 新用法的总结

  • ! 放在 Optional 变量的后面,表示强制的 unwrap 转换:
foo!.somemethod()  

 

这将会使一个 Optional<T> 的量被转换为 T。但是需要特别注意,如果这个 Optional 的量是 nil 的话,这种转换会在运行时让程序崩溃。所以在直接写 ! 转换的时候一定要非常注意,只有在有必死决心和十足把握时才做 ! 强转。如果待转换量有可能是 nil 的话,我们最好使用 if let 的语法来做一个判断和隐式转换,保证安全。

  • ! 放在类型后面,表示强制的隐式转换。

这种情况下和 ? 放在类型后面的行为比较类似,都是一个类型声明的语法糖。? 声明的是 Optional,而 ! 其实声明的是一个 ImplicitlyUnwrappedOptional 类型。首先需要明确的是,这个类型是一个struct,其中关键部分是一个 Optional<T> 的 value,和一组从这个 value 里取值的 getter 和 方法:

struct ImplicitlyUnwrappedOptional<T> : LogicValue, Reflectable {  
    var value: T?
    //...
    static var None: T! { get }
    static func Some(value: T) -> T!
    //...
}

 

从外界来看,其实这和 Optional 的变量是类似的,有 Some 有 None。其实从本质上来说,ImplicitlyUnwrappedOptional 就是一个存储了 Optional,实现了 Optional 对外的方法特性的一个类型,唯一不同的是,Optional 需要我们手动进行进行 unwrap (不管是使用 var! 还是 let if 赋值,总要我们做点什么),而 ImplicitlyUnwrappedOptional 则会在使用的时候自动地去 unwrap,并对继续之后的操作调用,而不必去增加一次手动的显示/隐式操作。

为什么要这么设计呢?主要是基于 objc 的 Cocoa 框架的两点考虑和妥协。

首先是 objc 中是有指向空对象的指针的,就是我们所习惯的 nil。在 Swift 中,为了处理和 objc 的 nil 的兼容,我们需要一个可为空的量。而因为 Swift 的目的就是打造一个完全类型安全的语言,因此不仅对于 class,对于其他的类型结构我们也需要类型安全。于是很自然地,我们可以使用 Optional 的空来对 objc 做等效。因为 Cocoa 框架有大量的 API 都会返回 nil,因此我们在用 Swift 表达它们的时候,也需要换成对应的既可以表示存在,也可以表示不存在的 Optional

那这样的话,不是直接用 Optional 就好了么?为什么要弄出一个 ImplicitlyUnwrappedOptional 呢?因为易用性。如果全部用 Optional 包装的话,在调用很多 API 时我们就都需要转来转去,十分麻烦。而对于 ImplicitlyUnwrappedOptional 因为编译器为我们进行了很多处理,使得我们在确信返回值或者要传递的值不是空的时候,可以很方便的不需要做任何转换,直接使用。但是对于那些 Cocoa 有可能返回 nil,我们本来就需要检查的方法,我们还是应该写 if 来进行转换和检查。

比如说,以下的写法就会在运行时导致一个 EXC_BAD_INSTRUCTION

let formatter = NSDateFormatter()  
let now = formatter.dateFromString("not_valid")  
let soon = now.dateByAddingTimeInterval(5.0) // EXC_BAD_INSTRUCTION  

 

因为 dateFromString 返回的是一个 NSDate!,而我们的输入在原来会导致一个 nil 的返回,这里我们在使用 now 之前需要进行检查:

let formatter = NSDateFormatter()  
let now = formatter.dateFromString("not_valid")  
if let realNow = now {  
    realNow.dateByAddingTimeInterval(5.0)
} else {
    println("Bad Date")
}

 

这和以前在 objc 时代做的事情差不多,或者,用更 Swift 的方式做

let formatter = NSDateFormatter()  
let now = formatter.dateFromString("not_valid")  
let soon = now?.dateByAddingTimeInterval(5.0)  

 

 

 

 

 

ios Swift ! and ?,,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。