注:本文为自己学习The Swift Programming Language的笔记,其中的例子为引用原书和其他博文或自己原创的。每个例子都会批注一些实践过程中的经验或思考总结。
1.基础
类和结构体是面对对象编程的核心,他们的概念并不陌生。
类和结构体可以相定义常量或变量和函数来定义属性和方法。与其他语言不同的事Swift不需要给出单独的接口和实现文件(Objective-C的复杂之处),Swift的类和结构体可以在单文件中定义,之后就可以直接使用,比较类似Python。
类的一个实例[instance]通常被称作一个对象[object],但由于Swift的类和结构体比起其他语言实用性更具有函数性,所以通称称之为一个实例。
2.类和结构体比较
Swift的类和结构体有许多相似之处,它们都可以:
(1)定义属性储存值
(2)定义具有功能性的方法
(3)定义下标并使用下标语法[点符号.]
(4)定义构造函数来给出初始值
(5)在原实现方法上扩展
(6)遵从协议来提供标准的功能
但类比结构体多出一下特性:
(1)类的继承
(2)类的实例可以实时类型转换
(3)类的析构函数可以释放实例分配的资源
(4)类使用引用计数,一个实例可以有多个引用(结构体是传值类型)
2.1类和结构体定义语法
类和结构体定义的方法类似,类型名最好使用UpperCamelCase规则,而参数和方法最好使用lowerCamelCase规则:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
值得一说的是类和结构都有默认的不带参数的构造函数,比如Resolution(),因而根据类型推断resolution属性是一个Resolution结构体。
而String?作为可空类型,name被自动赋予nil初始值。
//有一点需要注意,类和结构体的存储属性在定义时要么直接带有初值,要么在构造函数中会出现赋值方式,总之它必须有一个初值的源头。2.2类和结构体实例
我们用默认的无参数构造函数来创建类和结构的实例。
let someResolution = Resolution()
let someVideoMode = VideoMode()
Swift还有一种默认的构造函数,它的参数是每个存储变量的初始值,在调用构造函数时,要给出构造函数的外部参数名(默认是属性名):
let vga = Resolution(width : 640,height : 480)
注:原书专门在后面安排初始化一章介绍初始化[Initialization]。
2.3访问属性
访问属性使用点符号,支持多级点符号访问。与Objective-C不同的是Swift支持多级点符号来设置子属性[subproperties]:
someVideoMode.resolution.width = 1280
println("The width of someVideoMode is \(someVideoMode.resolution.width)")
在例子中width就是someVideoMode的子属性,因为它是属性resolution的子属性。
2.4传值类型和传址类型
结构体和枚举类型都是传值类型,而类是传址类型。
传值类型直译是“值类型”[Value Types],传值类型在赋值时是直接创建一个拷贝,拷贝和源值只是在赋值语句执行时内容完全一样,而在之后其他代码执行时两个量没有任何关系。即改变一个量的值不会影响另一个量。
传址类型直译是“引用类型”[Reference Type],这里这么翻译是遵循了C语言的这个很形象的叫法,也和传值类型鲜明对比。传址类型在复制时是把源值的指针赋值给新值,用指针的思想来看就是两个指针指向内存的同一个位置。既然是指向同一个位置,不仅赋值语句执行时两个量内容完全一样,而以后其他代码执行时两个量仍然保持一致。即改变一个量的值另一个量的值做相同改变。
注:(1)传值类型和传址类型(或者说值类型和引用类型)的例子在前面的学习过程中已经有很多,关于结构体和枚举是传值类型、类是传址类型的详细例子参见本系列的[Swift学习笔记(二)]
(2)我们可以用指针的思想来理解传址类型/引用类型,但是要知道的是引用类型并不是直接指向内存的某个地址,它不需要*符号来定义,事实上引用类型和其他变量常量的定义是一样的。
2.5一致性运算符
Swift提供2种特殊的运算符来检查两个变量或常量是否指代类的同一个实例:
(1)===[一致]在两个变量或常量指代类的同一个实例返回true否则返回false,区别于==:[等于]是指两个实例在值上相等或等价,首先不一定是类其次一定要有可等性。
(2)!==[不一致]相反,与!=区别类似。
在定义类和结构体时,相等[equal to]和不想等[not equal to]的定义是由程序员显式给出的,在Advanced Operator一章中会详细的将这一点。
3.类和结构体的选择
类和结构体有很多相似之处,但由于传值调用和传址调用的不同它们也适用于不同场合。
结构体更加适用于一下情况:
(1)封装一些简单的类型的值
(2)希望封装的值在赋值是拷贝而不是引用
(3)结构体的所有属性都是传值类型
(4)不需要继承
一些结构体应用的很好的备选例子有:
(1)封装几何形状,宽度、高度等Double型特性
(2)封装一个系列的范围,开始、结束等Int型特性
(3)点坐标
其他情况特别要是用到继承考虑选择类。
4.集合类型的赋值和拷贝
Swift的数组和字典类型是作为结构体实现的,但是数组在赋值给常量或变量时或者传递给函数或方法时,它的拷贝行为和字典以及其他结构体有一点不同。
区别:Objective-C中NSArray和NSDictionary是类实现的,都采用引用调用。
拷贝[Copying]一词在代码中都是看起来像是要创建一个拷贝,而实际上Swift在后台只对必需拷贝的值进行拷贝,Swift会做好最大限度地优化。
4.1字典的赋值和拷贝
赋值一个字典实例或者把字典实例传递给函数或方法时,字典在这个时刻时被拷贝。如果健、值的类型是传值类型,那么它们在赋值或调用时也是被拷贝的。但如果它们之中有引用类型的,这时只是引用进行拷贝,而不是它们指代的类的实例或函数。
除数组以外其他的结构体的拷贝和字典相同。
var ages = ["Peter": 23, "Wei": 35, "Anish": 65, "Katya": 19]
var copiedAges = ages
ages["Wei"] = 27
println(copiedAges["Wei"])
在修改了原来字典的值,拷贝字典并不改变值。
4.2数组的赋值和拷贝
数组的拷贝行为就要比字典稍微复杂一点了,它只在必要的时候拷贝。
当数组赋值和当作参数传入调用时,都是进行引用的拷贝,而不是实例的拷贝。
但是有一种情况,当一种行为有潜在的可能性修改数组长度时(包括附加[Append],插入[Insert],删除[Remove]或者用区间语法替换数组区间)数组进行值的拷贝。当这种拷贝发生时,数组的拷贝行为才和字典一样。
比如定义三个数组变量a,b,c,修改a[0]的值b和c的第一个元素同样改变,因为他们引用同一个实例:
var a = [1,2,3]
var b = a
var c = a
a[0] = 42
println("a[0] = \(a[0]), b[0] = \(b[0]), c[0] = \(c[0])")
//prints a[0] = 42, b[0] = 42, c[0] = 42
然而,在可能修改数组长度行为发生时,将新建拷贝:
a.append(77)
a[0] = 11
b[0] = 22
println("a[0] = \(a[0]), b[0] = \(b[0]), c[0] = \(c[0])")
//prints a[0] = 11, b[0] = 22, c[0] = 22
我们看到这里在a附加一个元素77后,为a进行了一次拷贝,而b、c仍然引用以前的实例。
4.3确保数组的独立性
当数组赋值或者传入时有时候有必要确保数组的独立性,数组变量可以调用unshare方法实现(常量不行,因为本来就不可变)。
如果同时几个数组变量引用一个实例,那么某一个变量在调用unshare方法时,Swift单独为它进行一次拷贝,其他不变。
b.unshare()
b[0] = 888
println("a[0] = \(a[0]), b[0] = \(b[0]), c[0] = \(c[0])")
//prints a[0] = 11, b[0] = 888, c[0] = 22
它的行为类似于4.2的第二个例子,这样一来三个原本引用同一实例的数组变量现在分别引用各自实例。
但是把调用过unshare方法的数组赋值给其他数组,这时仍然只是引用拷贝:
var e = b
e[0] = 456
println("a[0] = \(a[0]), b[0] = \(b[0]), c[0] = \(c[0]), e[0] = \(e[0])")
//prints a[0] = 11, b[0] = 456, c[0] = 22, e[0] = 456
b和e引用同一实例。
4.4检查数组引用的一致性
一致性算符可以用于检查数组是否引用同一个实例。
4.5强制拷贝数组
Swift提供一种在赋值时强制拷贝数组的方式,数组类型有一个copy方法,返回当前数组的一个拷贝,而不是引用:
var d = c.copy()
d[0] = 123
println("a[0] = \(a[0]), b[0] = \(b[0]), c[0] = \(c[0]), d[0] = \(d[0])")
//prints a[0] = 11, b[0] = 888, c[0] = 22, d[0] = 123
但是值得注意的一点是为了确保数组的独立性最好还是使用unshare方法,而非首选copy方法。因为unshare方法是在必要时才进行拷贝,而copy方法是一直拷贝的。