今年最遗憾的事恐怕就是不能亲身参加WWDC2014,虽然没有带来新设备,不过苹果依然給我们带来了重大的喜悦。 在objc相当成熟的今天,苹果毅然推出新的编程语言——Swift,无疑这是最大的亮点。
这几天一直在关注和探索Swift,对于一门全新的语言,开荒阶段的探索是十分激动人心的,同时由于资料缺失 和细节的隐藏不禁让人十分苦恼。
个人认为objc有更好的学习曲线,或者这样说,objc除了语法比较特殊外其概念还是比较容易的。而Swift语法 相当漂亮,但其背后却隐藏很多的细节和实现,如果无法理解这些细节和实现,在实际编码中你将会深深地嵌入 各种各样的编译不通过、运行出错等问题。本文主要总结了这几天学习Swift自己觉得比较重要的概念。当然自 己也只是接触了很短的时间,谈不上深入了解,不过希望这些总结对大家有用。
类型
类型是Swift中非常重要的概念。这个概念和其他编程语言中“类”的概念十分相似。Swift中类型分为命名类型
和复合类型;命名类型就是类(class)
、结构体(struct)
、枚举(enum)
以及接口(protocol)
,
复合类型包括函数(func)
、多元组(tuple)
。
在Swift世界中,我们看见的东西都一定属于某个类型。在PlayGround或项目中,通过在某个实际被命名的类型上
cmd+点击
就可以看见它的定义。
Array和Dictionary你真的了解?
感谢你坚持看到这里,其实我也开始佩服自己的吹水能力。下面让我们马上开始看一下Swift的细节部分。
我们首先讲的是参照和值。对于熟悉C系语言的童鞋都会知道调用一个函数的时候,往里传的参数有两种可能。 一种是传递类似一个数字或者结构体这样的基本元素,这时候这个整数的值会被在内存中复制一份然后传到函数内部; 另一种是传递一个对象,为了性能和内存上的考虑,这时候一般不会去将对象的内容复制一遍,而是会传递的一个指向同一块内存的指针。
Swift中的Collection Types,也就是Array
和Dictionary
和其他语言不太一样,它们并不是class
类型,
而是struct
结构体。按照我们的以往的经验在传值或者赋值的时候应该是会复制一份。那么我们看看Swift是怎样的。
Dictionary
的值没有问题,我们改变了dir
中的值,newDir
值保持不变,这证明了newDir
确实是被复制的。
但神奇的事情就在下面,当我们改变arr
时,newArr
的值也发生变化,也就是说明arr
和newArr
其实同一个参照。
在Apple的
官方文档其实也有提到。Swift考虑到实际使用的情景,对Array
做了特殊的处理,除非需要对Array
的大小作改变或者显式地进行复制,
否则Array
传递的时候会使用参照。
回归到我们的例子,如果我们要改变arr的值,而保持newArr
不变,我们可以显式地对arr
进行复制。
这样arr和newArr就指向不同的内存地址,那它们的任何改变也不会互相影响。
除了上面的copy()
方法外,还可以通过Array的初始化方法建立一个新的Array。
对于Array这个神奇的能力,Apple还提供了一个函数unshare()
給我们。unshare()
的作用是如果对象
数组不是唯一参照,则复制一份,并且将作参照的只想到新地址。
还有一点值得注意的是Array
的copy是不是深拷贝,所以Array
中的参照类型在拷贝之后仍然会是参照。
Slice
Array
实现了两个很重要的接口MutableCollection
和Sliceable
。第一个接口为Array
实现下标特性,
第二个接口Sliceable
实现了通过Range
来取出部分数组,这里有些特殊的地方值得注意。
Swift引入了在其他很多语言中很流行的用..
和...
来表示Range
的概念。首先为大家明确一下概念先,
..
代表的是半开半闭区间也就是a..b
就是[a,b),...
代表一個完全封闭区间也就是a...b
就是[a,b]。
你会得到一个编译错误,应为你没有重载下标。如果删除:Array
类型设置后,编译就能通过。
这就告诉我们,我们使用Rang
从Array
中取出来的东西,并不是Array
类型,这就是一个Slice
。
运行完上面代码,你是否明白一些什么呢?这里的slice
和newArr
当然不是同一个引用,但有趣的是通过
Range拿到Slice
的元素是指向原来的Array
的。
当然,在对应着Array
或Slice
任意一个内存指向发生变化时,这种关系就会被打破。
对于Slice
和Array
,其实有简单转换方法。因为collection
接口实现了+
重载,于是我们可以简单
地通过相加生成一个新的Array
,不过使用Array
初始化的方法会比较好。
c
var arr:Array = [0,0,0]
var slice = arr[0...1]
var result1:Array = [] + slice
var result2:Array = Array(slice)
使用Range下标的方式不仅可以获取Slice,还可以对原来数组进行批量赋值
,其实就是更替。
神奇的Optional
Swift引入最大的不一样应该要算Optional Value。在声明时,我们可以在类型后面加?
将变量声明为Optional。
如果不是Optional变量,那么它必须得有值。而是Optioanl变量,如果没有值,Optional会将它设为nil
。
Optional Vale其实就是一个盒子,盒子里可能装着实际的值,也可能什么也没有装。
细心的你,也许已经发现上面num赋值为9输出有些奇怪。其实?
只是简捷的写法,其实有正规的写法的。
这里你可以清晰得看见num
不是Int
类型而是Optional
类型。那么Optional是什么类型呢?
没错Optional
是泛型枚举enum
。实际上当我们使用这个枚举时,如果没有值,我们就规定这个枚举时
.None
,如果有就是some(value)
,而这个枚举刚好有实现了LogicValue
接口,这就是我们能使用
if
来对Optional的值进行判断,并进一步进行unwrap的依据。
既然var num:Int? = nil
其实给num
赋了一个枚举,那么nil又是什么东西?
值得注意的时objc的nil
和Swift的nil
完全不是一回事。objc的nil是实实在在存在的,指向一个空对象。
而Swift的nil代表空,它只是语义上概念,实际上是有实际类型的。
nil其实就是一个NilType变量,它有一个getter。Swift给了我们一个文档注释,告诉我们nil
其实只是
一个null
的标记值。实际上我们在声明或者赋值一个Optional
的变量时,?
语法糖做的事情就是声明一
个Optional<T>
,然后查看等号右边是不是nil
这个标记值。如果不是,则使用init(_ some: T)
用等号右边的类型T
的值生成一个.Some
枚举并赋值给这个Optional
变量;如果是nil
,将其赋为None
枚举。
谜底解开了,Optional的神奇在于在于这个?
。
NilType
这个类型非常特殊,目标还没有找到关于它的更多资料。
Apple 推荐我们在 unwrap 的时候使用一种所谓的隐式方法,即下面这种方式来 unwrap:
这样隐式调用足够安全,性能也得到了优化。
Optional 的变量也可以是 Optinal。
这样是很完美的两层Optional,使用的时候也一层层解开就好。但是如果是 nil 的话....
在 LLDB 里输出的时候,得到了两个 nil。
如果说str
其实是Optional<String>.None
,输出是nil
的话还可以理解。
anotherStr
其实是Optional<Optional<String>>.Some(Optional<String>.None)
,第一层
是有效的非空Optional
,而如果放在 PlayGround 里,anotherStr
得到的输出又是正确的{nil}
。
这到底发生了什么事????
LLDB 会在输出的时候直接帮我们尽可能地做隐式的 unwrap,这也就导致了我们在 LLDB 中输出的值只剩了一个裸的 nil。
如果想要看到 Optional 本身的值,可以在 Xcode 的variable
观察窗口点右键,选中Show Raw values
,这样就能显示出 None 和 Some 了。
?和!
?和!用法是objc中没有的概念。
?:
放在Optional
后的标记,自动将等号后面的内容wrap成Optional。
放在某个Optional后面,表示对变量进行判断,并隐式unwarp。
相比起一般的先判断再调用,类似这样的判断的好处是一旦判断为nil
或者说是false
,语句便不再继续执行,而是直接返回一个 nil。
上面的写法等价于:
这种写法更存在价值的地方在于可以链式调用,也就是所谓的 Optional Chaining,这样可以避免一大堆的条件分支,而使代码变得易读简洁。比如:
注意最后buildingIdentifier
后面的问号是在()
之后的,这代表了这个Optional
的判断对象是buildingIdentifier()
的返回值。
默认的 potocol 类型是没有 optional 的方法的,因为基于这个前提,可以对类型安全进行确保。
但是 Cocoa 框架中的 protocol 还是有很多 optional 的方法,对于这些可选的接口方法,
或者你想要声明一个带有可选方法的接口时,必须要在声明protocol
时再其前面加上@objc
关键字,并在可选方法前面加上@optional
。
!:
! 放在 Optional 变量的后面,表示强制的 unwrap 转换:
如果这个 Optional 的量是 nil 的话,这种转换会在运行时让程序崩溃。所以在直接写 ! 转换的时候一定
要非常注意,只有在有必死决心和十足把握时才做!
强转。如果待转换量有可能是 nil 的话,我们最好使用
if let
的语法来做一个判断和隐式转换,保证安全。
!
放在类型后面,表示强制的隐式转换。
这种情况下和 ? 放在类型后面的行为比较类似。声明的是Optional
,而!
其实声明的是一个ImplicitlyUnwrappedOptional
类型。
从外界来看,其实这和Optional
的变量是类似的,有Some
有None
。其实从本质上来说,ImplicitlyUnwrappedOptional
就是一个存储了Optional
,实现了Optional
对外的方法特性的一个类型,唯一不同的是,Optional
需要我们手动进行进行unwrap,
而ImplicitlyUnwrappedOptional
则会在使用的时候自动地去unwrap,并对继续之后的操作调用,而不必去增加一次手动的显示/隐式操作。