第1节:Swift基础知识

[TOC]

入门

  • 一、基础部分
  • 二、基本运算符

OC与Swift混编

原本在 swift 3 中除了手动添加 @objc 声明函数支持 OC 调用还有另外一种方式:继承 NSObject。

即class 继承了 NSObject 后,编译器就会默认给这个类中的所有函数都标记为 @objc ,支持 OC 调用。

然而在实际项目中,一个 swift 类虽然继承了 NSObject,但是其中还是有很多函数不会在 OC 中被调用,这里有很大的优化空间。

于是根据 SE160 的建议,苹果修改了自动添加 @objc 的逻辑:现在一个继承 NSObject 的 swift 类不再默认给所有函数添加 @objc。只在实现 OC 接口和重写 OC 方法时才自动给函数添加 @objc 标识。

XCode 9会在运行过程中自行检测类中函数是被 OC 调用,然后提示添加 @objc。下图中的 vc 是 swift 中的类,showStatus 也是 swift 函数,现在编译器会提示需要手动添加 @objc:

Swift概念新增/区别

swift的guard语句的时候,我当时不太理解以后会有什么理由能用到它。这个语句的介绍如下:

与if语句相同的是,guard也是基于一个表达式的布尔值去判断一段代码是否该被执行。与if语句不同的是,guard只有在条件不满足的时候才会执行这段代码。你可以把guard近似的看做是Assert,但是你可以优雅的退出而非崩溃。

一、基础部分

Swift基础部门:http://www.swift51.com/swift4.0/chapter2/01_The_Basics.html

1、声明常量和变量

声明变量时在类型后添加?或者!,就是告诉编译器这是一个Optional的变量,如果没有初始化,你就将其初始化为nil

常量和变量必须在使用前声明,用 let 来声明常量,用 var 来声明变量。

1
2
3
4
5
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

// 可以在一行中声明多个常量或者多个变量,用逗号隔开:
var x = 0.0, y = 0.0, z = 0.0

与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错:

1
2
3
let languageName = "Swift"
languageName = "Swift++"
// 这会报编译时错误 - languageName 不可改变

附:类型标注

Swift可以根据赋值推断出类型。

1
2
let PI = 3.1415
var Str = "string"

你也可以使用类型标注(type annotation)来指定类型。在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。声明中的冒号代表着“是…类型”

1
2
3
4
var welcomeMessage: String = "string"

// 可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型标注:
var red, green, blue: Double

2、输出print

Swift使用字符串插值(string interpolation)的方式将常量或变量当作占位符加入到长字符串中,我们可以借此拼接长字符。

1
2
3
4
let value1 = "123"
var value2 = 345
print("value1 = \(value1) , value2 = \(value2)")
// 输出: value1 = 123 , value2 = 345

3、数据类型(布尔值、数组、字典、元组、可选类型)

3.1、布尔值

和OC不同,Swift中的布尔值使用truefalse表示真、假

1
2
3
4
let boolValue = true
if boolValue {
print("value is true")
}

这里需要说明的是,Swift不能像OC中那样,数值为0即表示布尔值NO,非0即为YES,因此下面OC代码可以通过

1
2
3
4
5
float value = 0.001;
if (value) {
NSLog(@"value:%f",value);
}
// 输出: value:0.001000

而在Swift中会编译报错

1
2
3
4
let boolValue = 0.001
if boolValue {
print("value is true")
}

3.2、可选类型

3.2.1、可选类型

使用可选类型(optionals)来处理值可能缺失的情况。

1
2
3
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"

因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个 Int。一个可选的 Int 被写作 Int? 而不是 Int。问号暗示包含的值是可选类型,也就是说可能包含 Int 值也可能不包含值。(不能包含其他任何值比如 Bool 值或者 String 值。只能是 Int 或者什么都没有。)

3.2.2、隐式解析可选类型

可选类型 String 和隐式解析可选类型 String 之间的区别:

1
2
3
4
5
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感叹号

你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。

nil

Swift 的 nil 和 Objective-C 中的 nil 并不一样。

Objective-C Swift
nil 在 Objective-C 中,nil 是一个指向不存在对象的指针。
只有对象类型能被设置为nil
在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。
任何类型的可选状态都可以被设置为 nil,不只是对象类型。

你可以给可选变量赋值为nil来表示它没有值:

1
2
3
4
var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值

强制解析

1
2
3
4
5
6
7
//let possibleNumber = "123"
//let convertedNumber = Int(possibleNumber)
var convertedNumber: Int? = 123
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).") //当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping):
}
// 输出 "convertedNumber has an integer value of 123."

可选绑定

if 语句中写一个可选绑定:

1
2
3
if let constantName = someOptional {
statements
}

举例:

1
2
3
4
5
6
if let actualNumber = Int(possibleNumber) {
print("\'\(possibleNumber)\' has an integer value of \(actualNumber)") //转换成功,因为它已经被可选类型 包含的 值初始化过,所以不需要再使用 ! 后缀来获取它的值。
} else {
print("\'\(possibleNumber)\' could not be converted to an integer")
}
// 输出 "'123' has an integer value of 123"

这段代码可以被理解为:“如果 Int(possibleNumber) 返回的可选 Int 包含一个值,创建一个叫做 actualNumber 的新常量并将可选包含的值赋给它。”

二、基本运算符

1、赋值运算符

与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以以下代码是错误的:

1
2
3
if x = y {
// 此句错误, 因为 x = y 并不返回任何值
}

这个特性使你无法把(==)错写成(=),由于 if x = y 是错误代码,Swift 能帮你避免此类错误发生。

如果赋值的右边是一个多元组,它的元素可以马上被分解成多个常量或变量:

1
2
let (x, y) = (1, 2)
// 现在 x 等于 1,y 等于 2

2、算术运算符

加法运算符也可用于 String 的拼接:

1
"hello, " + "world"  // 等于 "hello, world"

在新版Swift中,++ 和 – 运算符被取消,因此 i++ 这种形式的累加需要换成 i += 1 这种形式。

3、一元正号/负号运算符

1
2
3
4
let tempValue = -6
let plusSix = -tempValue // plusSix 等于 6
let minusSix = +tempValue // minusSix 等于 -6
//当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。

4、比较运算符(Comparison Operators)

比较运算多用于条件语句,如if条件:

1
2
3
4
5
6
7
let name = "world"
if name == "world" {
print("hello, world")
} else {
print("I'm sorry \(name), but I don't recognize you")
}
// 输出 "hello, world", 因为 `name` 就是等于 "world"

5、空合运算符(Nil Coalescing Operator)

空合运算符a ?? b)将对可选类型 a 进行空判断,如果 a 包含一个值就进行解封,否则就返回一个默认值 b。表达式 a 必须是 Optional 类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。

1
2
3
a ?? b	// 空合运算符
// 等价于
a != nil ? a! : b // 三目运算符

注意: 如果 a 为非空值(non-nil),那么值 b 将不会被计算。这也就是所谓的短路求值

6、区间运算符(Range Operators)

写法 示例
闭区间运算符 a…b for index in 1…5 {}
闭区间运算符的单侧形式 [a…]或[…b] for name in names[2…] { print(name) }
半开区间运算符 a..<b for i in 0..<count {}
半开区间运算符的单侧形式 [..<b]

7、逻辑运算符(Logical Operators)

注意: Swift 逻辑操作符 &&|| 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。为使得代码更加可读,建议使用括号来明确优先级

1
2
3
4
5
6
7
8
9
10
11
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 等价于
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}

8、溢出运算符

1
2
3
4
5
6
7
8
9
10
11
var maxInt = Int8.max	// Int8 型整数能容纳的最大值是 +127,以二进制表示即 01111111。
var minInt = Int8.min // Int8 型整数能容纳的最小值是 -128,以二进制表示即 10000000。
print("Int8.max:\(maxInt), Int8.min:\(minInt)")
// 输出 Int.max:127, Int.min:-128

// 运算的错误写法:
maxInt += 1 // 编译错误
minInt -= 1 // 编译错误

// 运算的正确写法:
minInt = minInt &- 1 // 当使用溢出减法运算符对其(二进制10000000)进行减 1 运算时,得到二进制数值 01111111,也就是十进制数值的 127(符号位被翻转了),这个值也是 Int8 型整数所能容纳的最大值。

三、函数

Swift的函数使用 func 声明一个函数,形式为:

1
2
3
func name(parameters) -> return type {
function body
}

常用的函数形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 无参数函数
func sayHello() -> String {
return "hello, world"
}
// 多参数函数,无返回值( -> Void 可以省略 )
func personInfo(name: String, age: Int) -> Void {
// function body
}
// 注意:严格上来说,虽然没有返回值被定义,greet(person:) 函数依然返回了值。没有定义返回类型的函数会返回一个特殊的Void值。它其实是一个空的元组(tuple),没有任何元素,可以写成()。

// 多重返回值,这里返回一个元组
func minMax(array: [Int]) -> (min: Int, max: Int) {
return (array.min()!, array.max()!)
}
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
30
31
32
33
34
35
36
37
38
39
// 可选返回类型,‘_’ 表示忽略参数标签
func max(_ array: [Int]) -> Int? {
return array.max()
}
// 指定参数标签(参数说明,和参数名以空格分隔),区别于参数名,默认参数名就是外部的标签名
func someFunction(parameterDescription parameter: String) {
print(parameter)
}
someFunction(parameterDescription: "parameter")
// 可以看到参数标签只供外部调用显示使用,而参数名parameter可以供内部函数使用
// 默认参数
func sumFunc(a: Int, b: Int = 12) -> Int {
return a &+ b
// 如果你在调用时候不传第二个参数,parameterWithDefault 会值为 12 传入到函数体中。
}
print(sumFunc(a: 3)) // 输出 15
// 可变参数,使用(...)的方式接收可变参数,并且必须作为左后一个参数
func sumFunc2(_ numbers: Int ...) -> Int {
var total: Int = 0
// 和OC实现不定参数不同,Swift已将不定参转换为数组
for number in numbers {
total += number
}
return total
}
print("\(sumFunc2(1,2,3,4,5))") // 输出 15
/*
输入输出参数
如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就可以将这个参数定义为输入输出参数
这有点类似OC中常见的NSError的使用,他们的本质是接受一个地址,直接操作内存地址进行修改和访问
可是使用 inout 关键字来定义输入输出参数,需要注意的是,在新版本中,已经将 inout 放在了 '参数名:' 后面,而不是前面
*/
func swapTwoObj(_ a: inout String, _ b : inout String) {
(a, b) = (b, a)
}
var str1 = "123"
var str2 = "456"
swapTwoObj(&str1, &str2)
print("str1:\(str1),str2:\(str2)") // 输出 str1:456,str2:123