一直没有时间好好看一下swift,最近复习了一遍语法,这里记录swift学习过程中遇到的一些问题和要点,和Object-C的一些相关特性这里也不做介绍,只记录swift特有的一些特性
swift借鉴了很多语言的语法,特别是脚本语言,在swift里,可以看到python语言的一些影子,还有其他编程语言的影子
一、基础语法
- swift语句结束不需要分号(写了也没有问题),有一种情况需要分号,如果一行代码中有多条语句,这时候就必须要分号隔开
- swift字符串,数组语法糖,字典语法糖不需要
@
标示 - swift是类型安全的语言,所有的类型都不会自动转换(如:Int和UInt类型不能直接运算),同事swift具有强大的类型推测,所以很多时候我们不需要声明类型
- swift的多行注释支持嵌套
1
2
3/* 这是第一个多行注释的开头
/* 这是第二个被嵌套的多行注释 */
这是第一个多行注释的结尾 */ - swift的布尔值使用小写true和false,判断语句只能使用Bool类型
二、数据类型
- 与objc一样,swift支持以前(objc)使用的所有数据类型,swift的类型名字首字母大写,如Int, Float, NSInteger
- swift支持可选类型(Optionals)类型,相当于C#中的可空类型,标识变量可能为空,基础数据类型也可为空,可选类型不能直接赋非可选类型
1
2var a: Int? = 10
var b: Int = a // 报错,不同类型不能赋值 - swift的布尔类型使用
true/false
,而不用YES/NO
- swift支持使用
_
来分割数值来增强可读性而不影响值,如一亿可以表示为下面形式1
let oneMillion = 1_000_000
- swift数值类型进行运算符计算的时候不会自动进行类型转换,通常可以通过类型的构造方法进行类型转换
1
2
3
4var a: Int = 12
var b: Float = 23
var c = a + b // 报错
var d = Float(a) + b // 正确 - swift的基础数据类型与对象类型一视同仁,可以混用,不需要装箱和拆箱
TODO:Any, AnyObject,
三、常量变量
- 与
C/Obj-C
不同,swift的常量更为广义,支持__任意类型__,常量只能赋值一次 - swift的变量和常量在声明的时候类型就已经确定(由编译器自动识别或开发者指定)
- 使用let声明的集合为可变集合,使用var声明的集合为不可变集合
- 如果你的代码中有不需要改变的值,请使用 let 关键字将它声明为常量。只将需要改变的值声明为变量。这样可以尽量数据安全,并且常量是线程安全
1 | // 常量:使用let声明,赋值后就不能再修改 |
类型标注
在声明变量和常量的时候可以如果可以由编译器自动识别,可以不用制定类型,如下
1 | let a = 12 //常量a会编译为Int类型 |
我们也可以指定类型
1 | let a: Double = 12 |
可以在一行声明多个变量/常量,在最后一个声明类型
1 | var red, green, blue: UInt |
四、序列和集合
1. 数组Array
swift的数组可以是有类型的(泛型),存放同类型的数据,如果添加一个错误的类型会报编译错误,默认情况下编译器会自动识别
1 | //1. 数组的写法为:Array<Int>,也可以简写成[Int] |
2. 字典Dictionary
与数组类型一样,字典也支持泛型,其键值类型都可以指定或有编译器识别,其中Key的类型,必须是可Hash的,swift中基础数据类型都是可hash的(String、Int、Double和Bool)
1 | // 1. 用法与oc类似,初始化不需要@ |
数组(Array)或字典(Dictionary),如果声明为变量(var),则为可变,如果为常量(let),则为不可变
常量数组或字典编译器会对其进行优化,所以尽量把不可变的数组定义为常量数组
3. Set
Set集合用于存放无序不重复的对象,用法与数组类似,重复的项会被忽略
1 | var s: Set<Int> = [1, 3, 5, 6, 7, 4, 3, 7] // [1, 3, 4, 5, 6, 7] |
集合操作
1 | let oddDigits: Set = [1, 3, 5, 7, 9] |
- 使用“是否相等”运算符( == )来判断两个 合是否包含全部相同的值。
- 使用 isSubset(of:) 方法来判断一个 合中的值是否也被包含在另外一个 合中。
- 使用 isSuperset(of:) 方法来判断一个 合中包含另一个 合中所有的值。
- 使用 isStrictSubset(of:) 或者 isStrictSuperset(of:) 方法来判断一个 合是否是另外一个 合的子 合或 者父 合并且两个 合并不相等。
- 使用 isDisjoint(with:) 方法来判断两个 合是否不含有相同的值(是否没有交 )
4. 元组Tuple
与python类似,swift也支持元组,可以很方便的使用元组包装多个值,也使得函数返回多个值变得更加方便,特别是临时组建值得时候
- 支持任意类型
- 支持同时赋值
- 支持自定义key,支持索引
- 元组不是对象,不是
AnyObject
类型,由于swift是强类型的,所以元组有时不能当做普通的对象使用,例如不能把元组加到数组里面,元组内的所有类型必须是明确的
1 | // 1. 声明一个元组,元组支持任意类型 |
元组在临时组织值得时候很有用,可以不用重新定义数据结构
5. 字符串String
swift字符串是由Character字符组成的集合,支持+
操作符,可以与NSString无缝桥接,swift的字符串完全兼容unicode
字符串与值类型(与Int, Float)一样,是值类型,在传值的时候都会进行拷贝,当然这回带来一定的性能损耗,swift编译器在编译的时候会进行优化,保证只在必要的情况下才进行拷贝
1 | // 1. 与NSString不同,声明不需要@前缀,支持转移字符 |
6. 集合的赋值和拷贝行为
swift的集合通常有Array和Dictionary,他们在赋值或传递的时候,行为上有所不同,字典类型Dictionary或数组类型Array在赋值给变量或常量的时候,只要有做修改,就会进行值拷贝,并且不会作用到原来变量上
1 | var dict1 = ["a": 1, "b": 2] |
当数组或字典作为参数传递给函数的时候,由于在Swift3中不推荐使用变量参数,故所有函数参数不可变,故也不进行拷贝
五、可选类型(可空类型)
swift加入了可空类型让我们使用数据的时候更为安全,我们需要在可空的地方使用可选类型声明该变量可为空,不能给非可选类型设值nil
值,在使用的时候可以明确的知道对象是否可能为nil,有点像ObjC的对象,对象可以为nil,也可以不为nil,而swift得可选类型范围更广可以作用于任何类型(基础类型,类,结构体,枚举)
1. 声明
1 | // 1. 声明可选类型,在类型后面加上? |
2. 强制解析
可选类型不能直接使用,需要通过取值操作符!
取得变量的值,才能使用,如果变量有值,则返回该值,如果变量为空,则会运行时错误
1 | var b: Int? |
3. 可选绑定
使用可选绑定可以判断一个可选类型是否有值,如果有值,则绑定到变量上,如果没有值,返回false,使用if-let
组合实现
1 | var i: Int? = nil |
可选绑定还支持绑定条件
1 | var i: Int? = nil |
可选绑定还支持多个绑定,不许所有的绑定都满足才返回true
1 | if let firstNumber = 1, let secondNumber = 2) |
4. 隐式解析
声明类型的时候可以使用隐式解析,即在使用可选变量的时候自动取值,不需要调用!
操作符,
1 | // 一个函数返回一个可选类型 |
使用dog的时候都需要取值我们觉得太麻烦了,可以声明成隐式可选类型,使用的时候自动取值
1 | var dog: String! = getdog() // 实际上dog还是可选类型,只是使用的时候回自动取值 |
5. 可选类型自判断链接
在使用可选类型之前,需要进行判断其是否有值,才能使用,通过!
操作符取值后使用(保证有值的情况下),或通过if-let
可选绑定的方式,swift提供了一种类似C#语言的语法糖可以让代码更为简洁,可以自动判断值,如果有值,则操作,无值则不操作,并返回nil,在使用前加上?
1 | class Person { |
自判断链接还支持多连接如
1 | let identifier = john.residence?.address?.buildingIdentifier |
6. 可选关联运算符
可选关联运算符可对可选类型进行拆包,如果可选类型对象为nil,返回第二个操作数,第二个操作数类型必须和第一个操作数同类型(可选或不可选)
1 | let defaultColorName = "red" |
- defaultColorName和userDefinedColorName必须是同类型(String或String?)
- 如果userDefinedColorName不为空,返回其值,如果userDefinedColorName为空,返回defaultColorName
- 返回值colorNameToUse的类型同
??
的第二个操作数的类型,为String
六、运算符
swift运算符在原有的基础上做了一些改进,还添加了一下更高级的用法,还有新的运算符
=
运算符不返回值符合运算符
+=
,-=
等不返回值1
2//下面语句会报错
let b = a *= 2比较运算符可以用于元组的比较(逐个比较,如果遇到不等的元素,则返回,默认最多只能比较7个元素的元组,超过则需要自定义)
1
(1, "zebra") < (2, "apple") // true,因为 1 小于 2
字符串String,字符Character支持
+
运算符浮点数支持
%
求余运算1
8 % 2.5 // 等于 0.5
++/--
运算在swift3被抛弃,用+=/-=
代替支持溢出运算符(
&+
,&-
,&*
),可以在溢出时进行(高位)截断支持位运算符(
>>
,<<
)支持三目运算符(
a ? b : c
)支持逻辑运算符(
&&
,||
,!
)与其他高级语言类似,swift运算符支持重载,可以为类添加自定义的运算符逻辑,后面会讲到
!=
,==
,===
,!==
(恒等于/不恒等于)===
:这两个操作符用于引用类型,用于判断两个对象是否指向同一地址!===
:与===
相反,表示两个变量/常量指向的的地址不同==
:表示两个对象逻辑相等,可以通过重载运算符实现相等的逻辑,两个值相等的对象可以是不同地址的对象!=
:与==
相反,表示两个对象逻辑不等区间运算符
可以使用a...b
表示一个范围,有点类似于Python的range(a, b)
1
2
3for i in 1...5 {
print(i) // 1, 2, 3, 4, 5
}a...b
: 从a到b并包含a和b
a..<b
: 包含a不包含ba..b
表示半闭区间的用法已经被放弃范围运算符也可以作用于字符串
1
2let az = "a"..."z" // 返回的是CloseInteval或HalfOpenInterval
az.contains("e") // True空合运算符
??
(与C#类似)
对于可选类型取值,如果不为空则返回该值,如果为空则去第二个操作数1
let result = a ?? b
七、流程控制
swift使用三种语句控制流程:for-in
、for
、switch-case
、while
和repeat-while
,且判断条件的括号可以省略
1 | let names = ["Anna", "Alex", "Brian", "Jack"] |
流程控制语句的条件返回值必须是Bool,下面会报错
1 | var dd: Bool? = true |
条件判断可以与let
结合使用,当值为nil时,视为false(即:可选绑定
)
1 | var dd: Bool? = true |
在Swift2.0以后,不支持do-while
语句,使用repeat-while
代替,用法与do-while
一样
1 | repeat { |
guard-else
翻译为保镖模式,在执行操作前,进行检查,如果不符合,则拦截,使用方式与if有些类似,如果与let结合使用,可以对可选类型解包,先看看普通的if-else
模式
1 | func test(i: Int?) { |
上面的处理把条件放在了条件判断内部,使用guard与之相反,把正确的情况放在最外部,而异常情况放在条件判断内部
1 | func test(i: Int?) { |
保镖模式可以避免代码中过多的流程判断代码导致过多的代码块嵌套,增强可读性
保镖模式
guard-else
内的代码块必须包含break
,return
等跳出代码块的关键字
switch-case
- switch语句支持更多数据类型(String,Int, Float, 元组, 枚举),理论上switch支持任意类型的对象(需要实现
~=
方法或Equatable
协议,详情参见这里) - case可以带多个值,用逗号隔开
- case可以支持区间(
a...b
),支持元组,区间可以嵌套在元组内使用 - case多条语句不需要用大括号包起来
- case语句不需要break,除了空语句,如果需要执行下面的case,可以使用
fallthrough
- 如果case不能命中所有的情况,必须要
default
,如Int,String类型,否则编译会失败 - 可以用
fallthrough
关键字声明接着执行下一条case语句,注意,如果case语句有赋值语句(let
),则fallthrough
无效
1 | // 定义一个枚举 |
case除了和swift一起使用外,还支持与if语句结合使用,用法与switch一样
1 | let bb = (12, "bomo") |
带标签的语句
如果有多层嵌套的情况下,有时候我们需要在某处直接退出多层循环,在objc下并没有比较好的方式实现,需要添加退出标识,然后一层一层退出,而在swift可以很方便的退出多层循环,首先需要使用标签标识不通的循环体,形式如下
1 | labelName : while condition { statements } |
看下面例子
1 | outerLoop1 : for i in 1...10 { |
八、函数
1. 基本形式
1 | //有返回值 |
函数调用除了第一个参数,后面所有的参数必须带上参数名(符合Objc的函数命名规则)如果是调用构造器,第一个参数也需要显示声明
1 | class A { |
2. 可变参数
可变参数只能作为最后一个参数,一个方法最多只有一个可变参数
1 | func sum(numbers: Int...) -> Int { |
3. 外部参数名
默认情况下,如果不指定外部参数名,swift编译器会自动为函数参数声明与内部参数名同名的外部参数名(格式为:外部参数名 内部参数名: 类型名
)
1 | //默认情况下,外部参数名与内部参数名一样 |
如果函数在第一个参数定义外部参数名,必须显示指定,当然我们还可以通过下划线_
让函数忽略参数名
1 | func add(a: Int, _ b: Int) -> Int { |
4. 函数默认值
函数还支持声明默认值,(格式为:外部参数名 内部参数名: 类型名 = 默认值
)
1 | func log(msg: String, isDebug: Bool = true) { |
如果使用默认值并且默认值不是出现在最后,那调用的时候必须写全所有参数
建议把默认参数放到最后面,这样可以确保非默认参数的赋值顺序,减少参数混乱的情况
5. 闭包
- 函数作为变量
- 函数作为函数参数
- 函数作为函数返回值
- 闭包函数声明
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
26
27
28
29
30func add(a: Int, b: Int) -> Int {
return a + b
}
//函数作为变量,函数hello赋给somefunc,并调用
let somefunc: (Int, Int) -> Int = add
somefunc(10, 20) // 30
//函数作为参数
func logAdd(a:Int, b:Int, function: (Int, Int) -> Int) {
// 函数内容
print("begin")
function(a, b)
print("end")
}
logAdd(12, b: 23, function: add)
//函数作为返回值(包装一个函数,在执行前后输出信息),函数作为参数又作为返回值
func addWrapper(addFunc: (Int, Int) -> Int) -> ((Int, Int) -> Int) {
// 函数内容
func wrapper(a: Int, b: Int) -> Int {
print("begin")
let res = addFunc(a, b)
print("end")
return res
}
return wrapper
}
var newAdd = addWrapper(add)
newAdd(12, 32)
闭包函数声明形式
1 | { (parameters) -> returnType in |
闭包函数
1 | //定义一个函数变量 |
6. Trailing(尾行)闭包
如果函数作为另一个函数的参数,并且是最后一个参数时,可以通过Trainling闭包来增强函数的可读性
1 | func someFunctionThatTakesAClosure(a: Int, closure: () -> ()) { |
7. Escaping(逃逸)闭包
如果一个闭包/函数作为参数传给另外一个函数,但这个闭包在传入函数返回之后才会执行,就称该闭包在函数中”逃逸”,需要在函数参数添加@escaping
声明,来声明该闭包/函数允许从函数中”逃逸”,如下
1 | var completionHandlers: [() -> Void] = [] |
逃逸闭包只是一个声明,以增强函数的意图
8. 自动闭包
对于没有参数的闭包,swift提供了一种简写的方式,直接写函数体,不需要函数形式(返回值和参数列表),如下
1 | // 声明一个自动闭包(无参数,可以有返回值,返回值类型swift可以自动识别) |
自动闭包只是闭包的一种简写方式
如果一个函数接受一个不带参数的闭包
1 | func logIfTrue(predicate: () -> Bool) { |
调用的时候可以使用自动闭包
1 | logIfTrue(predicate: { return 1 < 2 }) |
上面代码看起来可读性不是很好,swift引入了一个关键字@autoclosure
,简化自动闭包的大括号,在闭包类型前面添加该关键字声明
1 | func logIfTrue(predicate: @autoclosure () -> Bool) { |
@autoclosure
关键字是为了简化闭包的写法,增强可读性,这里的例子比较简单,可以参考:@AUTOCLOSURE 和 ??
9. 常量参数和变量参数
默认情况下所有函数参数都是常量,意味着参数是不可变的,我们可以显式的声明参数为变量
1 | func log(msg: String) { |
注:变量参数在swift3被抛弃
10. 输入输出参数
在c语言里有指针,可以通过传址直接修改外部变量的值,在swift通过inout
关键字声明函数内部可直接修改外部变量,外部通过&
操作符取得变量地址
1 | func swap(inout a: Int, inout b: Int) { |
11. 嵌套函数
swift的函数还支持嵌套,默认情况下,嵌套函数对外部不可见,只能在函数内部使用
1 | func chooseStepFunction(backward: Bool) -> (Int) -> Int { |
嵌套函数相当于objc函数内的block
12. defer
在swift2.0之后添加了defer
关键字,可以定义代码块在函数执行完成之前的完成一些操作,并且在函数抛出错误的时候也可以执行
1 | func test() { |
上面输出结果为
1 | begin1 |
通常可以用在需要成对操作的逻辑中(如:open/close
)
九、枚举
swift的枚举比C语言的枚举更为强大,支持更多特性,swift的枚举更像类和结构体,支持类和结构体的一些特性,与ObjC
不同,如果不声明枚举的值,编译器不会给枚举设置默认值
枚举与结构体一样,是值类型
1. 声明和使用
1 | // 1. 定义枚举 |
2. 嵌套枚举
swift的枚举定义支持嵌套,在使用的时候一层一层引用
1 | enum Character { |
3. 递归枚举
枚举的关联值的类型可以设为枚举自身,这样的枚举称为递归枚举
1 | enum ArithmeticExpression { |
带递归类型的枚举需要在case前面添加关键字声明indirect
,也可以在enum前面加上声明,表示所有的成员是可以递归的
1 | indirect enum ArithmeticExpression { |
使用递归枚举取值的时候可以使用递归函数
1 | func evaluate(_ expression: ArithmeticExpression) -> Int { |
其实感觉这种嵌套多层的用法可读性并不是特别好,而且在取值的时候还需要递归,通常来说,嵌套一层就够了
4. 原始值
与C语言一样,可以为每个枚举指定值,并且可以支持更多类型(Int
, Float
, Character
, String
)
1 | // 定义枚举,并初始化原始值 |
5. 关联值
上面我们说到,枚举与类和结构体类似,swift的枚举可以给不同的枚举值绑定关联值,如下
1 | enum Barcode { |
如上面这种轻量的数据,在OC上一般我们可能需要定义两个类实现,而swift的枚举可以轻松的处理这种轻量数据,而减少项目中类的定义和维护
十、类与结构体
先来看看结构体和类的一些差异
- 类是引用类型,结构体为值类型
- 类使用引用计数管理内存,结构体分配在栈上,有系统管理内存,变量传递的时候,结构体整个拷贝,而类默认只传递引用地址(有些类会进行一些额外的拷贝,详见深拷贝和浅拷贝)
- 结构体不支持继承,类支持继承
- 与ObjC不同,swift的结构体可以定义方法
- 类支持运行时类型检查,而结构体不支持
- 类有构造器和析构器,结构体只有构造器
- 常量结构体的成员的值不能改变
实际上,在 Swift 中,所有的基本类型:整数(Integer)、浮 点数(floating-point)、布尔值(Boolean)、字符串(string)、数组(array)和字典(dictionary),都是 值类型,并且在底层都是以结构体的形式所实现。
1. 结构体,类定义
1 | struct Point { |
swift中,许多基本类型如String
, Array
和Dictionary
都是用结构体实现的,意味着在传递的时候都会进行值拷贝,当然swift也对这些类型进行了优化,只有在需要的时候进行拷贝
2. 静态属性,静态方法
swift中有两个static
和class
声明静态变量或方法,其中class
只能用在类的方法和计算属性上,其他的都使用static
,由于类支持继承,所以使用class
声明的静态方法可以被继承,而static声明的静态方法不能被继承
1 | class Person { |
类和结构体的声明和用法与类类似,使用static
注意:
class
只能用来声明计算属性和方法,不能用来声明普通属性
3. 构造器和析构器
swift的构造器规则和限制比较多,关于构造器可以参见:这里
析构器相当于objc里面的dealloc
方法,做一些需要手动释放资源的操作,析构器与构造器不同,没有参数,定义的时候不需要括号,类在释放的之前会自动调用父类的析构器,不需要主动调用
1 | class Person { |
4. 类型判断
在objc中,我们通常使用isKindOfClass
, isMemberOfClass
, isSubclassOfClass
等方法进行类型判断,swift使用is
和as
判断类型
1 | class Parent { |
//TODO: swift动态性,反射
5. 弱引用
与ObjC
一样,swift的内存管理也使用引用计数管理,也使用weak声明弱引用变量
1 | class Person { |
6. 访问级别
在swift中,framework和bundle都被处理成模块
* public:公开,可以被外部访问
* internal:内部,在模块(framework)内部使用,模块外访问不到
* private:只能在当前源文件中使用
swift默认的访问级别为Internal,使用的时候只需要在类/变量/函数前面加上访问级别即可
1 | public class Person { |
外层访问级别的必须是比成员更高,下面会报警告
1 | class Person { // 默认为internal |
函数的访问级别要比参数(或泛型类型)的访问级别低,否则会报警告
1 | private class PrivatePerson { |
枚举类型
的成员访问级别跟随枚举类型,嵌套类型默认最高访问级别为internal(外层为public,内层默认为internal)
1 | public enum CompassPoint { |
子类访问级别不能高于父类(包括泛型类型),协议继承也同理,子协议访问级别不能高于父协议
1 | class Parent { |
元组
的访问级别为元组内所有类型访问级别中最低级的
1 | class Parent { |
变量的访问级别不能高于类型
1 | private class PrivateClass { |
属性的 Setter 访问级别不能高于 Getter访问级别
1 | public class SomeClass { |
协议与类的访问级别关系
- 协议中所有必须实现的成员的访问级别和协议本身的访问级别相同
- 其子协议的访问级别不高于父协议(与类相同)
- 如果类实现了协议,那类的访问级别必须低于或等于协议的访问级别
类型别名访问级别与类型的关系
- 类型别名的访问级别不能高于原类型的访问级别;
函数构造函数默认访问级别为internal,如果需要给其他模块使用,需显式声明为public
注意:swift的访问级别是作用于文件(private)和模块的(internal)的,而不只是类,所以只要在同一个文件内,private访问级别在不同类也可以直接访问,例如我们可以通过子类包装父类的方法以改变访问级别
1 | public class A { |
7. 属性
- 使用关键字
lazy
声明一个懒加载 变量 属性,当属性被使用的时候(get),才会进行初始化 - set方法的访问级别必须必get方法低
- 声明属性的时候可以使用
private(set)
和internal(set)
改变set方法默认的访问级别 - 每个实例都有一个self属性,指向实例自身,通常在属性与函数参数有冲突的时候使用
- 对于常量属性,不许在定义它的类的构造器中赋值,不能再子类赋值
1 | class DataImporter { |
使用lazy声明的属性不是线程安全的,在多线程情况下可能产生多份,需要自己控制
对于结构体,与OC不同,swift的结构体允许直接对属性的子属性直接修改,而不需要取出重新赋值
1 | someVideoMode.resolution.width = 1280 |
在oc上需要这样做
1 | var resolution = someVideoMode.resolution |
8. 继承
我们都知道,在oc里所有的类都继承自NSObject/NSProxy,而在swift中的类并不是从一个通用的基类继承的,所有没有继承其他父类的类都称为基类
1 | class Parent { |
重写属性的时候,如果属性提供了setter方法,则必须为提供getter方法
如果重写了属性的setter方法,则不能重写willSet和didSet方法
如果重写了willSet和didSet方法,则不能重写get和set方法
父类的属性,方法,类方法,附属脚本,包括类本身都可以被子类继承和重写,可以通过final
约束限制子类的重写(final class
, final var
, final func
, final class func
, 以及 final subscript
)
1 | class Parent { |
swift编译器在识别数组类型的时候,如果数组元素有相同的基类,会被自动识别出来
1 | class Person { |
向下类型转换as!
, as?
,as!
返回非可选类型,如果类型不匹配会报错,as?
返回可选类型,如果类型不匹配返回nil
1 | for person in people { |
9. 附属脚本subscript
附属脚本可以让类、结构体、枚举对象快捷访问集合或序列,而不需要调用使用对象内的实例变量引用,看下面实例
1 | class DailyMeal { |
使用附属脚本可以直接通过类对象索引访问meals的值
1 | class DailyMeal { |
附加脚本还支持多个参数
1 | struct Matrix { |
附加脚本类似属性,拥有get/set方法,支持只读和读写两种方式,附加脚本也支持多个参数,附属脚本可以屏蔽外部对内部对象的直接访问,隐藏对象内部的细节,提高封装度,使得代码更加健壮和简洁
10. 类型嵌套
与枚举一样,结构体和类都支持类型嵌套,可以在类里面再定义类/结构体/枚举
1 | class SomeClass { |
11. 类型别名
swift类型别名与c语言中取别名有点像,通过关键字typealias
声明别名
1 | public typealias MyInt = Int |
通常在容易出现命名冲突的情况下会考虑使用类型别名
十一、扩展Extension
与oc一样,扩展就是对已有的类添加新的功能,与oc的category类似,swift的扩展可以:
- 提供新的构造器(需要符合构造器的基本规则)
- 添加实例计算型属性和类计算性属性
- 添加实例方法和类方法
- 添加附加脚本
- 添加新的嵌套类型
- 使一个已有类型符合某个接口
swift扩展不可以:
- 不可以添加存储属性
- 不可以向已有属性添加属性观测器(willSet, didSet)
1 | class Person { |
扩展也可以作用在结构体和枚举上
1 | struct Rectangle { |
扩展内的成员定义与类类似,这里不再说明
扩展属性
由于swift不能扩展新的属性,有时候我们希望给类添加属性,在oc里可以用关联属性新增存储属性,在swift也可以,需要引入ObjectiveC
模块
1 | import ObjectiveC |
十二、协议Protocal
swift的协议在oc的基础上加了更多的支持,可以支持属性,方法,附加脚本,操作符等,协议的属性必须为变量var
1 | protocol SomeProtocol { |
1. mutating
在结构体/枚举中的值类型变量,默认情况下不能对其进行修改,编译不通过,如果需要修改值类型的属性,需要在方法声明前加上mutating
1 | struct Point { |
可变方法还可以修改枚举值自身的值
1 | enum TriStateSwitch { |
特别是在定义Protocal的时候,需要考虑到协议可能作用于枚举或结构体,在定义协议的时候需要在方法前加上mutating
1 | protocol SomeProtocol { |
2. 协议类型
协议虽然没有任何实现,但可以当做类型来用,与oc的protocal类似,用协议类型表示实现了该协议的对象,与oc的id<SomeProtocol>
一样
3. 协议组合
有时候我们需要表示一个对象实现多个协议,可以使用协议组合来表示,如下
1 | protocol SwimProtocal { |
4. 自身类型
有时候我们需要表示实现协议的类型,可以使用Self
代替,如下
1 | protocol CompareProtocal { |
5. @objc协议
swift声明的协议是不能直接被oc的代码桥接调用的,如果需要,需要在声明前加上@objc
,使用@objc
声明的协议不能被用于结构体和枚举
1 | import Foundation |
6. Optional要求
在oc中的protocal可以定义可选方法,在swift默认不支持可选方法,swift只有在添加了@objc
声明的协议才能定义可选方法,在定义前添加optional
声明
1 | import Foundation |
十三、错误
与其他高级语言异常处理有点类似,swift引入了错误的机制,可以在出现异常的地方抛出错误,错误对象继承自Error,抛出的错误函数会立即返回,并将错误丢给调用函数的函数处理,如果一个函数可能抛出错误,那么必须在函数定义的时候进行声明,如下
1 | //定义错误类型 |
如果错误是一个对象,而不是枚举,可以用let绑定到变量上
1 | do { |
如果不处理错误的话可以使用try?
,使用try?关键字的方法会被包装到一个可选类型中,如果发生错误,则会返回nil,如下面序列化的例子
1 | func serialize(obj: AnyObject) -> String { |
try?配合guard let一起使用效果更好
十四、断言
断言可以让我们在调试时候更好的发现问题,排查错误,几乎所有的高级语言都支持断言,swift也如此,断言的代码在release的时候回被忽略,不会影响发布程序的性能,只会在调试的时候生效
1 | // 如果age小于0,程序会停止,并输出错误信息 |
十五、泛型
关于泛型的介绍,这里不进行说明,swift的泛型是我认为最酷的特性之一,当然其他语言也有,可以让类或函数更大程度的重用,swift的泛型与其他语言的泛型有点类似
1. 定义
在类或函数声明的时候,指定一个泛型类型参数(通常为T)然后使用的时候直接把T当成类型使用
1 | //泛型函数定义 |
2. 泛型约束
我们还可以对泛型进行约束,泛型类型参数只能是某些类型的子类,或实现了某些协议
1 | func findIndex<T>(array: [T], valueToFind: T) -> Int? { |
上面函数会报编译错误,因为在swift里,并不是所有的类都能用==
操作符比较,只有实现了Equatable协议的类才能用==
操作符,修改为
1 | func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? { |
3. 多泛型类型参数
有时候我们需要用多个协议进行约束,可以使用下面方式(类与函数的使用方式类似)
1 | func someFunc<T : protocol<StudyProtocal, RunProtocal>>(arg: T) { |
如果约束既有类又有协议的话可以使用where
添加限制条件
1 | func someFunc<T, TK where T:Student, T: StudyProtocal>(t: T, tk: TK) { |
4. 泛型是不可变的
1 | var dog1 = SomeClass<Parent>() |
关于可变,不可变,逆变,协变参考这里:http://swift.gg/2015/12/24/friday-qa-2015-11-20-covariance-and-contravariance/
5. 泛型协议
swift的协议不支持泛型,不能像类一样定义泛型,而是通过类型参数定义泛型
1 | protocol GenericProtocol { |
十六、运算符重载
与其他高级语言的一样,swift也提供了运算符重载的功能,我们可以自定义运算符的实现,运算符通常分为三种类型
- 单目运算符:
<运算符><操作数>
或<操作数><运算符>
,如!a
- 双目运算符:
<操作数><运算符><操作数>
,如:1 + 1
- 三元运算符:
<操作数><运算符><操作数><运算符><操作数>
,如:a ? b : c
swift的运算符重载
- 支持自定义运算符
/
,=
,-
,+
,*
,%
,<
,>
,!
,&
,|
,^
,.
,~
的任意组合。可以脑洞大开创造颜文字。 - 不能对默认的赋值运算符
=
进行重载。组合赋值运算符可以被重载,如==
,!==!
- 无法对三元运算符
a ? b : c
进行重载 - 运算符声明和定义只能定义在全局作用域,不能定义在类/结构体/枚举内
1. 前缀,中缀,后缀运算符
- 前缀
prefix
:默认的有-,!,~等 - 中缀
infix
:默认的有+,*,==等 - 后缀
postfix
:默认的有:++,–等
1.1 声明运算符
如果实现不存在的运算符需要添加运算符声明(系统的提供的,可以不需要声明),声明必须放在全局作用域
1 | // 前缀运算符 |
1.2 实现上面三个运算符
1 | // 定义Point结构体 |
1.3 使用
1 | var p1 = Point(x: 12, y: 21) |
2. 优先级
这个很好理解,就是优先级高的运算符先执行,声明运算符的时候可以指明优先级
1 | infix operator ^ { |
这里可以查看默认运算符的优先级
3. 结合性
运算符还可以定义结合性,对于双目运算符,当优先级一样的时候,可以定义运算符优先进行左结合还是右结合,运算符的结合性有下面三种
- left:左结合
- right:右结合
- none:无
结合性设置为left
1 | // 定义一个双目操作符 |
如果我们设置结合性为right
1 | // 定义一个双目操作符 |
如果结合性设置为none
,则会报错,无法判断
十七、命名空间
在很多语言里面,都有命名空间的概念,可以分离代码,防止命名冲突,而swift也有类似命名空间的概念,通过访问级别实现命名空间
//TODO
十八、参考链接
十九、总结
总的来说,swift还是比较装逼的,整个很多新名词,新概念,例如,指定构造器,便利构造器,构造器代理,但其实这些东西在别的语言基本上有,没那么复杂,另外swift的关键字太多了,有些可有可无,是不是苹果看到什么好的就想往swift里面塞还是怎么着,另外感觉苹果还是太装逼了,例如do-while非要偏偏要搞成repeat-while啥的,个人感觉编程语言应该是轻便,简单,当然,并且能满足所有需求的,反正,没什么特别的好感