Kingyeung Chan

THINK GREAT THOUGHTS AND YOU WILL BE GREAT

大家好,我系Monster.Chan,一名来自中国的 iOS / AWS / Unity3D 开发者,就职于SAINSTORE。在不断修炼,努力提升自己


结合工作经验,目前写了《Getting Started with AWS》、《Websites & Web Apps on AWS》,欢迎试读或者购买

Thinking in Swift

今年最遗憾的事恐怕就是不能亲身参加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,也就是ArrayDictionary和其他语言不太一样,它们并不是class类型, 而是struct结构体。按照我们的以往的经验在传值或者赋值的时候应该是会复制一份。那么我们看看Swift是怎样的。

Dictionary的值没有问题,我们改变了dir中的值,newDir值保持不变,这证明了newDir确实是被复制的。 但神奇的事情就在下面,当我们改变arr时,newArr的值也发生变化,也就是说明arrnewArr其实同一个参照。 在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实现了两个很重要的接口MutableCollectionSliceable。第一个接口为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类型设置后,编译就能通过。 这就告诉我们,我们使用RangArray中取出来的东西,并不是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]

运行完上面代码,你是否明白一些什么呢?这里的slicenewArr当然不是同一个引用,但有趣的是通过 Range拿到Slice的元素是指向原来的Array的。

当然,在对应着ArraySlice任意一个内存指向发生变化时,这种关系就会被打破。

对于SliceArray,其实有简单转换方法。因为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的变量是类似的,有SomeNone。其实从本质上来说,ImplicitlyUnwrappedOptional 就是一个存储了Optional,实现了Optional对外的方法特性的一个类型,唯一不同的是,Optional需要我们手动进行进行unwrap, 而ImplicitlyUnwrappedOptional则会在使用的时候自动地去unwrap,并对继续之后的操作调用,而不必去增加一次手动的显示/隐式操作。

最近的文章

Run-Tracking:Part1

在WWDC 2014会议,Apple Inc向我们展示了即将到来的HealthKit API和相应的Health App. 与此同时,相关Health and Fitness应用分类在App Store上越来越受欢迎。为什么Health App会变得这么受欢迎呢?从以下的的信息也许可以找到原因:* 健康是极其重要的* 智能手机可以作为管理和跟踪健康的辅佐工具* 有趣的应用利用徽章制度可以调动人的积极性,但最重要还是让你持续健身运动 这个章节会告诉你怎么实现一个Run-Tracking。你...…

继续阅读
更早的文章

市场模型和Hurst指数

投资者总是希望能够弄清楚他们投资市场的特性。他们急需要一个能够有效描述市场规律的理论模型。于是上世纪中后期数学界大牛和金融界大牛进行了第一次研究,这就是首次把统计分析的方法应用于股票收益。这就是现代技术分析和量化金融分析的起源。股票收益率序列波动的数学期望值总为零,这为有效市场假说(Efficient Markets Hypothesis,以下简称EMH)奠定了理论基础。作为现代金融理论基石的EMH中,最重要的是确认了奥斯本提出了随机漫步理论。奥斯本认为股票价格的变化类似于化学中的分子布朗...…

继续阅读