Swift入门笔记-2

霉霉斯威夫特入门笔记,继续流水账。

3. struct与class

基础

Swift抄C#设计里的structclass可谓貌合神离,struct是值类型,class是引用类型。

所以class可以用===!==来比较Reference Equal。

Swift里的数组、字符串、字典是值类型。

定义

太老套了,没什么值得说的……

构造函数

1
2
3
4
5
class Person {
init() {
// ...
}
}

构造函数固定叫init,可以被重载。

定义property

像是JS和C#的结合体,并不像C#那样区分field和property,但是在对property定义getter/setter的时候语法很像。

1
2
3
4
5
6
7
class Person {
var firstName: String = ""
var lastName: String = ""
var fullName: String {
return firstName + " " + lastName
}
}

上面的例子里fullName因为只有getter所以可以使用隐藏get写法,完整写法:

1
2
3
4
5
6
7
8
var someProperty: String {
get {
// blablabla
}
set(newValue) {
// blablabla
}
}

如果把setter的参数省略,在函数体里面可以隐式使用newValue这个变量。

对于一般的属性(Swift里管叫存储属性)可以定义Observer来监听它的变动:

1
2
3
4
5
6
7
8
9
10
class Person {
var firstName: String = "" {
willSet(newValue) {
print("willSet", firstName, "to", newValue)
}
didSet(oldValue) {
print("didSet", oldValue, "to", firstName);
}
}
}

类似地,这里的newValueoldValue两个参数也是可以隐式使用的。

属性可以定义成lazy的,在var前面加个lazy就行,这样只有在这个属性第一次被访问的时候才会进行初始化。

属性可以定义成只读的,用let定义的属性在构造函数里有唯一的一次赋值机会。

静态属性的定义及使用方法和C#没有太大区别。

定义method

和定义一般的方法没有明显区别,可以隐式使用self访问上下文对象。

struct定义方法时,默认只支持纯函数,如果修改了自身属性,需要在func前面声明一下mutating,因为是值类型,甚至可以对self重新赋值。

定义索引器

索引器,在C++里好像叫[]运算符,在Swift里叫“下标脚本”

1
2
3
4
5
6
7
8
9
10
11
class Person {
subscript(i: Int) -> String {
get {
// 返回与入参匹配的Int类型的值
}

set(newValue) {
// 执行赋值操作
}
}
}

和定义property的时候类似,当只有getter的时候也可以用简便写法。

继承

1
2
3
4
5
class Car: Vehicle {
override func run() {
// ...
}
}

getter/setter也可以重写,属性observer也可以重写,用final标记的方法和属性不能重写。用final标记的class不能被继承。

实例化

构造

如上面所说,构造函数叫init,与普通函数的区别是,构造函数的第一个参数在调用时不能省略参数名(也还是可以用_来discard)。

1
2
3
4
5
6
7
8
class Car {
let brand: String
init(brand: String) {
self.brand = brand
}
}

var c1 = Car(brand: "BMW") // 这里brand: 不能省

常量字段在构造函数里有唯一一次赋值机会(父类的常量字段在子类构造函数里不能改)。

构造函数分两种,一种叫“指定构造器”就是上面那种看起来很普通的,一种叫“便利构造器”,在构造函数声明前面加个convenience

1
2
3
convenience init() {
self.init(brand: "unknown")
}

convenience构造函数里面必须调用它的其他构造函数。

Swift的实例构造过程也是常见的所谓“两段式构造”:

  1. 申请内存、给属性赋空值;调用子类构造器,完成子类属性赋值;调用父类构造器,完成父类属性赋值。
  2. 构造完成,可在子类构造器中执行任何其他操作。

换句话说,在调用super.init之前,是不能访问父类属性的,在之后就可以了。

Swift里还有一种特殊的“可失败构造器”,就是在init后面加个?,在构造失败的时候,可以return nil

析构

析构函数叫deinit(还敢再难听一点吗),是一个无参数无返回值函数,没有别的重载。

对象的析构时机当然就是生命周期结束的时候啦,这个比较复杂,等学到ARC的时候再细看吧。

4. ARC

ARC就是Automatic Reference Counting,自动引用计数。

Swift里没有传统意义上说的GC,对于引用计数为0的对象将会销毁(析构)。

1
2
3
4
5
func nonsense() {
let car = Car()
print(car.brand)
}
nonsense()

函数nonsense执行完以后,car就被销毁了,它的析构函数也被调用。

循环引用

这种情况太常见了,比如对象a持有一个对象b的引用,b也持有一个a的引用,也就是a.b = bb.a = a,就构成了一个循环引用。这种时候,a = nil; b = nil;并不会让ab被析构,因为虽然外部引用没了,但内部还存在引用,于是内存泄露,gg。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {
var b: B?
deinit {
print("A is dead")
}
}
class B {
var a: A?
deinit {
print("B is dead")
}
}

var a: A? = A()
var b: B? = B()
a!.b = b
b!.a = a

a = nil
b = nil

会发现ab的析构函数都没被调用。

弱引用

如果一个对象只被弱引用,那么它就可以被回收掉。在Swift里面用weak来把一个引用声明为弱引用,如

1
2
3
4
5
6
class B {
weak var a: A?
deinit {
print("B is dead")
}
}

还是刚才的代码,这时候神奇的时期发生了,ab被销毁了。为什么呢?因为ba的引用改成了弱引用,在ab都被赋值为nil后,a就只被弱引用(被b)了,于是它可以被回收,所以先看到A is dead;进而ab的强引用也就不存在了(a都挂了),于是可以回收B is dead,计划通。

无主引用

弱引用的例子是非常容易,就是弱引用对象可能会为nil。例如游客Customer与票Ticket,游客可以不持有票,但是票必须有主人。表现为Customerticket数学是一个Ticket?,而Ticketholder属性是Customer(不能为nil)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Customer {
var ticket: Ticket?
deinit {
print("Customer is dead")
}
}
class Ticket {
var holder: Customer
init(holder: Customer) {
self.holder = holder
}
deinit {
print("Ticket is dead")
}
}

var jim: Customer? = Customer()
jim!.ticket = Ticket(holder: jim!)

jim = nil

很明显上面的代码有循环强引用,jim不会被销毁,它所持有的ticket也不会被销毁。

然而票对游客的引用其只是一个“从属”关系,并不是真正的“持有”,所以这里应该把Ticketholder属性定义为无主引用

1
unowned var holder: Customer

这样就上面的代码将jim置为空时,jim仅仅被jim.ticket持有无主引用,于是它可以被销毁,先能看到Customer is dead;接下来,因为jim不存在了,jimjim.ticket所持有的强引用也随之被打破,jim.ticket也被销毁,于是看到Ticket is dead,计划通。

我理解其实无主引用是一个语法糖,因为其实只用weak ptr也够了(C++11就只有weak_ptr<T>而没有unowned_ptr<T>,但是这样在语义上必须允许一个引用为nil,也许Swift希望在概念上保留不能为空的机会,于是引入了这个东西。而如果没有它的话就只能靠程序员自己去理解了……

两边都不能为空时的循环引用

例如City必须属于一个国家,Country也必须有一个capital,两者缺一不可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class City {
let name: String
unowned let country: Country // 因为City“隶属于”Country,这里声明成无主引用
init(name: String, country: Country) {
self.name = name
self.country = country
}
deinit {
print("\(self.name) is dead")
}
}
class Country {
let name: String
var capital: City
init(name: String, capitalName: String) {
self.name = name
self.capital = City(name: capitalName, country: self)
}
deinit {
print("\(self.name) is dead")
}
}

var usa: Country? = Country(name: "USA", capitalName: "Washington")
usa = nil

看起来很美好啊,但其实是不行的,因为在self.capital = City(name: capitalName, country: self)这句话当中,self.capital还没有值,这样self就还没构造完成(所谓两段式构造),于是country: self这个传参就不能用,造成了先有鸡还是先有蛋的问题。

Swift为了解决这个问题提供了一个叫做“隐式解析可选类型”的特性,把

1
var capital: City

的定义改成

1
var capital: City!

这样在Country的构造阶段它的值会被自动赋值为nil(于是妈了个臀的我就想问问这和City?有啥区别?)。

然后就可以先后看到USA is deadWashington is dead了。我不会告诉你一开始我写的demo是ChinaBeijing,然后程序一跑通我给吓尿了,赶紧改?

to be continued

今天先到这儿吧…………下一篇什么内容再说了。