今年最遗憾的事恐怕就是不能亲身参加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
进行复制。
var arr = [0, 0, 0]
var newArr = arr.copy()
arr[0] = 1
arr //[1,0,0]
newArr //[0,0,0]
这样arr和newArr就指向不同的内存地址,那它们的任何改变也不会互相影响。
除了上面的copy()
方法外,还可以通过Array的初始化方法建立一个新的Array。
var arr = [0, 0, 0]
var newArr = Array(arr)
arr[0] = 5
arr //[5,0,0]
newArr //[0,0,0]
对于Array这个神奇的能力,Apple还提供了一个函数unshare()
給我们。unshare()
的作用是如果对象
数组不是唯一参照,则复制一份,并且将作参照的只想到新地址。
var arr = [0, 0, 0]
var newArr = arr
arr.unshare()
arr[0] = 2
arr //[2,0,0]
newArr //[0,0,0]
还有一点值得注意的是Array
的copy是不是深拷贝,所以Array
中的参照类型在拷贝之后仍然会是参照。
Slice
Array
实现了两个很重要的接口MutableCollection
和Sliceable
。第一个接口为Array
实现下标特性,
第二个接口Sliceable
实现了通过Range
来取出部分数组,这里有些特殊的地方值得注意。
Swift引入了在其他很多语言中很流行的用..
和...
来表示Range
的概念。首先为大家明确一下概念先,
..
代表的是半开半闭区间也就是a..b
就是[a,b),...
代表一個完全封闭区间也就是a...b
就是[a,b]。
var arr = [0, 0, 0]
var partOfArr:Array = arr[0...1]
//Could not find an overload for 'subscript' that accepts the supplied arguments
你会得到一个编译错误,应为你没有重载下标。如果删除:Array
类型设置后,编译就能通过。
这就告诉我们,我们使用Rang
从Array
中取出来的东西,并不是Array
类型,这就是一个Slice
。
var newArr:Array = [0,0,0]
var slice = newArr[0...1]
newArr[0] = 1
newArr //[1,0,0]
slice //[1,0,0]
slice[1] = 2
newArr //[1,2,0]
slice //[1,2,0]
运行完上面代码,你是否明白一些什么呢?这里的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
。
var num:int? //num不是Int
num = nil
num = 9 //{some 9}
Optional Vale其实就是一个盒子,盒子里可能装着实际的值,也可能什么也没有装。
细心的你,也许已经发现上面num赋值为9输出有些奇怪。其实?
只是简捷的写法,其实有正规的写法的。
var num:Optional<Int>
num = Optional<Int>()
num = Optional<Int>(9)
这里你可以清晰得看见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
}
没错Optional
是泛型枚举enum
。实际上当我们使用这个枚举时,如果没有值,我们就规定这个枚举时
.None
,如果有就是some(value)
,而这个枚举刚好有实现了LogicValue
接口,这就是我们能使用
if
来对Optional的值进行判断,并进一步进行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又是什么东西?
值得注意的时objc的nil
和Swift的nil
完全不是一回事。objc的nil是实实在在存在的,指向一个空对象。
而Swift的nil代表空,它只是语义上概念,实际上是有实际类型的。
var nil:NilType {get}
nil其实就是一个NilType变量,它有一个getter。Swift给了我们一个文档注释,告诉我们nil
其实只是
一个null
的标记值。实际上我们在声明或者赋值一个Optional
的变量时,?
语法糖做的事情就是声明一
个Optional<T>
,然后查看等号右边是不是nil
这个标记值。如果不是,则使用init(_ some: T)
用等号右边的类型T
的值生成一个.Some
枚举并赋值给这个Optional
变量;如果是nil
,将其赋为None
枚举。
谜底解开了,Optional的神奇在于在于这个?
。
NilType
这个类型非常特殊,目标还没有找到关于它的更多资料。
Apple 推荐我们在 unwrap 的时候使用一种所谓的隐式方法,即下面这种方式来 unwrap:
var num: Int? = 3
if let n = num {
//have a num
} else {
//no num
}
这样隐式调用足够安全,性能也得到了优化。
Optional 的变量也可以是 Optinal。
var str: String? = "Hi" //{Some "Hi"}
var anotherStr: String?? = str //
这样是很完美的两层Optional,使用的时候也一层层解开就好。但是如果是 nil 的话....
var str:String? = nil
var anotherStr:String?? = 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。
var str:String? = nil //声明一个String类型的Optional,并将其设为啥都没有
var another:String? = "Hello" //声明一个String类型的Optional,并将其设为字符串
放在某个Optional后面,表示对变量进行判断,并隐式unwarp。
foo?.method()
相比起一般的先判断再调用,类似这样的判断的好处是一旦判断为nil
或者说是false
,语句便不再继续执行,而是直接返回一个 nil。
上面的写法等价于:
if let myFood = foo {
MyFood.method()
}
这种写法更存在价值的地方在于可以链式调用,也就是所谓的 Optional Chaining,这样可以避免一大堆的条件分支,而使代码变得易读简洁。比如:
if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {
println("John's uppercase building identifier is \(upper).")
}
注意最后buildingIdentifier
后面的问号是在()
之后的,这代表了这个Optional
的判断对象是buildingIdentifier()
的返回值。
默认的 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!.metho()
如果这个 Optional 的量是 nil 的话,这种转换会在运行时让程序崩溃。所以在直接写 ! 转换的时候一定
要非常注意,只有在有必死决心和十足把握时才做!
强转。如果待转换量有可能是 nil 的话,我们最好使用
if let
的语法来做一个判断和隐式转换,保证安全。
!
放在类型后面,表示强制的隐式转换。
这种情况下和 ? 放在类型后面的行为比较类似。声明的是Optional
,而!
其实声明的是一个ImplicitlyUnwrappedOptional
类型。
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,
而ImplicitlyUnwrappedOptional
则会在使用的时候自动地去unwrap,并对继续之后的操作调用,而不必去增加一次手动的显示/隐式操作。