swift ?和!之间区别:

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

//num 不是一个 Int
var num: Int?
//num 没有值
num = nil //nil
//num 有值
num = //{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>()

没错,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> =
if num { //因为有 LogicValue,
//.None 时 getLogicValue() 返回 false
//.Some 时返回 true
var realInt = num!
realInt //
}

既然 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? =
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 ?的更多相关文章

  1. iOS swift的xcworkspace多项目管理(架构思想)

    iOS  swift的xcworkspace多项目管理(架构思想) 技术说明: 今天在这里分享 swift下的 xcworkspace多项目管理(架构思想),能为我们在开发中带来哪些便捷?能为我们对整 ...

  2. iOS Swift 模块练习/swift基础学习

    SWIFT项目练习     SWIFT项目练习2 iOS Swift基础知识代码 推荐:Swift学习使用知识代码软件 0.swift中的宏定义(使用方法代替宏) 一.视图  +控件 1.UIImag ...

  3. ios swift 实现饼状图进度条,swift环形进度条

    ios swift 实现饼状图进度条 // // ProgressControl.swift // L02MyProgressControl // // Created by plter on 7/2 ...

  4. Building gRPC Client iOS Swift Note Taking App

    gRPC is an universal remote procedure call framework developed by Google that has been gaining inter ...

  5. iOS Swift WisdomScanKit图片浏览器功能SDK

    iOS Swift WisdomScanKit图片浏览器功能SDK使用 一:简介      WisdomScanKit 由 Swift4.2版编写,完全兼容OC项目调用. WisdomScanKit的 ...

  6. iOS Swift WisdomScanKit二维码扫码SDK,自定义全屏拍照SDK,系统相册图片浏览,编辑SDK

    iOS Swift WisdomScanKit 是一款强大的集二维码扫码,自定义全屏拍照,系统相册图片编辑多选和系统相册图片浏览功能于一身的 Framework SDK [1]前言:    今天给大家 ...

  7. iOS Swift WisdomHUD 提示界面框架

    iOS Swift WisdomHUD 提示界面框架  Framework Use profile(应用简介) 一:WisdomHUD简介 今天给大家介绍一款iOS的界面显示器:WisdomHUD,W ...

  8. iOS Swift WisdomKeyboardKing 键盘智能管家SDK

    iOS Swift WisdomKeyboardKing 键盘智能管家SDK [1]前言:    今天给大家推荐个好用的开源框架:WisdomKeyboardKing,方面iOS日常开发,优点和功能请 ...

  9. iOS swift项目IM实现,从长连接到数据流解析分析之Socket

    iOS  swift项目IM实现,从长连接到底层数据解析分析之Socket 一:项目简介:  去年开始接手了一个国企移动项目,项目的需求是实现IM即时通讯功能. * 一期版本功能包括了:       ...

  10. [IOS]swift自定义uicollectionviewcell

    刚刚接触swift以及ios,不是很理解有的逻辑,导致某些问题.这里分享一下swift自定义uicollectionviewcell 首先我的viewcontroller不是直接继承uicollect ...

随机推荐

  1. window.showModalDialog基础

    本文转载:http://www.cnblogs.com/sunnycoder/archive/2010/05/05/1728047.html 基本知识 l  showModalDialog() (IE ...

  2. Java中的Annotation(2)----Annotation工作原理

    Java中的Annotation(2)----Annotation工作原理 分类: 编程语言2013-03-18 01:06 3280人阅读 评论(6) 收藏 举报 上一篇文章已经介绍了如何使用JDK ...

  3. MyBatis之一:入门

    一.什么是Mybatis 可以简单将mybatis理解为ibatis的升级版本,它是一个java的持久层框架,底层依赖jdbc接口,此持久层框架包含sql maps与data access objec ...

  4. poj 3613 Cow Relays

    Cow Relays Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 5411   Accepted: 2153 Descri ...

  5. cookie转CookieCollection

    CookieCollection cookiesResponse = new CookieCollection(); if (response != null) { foreach (string c ...

  6. oracle internal :VIEW: X$KCBLDRHIST - Direct Read HISTory

    WebIV:View NOTE:159900.1     Note (Sure) - Note    Mods - Note Refs Error ORA 600 TAR TAR-Info Bug B ...

  7. StarlingMVC Framework中文教程

    配置与开始 将Starling项目配置为StarlingMVC项目,仅需几行代码.在继承于starling.display.Sprite的起始类里,创建一个StarlingMVC的实例,并传递给它三个 ...

  8. C++中new与delete问题学习

    一.new char与delete问题 . 问题程序 [cpp] view plaincopy #include <iostream> using namespace std; void ...

  9. debian之source.list详解

    之前安装的是debian sarge(内核是2.4.7),不太想更新,但是发现原来的源/ect/apt/source.lists如下,但是用apt-get update,发现大都已经不可用了.怎么办, ...

  10. Android基本控件之ListView(二)<ListView优化>

    之前我们说到ListView的基本用法.但是,有很多的时候会额外的占用一些内存,从而消耗了性能.既然有消耗性能的可能,那么我们就对其做出相应的优化 我们首先来说说优化的步骤: 第一步.将宽和高设置为填 ...