ios Swift ! and ?
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 ?的更多相关文章
- iOS swift的xcworkspace多项目管理(架构思想)
iOS swift的xcworkspace多项目管理(架构思想) 技术说明: 今天在这里分享 swift下的 xcworkspace多项目管理(架构思想),能为我们在开发中带来哪些便捷?能为我们对整 ...
- iOS Swift 模块练习/swift基础学习
SWIFT项目练习 SWIFT项目练习2 iOS Swift基础知识代码 推荐:Swift学习使用知识代码软件 0.swift中的宏定义(使用方法代替宏) 一.视图 +控件 1.UIImag ...
- ios swift 实现饼状图进度条,swift环形进度条
ios swift 实现饼状图进度条 // // ProgressControl.swift // L02MyProgressControl // // Created by plter on 7/2 ...
- Building gRPC Client iOS Swift Note Taking App
gRPC is an universal remote procedure call framework developed by Google that has been gaining inter ...
- iOS Swift WisdomScanKit图片浏览器功能SDK
iOS Swift WisdomScanKit图片浏览器功能SDK使用 一:简介 WisdomScanKit 由 Swift4.2版编写,完全兼容OC项目调用. WisdomScanKit的 ...
- iOS Swift WisdomScanKit二维码扫码SDK,自定义全屏拍照SDK,系统相册图片浏览,编辑SDK
iOS Swift WisdomScanKit 是一款强大的集二维码扫码,自定义全屏拍照,系统相册图片编辑多选和系统相册图片浏览功能于一身的 Framework SDK [1]前言: 今天给大家 ...
- iOS Swift WisdomHUD 提示界面框架
iOS Swift WisdomHUD 提示界面框架 Framework Use profile(应用简介) 一:WisdomHUD简介 今天给大家介绍一款iOS的界面显示器:WisdomHUD,W ...
- iOS Swift WisdomKeyboardKing 键盘智能管家SDK
iOS Swift WisdomKeyboardKing 键盘智能管家SDK [1]前言: 今天给大家推荐个好用的开源框架:WisdomKeyboardKing,方面iOS日常开发,优点和功能请 ...
- iOS swift项目IM实现,从长连接到数据流解析分析之Socket
iOS swift项目IM实现,从长连接到底层数据解析分析之Socket 一:项目简介: 去年开始接手了一个国企移动项目,项目的需求是实现IM即时通讯功能. * 一期版本功能包括了: ...
- [IOS]swift自定义uicollectionviewcell
刚刚接触swift以及ios,不是很理解有的逻辑,导致某些问题.这里分享一下swift自定义uicollectionviewcell 首先我的viewcontroller不是直接继承uicollect ...
随机推荐
- window.showModalDialog基础
本文转载:http://www.cnblogs.com/sunnycoder/archive/2010/05/05/1728047.html 基本知识 l showModalDialog() (IE ...
- Java中的Annotation(2)----Annotation工作原理
Java中的Annotation(2)----Annotation工作原理 分类: 编程语言2013-03-18 01:06 3280人阅读 评论(6) 收藏 举报 上一篇文章已经介绍了如何使用JDK ...
- MyBatis之一:入门
一.什么是Mybatis 可以简单将mybatis理解为ibatis的升级版本,它是一个java的持久层框架,底层依赖jdbc接口,此持久层框架包含sql maps与data access objec ...
- poj 3613 Cow Relays
Cow Relays Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 5411 Accepted: 2153 Descri ...
- cookie转CookieCollection
CookieCollection cookiesResponse = new CookieCollection(); if (response != null) { foreach (string c ...
- 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 ...
- StarlingMVC Framework中文教程
配置与开始 将Starling项目配置为StarlingMVC项目,仅需几行代码.在继承于starling.display.Sprite的起始类里,创建一个StarlingMVC的实例,并传递给它三个 ...
- C++中new与delete问题学习
一.new char与delete问题 . 问题程序 [cpp] view plaincopy #include <iostream> using namespace std; void ...
- debian之source.list详解
之前安装的是debian sarge(内核是2.4.7),不太想更新,但是发现原来的源/ect/apt/source.lists如下,但是用apt-get update,发现大都已经不可用了.怎么办, ...
- Android基本控件之ListView(二)<ListView优化>
之前我们说到ListView的基本用法.但是,有很多的时候会额外的占用一些内存,从而消耗了性能.既然有消耗性能的可能,那么我们就对其做出相应的优化 我们首先来说说优化的步骤: 第一步.将宽和高设置为填 ...