Swif学习总结

Swift 作为 Objective-C 的新世代,已经成为了苹果首推的iOS开发语言,而且 Swift 的相关开发内容已经逐渐成熟,使用 Swift 开发的项目也越来越多,Swift5.0Swift 也基本趋于稳定,因此Swift已经成为了iOS开发人员的必备能力了。


目录


[TOC]

基础类型

数据类型和集合类型

Swift新提供了一套新的数据类型

  • 整型: Int
  • 浮点: Double Float
  • 布尔型: Bool
  • 字符串: String
  • 数组: Array
  • 字典: Dictionary
  • 组合: Set

常量和变量

Swift使用 letvar 来最为常量和变量的修饰符

常量和变量在Swift中,如果要使用,必须先声明

1
2
let testNum = 1
var testStr = "test"

在开发过程中,如果是不会别修改的变量,也需要用let声明为常量。

分号

Swift中换行是不需要分号的,但是如果一行里写多句代码,分号还是需要的

1
2
3
4
let testString = "Test"
print(testString)

let testNum = 0; print("\(testNum)")

数据类型的范围

Swift中可以使用minValue或者maxValue来获取当前数据的类型的最小最大值

整型

Swift 提供了 8,16,32 和 64 位编码的有符号和无符号整数Int8,UInt8 ... Int64,UInt64

而 Int 和 UInt 则和CPU有关,

在 32位平台上, Int和UInt的长度 等于 Int32 和 UInt32
在 64位平台上, Int和UInt的长度 等于 Int64 和 UInt64

浮点数

  • Double 至少15位数字的精度(64位的浮点数)
  • Float 6位数字的精度(32位的浮点数)

通常情况下,Double和Float都可以使用的时候,推荐使用Double

整数和浮点数转换

Swift中,整数和浮点数类型的转换必须显式地指定类型

1
2
3
4
5
6
7
8
let num1 = 1
let num2 = 1.1

// 显式的将浮点数转换为整数
let num3 = num1 + Int(num2)

// 显式的将整数转换为浮点数
let num4 = Double(num1) + num2

在用浮点数初始化一个新的整数类型的时候,数值会被截断。也就是说 1.95 会变成 1 , -3.5 会变为 -3

类型别名 typealias

typealias修饰词的作用类似Objective-C中的typedef,可以为已经存在的类型定义了一个新的可选名字

1
2
typealias NewType = UInt
var maxNewType = Newtype.maxValue

元组

元组把多个值合并成单一的复合型的值。元组内的值可以是任何类型,而且可以不必是同一类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建一个简单的元组
let postCode1 = (44,"OK",3.1415)

//创建一个命名元组
let postCode2 = (x: 5, y: 3)

// 不同的类型
let postCode3 = (name: "Carl", age: 78, pets: ["Bonny", "Houdon", "Miki"])

// 访问元组元素
postCode1.0 // 44
postCode1.1 // "OK"

postCode2.x // 5
postCode2.y // 3

//元组拆解
let (x, y, z) = postCode1
print("\(x)","\(y)","\(z)") // 44,"OK",3.1415

let (h, _, _) = postCode1 // 不需要的数据可以直接用"_"来代替
print("\(h)") // 44

元组适合于简单的数据组合,复杂的数据组合还是使用使用类或者结构体

需要临时组合一些相关值的时候,元组非常有用。如果数据结构需要在临时范围之外仍然存在。那就把它抽象成类或者结构体

可选项

Swift的可选项用来处理一些可能为nil的值,来保证安全性,提示这个值可能为nil.

1
let newNum = Int("哈哈哈哈")

“哈哈哈哈”并不能被Int转换成一个整数,所以这次转换可能会失败,newNum可能会为nil,所以Int()会返回一个 Int?,而Int?后面的?号代表这时一个可选项

nil

Swift 中的 nil 和 Objective-C 中的 nil 是不一样的,OC 中的 nil 是一个指向不存在对象的指针,只有对象可以设置为 nil。在 Swift中, nil 不是指针,他是值缺失的一种特殊类型,任何类型的可选项都可以设置成 nil 而不仅仅是对象类型

同样的,如果Swift中要给一个变量设置为 nil, 则需要使用 可选项

var newString: String? = nil

可选值的强制展开

因为可选项的存在,可选值可能为空,所以在使用可选值的时候必须进行判断

1
2
3
if newString != nil {
print(“newString”)
}

但是有的时候,我们已经知道这个值是一定不为空的,不需要再做多余的判断了,就可以直接使用 “!” 来强制展开,

1
print(newString!)

因为强制展开可选值的要求严格,如果强制展开了一个nil,则会导致崩溃,所以小心使用

可选项绑定

因为强制展开充满了危机,所以我们可以使用 if语句 和 变量常量 的结合,进行可选项绑定,来安全处理可选值

1
2
3
4
5
6
7

if let newNum = Int(unknownString) {
print("\(newNum)")
}
else {
print("error")
}

if let newNum = Int(unknownString)中,如果Int(unknownString)不为nil,则newNum则是展开后的值,为空则进入else语块

同时也支持变量if var newNum = Int(unknownString),方便在if代码块中操作newNum。

隐式展开可选项

当一个值在使用的时候一定不为nil,但是在初始化的时候是可能为nil的,就可以使用隐式展开可选项

在iOS中,有个很简单的例子,就是ViewController中的View,在初始化的过程中,并不是一定有值,可能为nil。但是在我们使用的过程中,View是一定有值,所以这个时候,用的是就是 view!(隐式展开可选项)

因此我们在创建一个初始化可能为nil,但是使用的时候一定有值得 变量或者常量的时候就可以使用了。

如果你在隐式展开可选项没有值的时候还尝试获取值,会导致运行错误。结果和在没有值的普通可选项后面加一个叹号一样。

错误处理

相比于可选项的通过值是否缺失来判断程序的执行正确与否,而错误处理机制能允许你判断错误的形成原因,在必要的情况下,还能将你的代码中的错误传递到程序的其他地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 可能会抛出异常的函数
func testFunction() throws {
// ...
}


// 通过 do-try-catch 块来处理异常
do {
try testFunction() // 查看函数是否异常
testFinish() // 无异常则调用此函数
} catch Error.OutOfCleanDishes { // 异常满足此条件,则调用这个catch块中的内容
testShowError()
} catch Error.MissingIngredients(let ingredients) {
testShowErroContent(ingredients)
}

断言调试(assert)

使用全局函数 assert(::) 函数来写断言。向 assert(::) 函数传入一个结果为 true 或者 false 的表达式以及一条会在结果为 false 的时候显式的信息:

1
2
3
4
func assert(@autoclosure condition: () -> Bool,
@autoclosure _ message: () -> String = default,
file: StaticString = default,
line: UInt = default)

断言只会在 Debug 模式下起作用,在 Release 版本中是被忽略的。

例子:

1
2
let testNum = 3
assert(testNum < 0, "Num Error !")

testNum < 0 为false,断言被触发,应用终止。

先决条件(precondition)

precondition 在使用和作用上和 assert 类似

precondition 与 assert 的区别在于:

  • assert只在debug环境下生效,而precondition是debug和release都生效 ( 断言是在测试期间的理性检查,而前提条件是防御的事情,如果发生,意味着你的程序只是不能合理地进行 )

基本运算符

X元运算符

  • 一元运算符对一个目标进行操作(比如 -a )。一元前缀运算符在目标之前直接添加(比如 !b ),同时一元后缀运算符直接在目标末尾添加(比如 c! )。
  • 二元运算符对两个目标进行操作(比如 a + b )同时因为它们出现在两个目标之间,所以是中缀。
  • 三元运算符操作三个目标:三元条件运算符( a ? b : c )。

赋值运算符

赋值运算符 “=” ,可以将右侧内容更新左侧内容。

但是 Swift 的赋值符号自身不会返回值,所以类似

1
2
if x = y {
}

这样的使用的禁止的

算术运算符

  • 加 ( + )
  • 减 ( - )
  • 乘 ( * )
  • 除 ( / )
  • 求余数 ( % ) (考虑负数运算,所以不是取模)

合并空值运算符

合并空值运算符 ( a ?? b )如果可选项 a 有值则展开,如果没有值,是 nil ,则返回默认值 b 。表达式 a 必须是一个可选类型。表达式 b 必须与 a 的储存类型相同。

a ?? b =====> (a != nil) ? a! : b

区间运算符

  • 闭区间运算符: a…b(a <= b) , 定义了a到b,同时包含a和b的一系列数据
  • 半开区间运算符: a..<b(a < b) , 定义了从 a 到 b 但不包括 b 的区间,即 半开
  • 单侧区间: 闭区间有另外一种形式来让区间朝一个方向尽可能的远, array[a…](a到该数组末尾,[…a]则相反为数组开始到a)

逻辑运算符

  • 逻辑 非 ( !a )
  • 逻辑 与 ( a && b )
  • 逻辑 或 ( a || b )

Swift 语言中逻辑运算符 && 和 || 是左相关的,这意味着多个逻辑运算符组合的表达式会首先计算最左边的子表达式。

字符串

字符串字面量

即为常见的双引号内的内容: let a: String = “字符串字面量内容”

初始化一个空字符串

Swift中,通常在创建一个字符串变量时要先初始化:
其中:

1
2
var testStr = ""
var testString = String()

是一样的

字符串可变性

Swift 和 OC 的字符串有着很大的不同,在 OC 中字符串的是否可变是有 (NSString , NSMutableString) 来确定的。
而Swift则统一由 “let” 和 “var” 来确定

1
2
3
4

let test1 = "test1"
var test2 = "look "
test2 += test1 // look test1

字符串是值类型

Swift 的 String类型是一种值类型。如果你创建了一个新的 String值, String值在传递给方法或者函数的时候会被复制过去,还有赋值给常量或者变量的时候也是一样。每一次赋值和传递,现存的 String值都会被复制一次,传递走的是拷贝而不是原本。Swift 的默认拷贝 String行为保证了当一个方法或者函数传给你一个 String值,你就绝对拥有了这个 String值,无需关心它从哪里来。你可以确定你传走的这个字符串除了你自己就不会有别人改变它。

Character

Swift 中,String 可以看做一个 Character 的集合, 而且 Character 值能且只能包含一个字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14

let char1: Character = "1"
let char2: Character = "+"
let char3: Character = "1"

let str: String = String([char1,char2,char3]) // 1+2


for character in "1+2" {
print(character)
}
// 1
// +
// 2

字符串插值

使用 : \(a) 来进行字符串插值

1
2
3
4

let a = 10
let b = "5 + 5 = \(a)"
print(b) // "5 + 5 = 10"

Unicode

Swift 的 String 和 Characte r类型是完全 Unicode 兼容的

字符串字面量中的特殊字符

  • 转义特殊字符 \0 (空字符), \ (反斜杠), \t (水平制表符), \n (换行符), \r(回车符), \” (双引号) 以及 \’ (单引号);
  • 任意的 Unicode 标量,写作 \u{n},里边的 n是一个 1-8 个与合法 Unicode 码位相等的16进制数字。let dollarSign = "\u{24}" // $

字符串的相关操作

字符统计

Swift中,Sting 的 count 属性可以获取该字符串的 Character值 的总数。

通过 count属性返回的字符统计并不会总是与包含相同字符的 NSString中 length属性相同。 NSString中的长度是基于在字符串的 UTF-16 表示中16位码元的数量来表示的,而不是字符串中 Unicode 扩展字形集群的数量。

字符串索引

String.Index,它表示每个 Character 在字符串中的位置。 ( startIndex属性来访问 String中第一个 Character的位置。 endIndex属性就是 String中最后一个字符后的位置 )

不同的字符会获得不同的内存空间来储存,所以为了明确哪个 Character 在哪个特定的位置,你必须从 String的开头或结尾遍历每一个 Unicode 标量。因此,Swift 的字符串不能通过整数值索引

1
2
3
4
5
6
7
8
9
10
11

let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// ! 给的索引的前一个
greeting[greeting.index(after: greeting.startIndex)]
// u 给的索引的后一个
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a 给的索引的位移后的一个

endIndex索引对应的并不是一个有效字符,只是一个结束标识符,不能被访问

同样的,如果需要获取字符串的所有的索引,则可以使用indices属性

1
2
3
4
5
6
7
8

let str = "hello word"

for itemIndex in str.indices {
print(str[itemIndex])
}

//hello word

插入和删除

  • 插入
    • insert(_:at:) 插入字符
    • insert(contentsOf:at:) 插入字符串
1
2
3
4

var testStr = "Hello, playground"
testStr.insert("!", at: testStr.endIndex) // "Hello, playground!"
testStr.insert(contentsOf: "Niko: ", at: testStr.startIndex) // // "Niko: Hello, playground!"
  • 删除
    • remove(at:) 特定索引位置移除字符
    • removeSubrange(_:) 移除一小段特定范围的字符串
1
2
3
4

var testStr = "Niko: Hello, playground!"
testStr.remove(at: testStr.index(before: testStr.endIndex)) // Niko: Hello, playground
testStr.removeSubrange(testStr.startIndex...testStr.index(testStr.startIndex, offsetBy: 5)) // Hello, playground

子字符串

Swift中获取自字符串也比较简单,直接通过索引获取就行了

1
2
3
4

var testStr = "Niko: Hello, playground!"
let subTestStrTmp = testStr[testStr.startIndex...testStr.index(testStr.startIndex, offsetBy: 6)]
print(String(subTestStrTmp)) // Niko: H

Swift 中截取出的子字符串并不是 String 类型,因为处于优化的原因,SubString 并不不会单独用内存保存着组成这个字符串的字符,而是共享 被截取的String 的内存中的那些数据,所以如果你需要保存这个子字符串,则需要用 String 去初始化一下。

字符串比较

两个 String值(或者两个 Character值)如果它们的扩展字形集群是规范化相等,则被认为是相等的。如果扩展字形集群拥有相同的语言意义和外形,我们就说它规范化相等,就算它们实际上是由不同的 Unicode 标量组合而成。

使用逻辑运算符中的 “==” 和 “!=” 即可

前缀和后缀相等性

  • hasPrefix(_:) 比较前缀相等
  • hasSuffix(_:) 比较后缀相等
1
2
3
4

let testStr = "Niko: Hello, playground!"
print(testStr.hasPrefix("Niko")) // true
print(testStr.hasSuffix("word!")) // false

集合

数组

初始化

1
2
3
4

var list1 = [Int]()

var list2 = []

集合类

Array

初始化

1
2
3
var list1 = [Int]()

var list2 = []

数组的操作

  • array.count count属性可以查看数组的元素数量
  • array.isEmpty isEmpty可以直接查看数组是否为空
  • append(_:) 可以添加元素: array.append("元素")
  • “+=” 可以直接加数组: array1 += ["1","2","3"]
  • insert(_ : at :) : 数组插入元素 : array.insert("元素", at: 0)
  • remove(_:at:) 数组删除元素 :array.remove("元素", at: 0)
  • 数组下标: array[0]获取某个元素,或者array[0...2]获取某段数组
  • lastfirst 可以直接获取首尾的元素,这样可以避免使用count属性
  • for in 遍历数组的元素, for (index, value) in array.enumerated() 可以获取元素和索引

Set

合集(Set) 将同一类型且不重复的值无序地储存在一个集合当中。当元素的顺序不那么重要的时候你就可以使用合集来代替数组,或者你需要确保元素不会重复的时候。

所有能储存在合集中的元素必须是可哈希的,所以如果需要自定义数据结构支持Set,就必须实现Swift的hashtable协议

初始化

1
2
3
4
5

var valueTest = Set<String>()
valueTest.insert("test")

var sets: Set = ["1","3","2"]

合集的操作

  • array.count count属性可以查看合集的元素数量
  • array.isEmpty isEmpty可以直接查看合集是否为空
  • remove()removeAll() 合集删除元素 :array.remove("元素") array.removeAll()
  • contains(_:) 检查合集中是否包含某个元素,返回布尔值:list.contains("元素")
  • for in 遍历合集

合集的交并集

  • 使用 intersection(_:)方法来创建一个只包含两个合集共有值的新合集;
  • 使用 symmetricDifference(_:)方法来创建一个只包含两个合集各自有的非共有值的新合集;
  • 使用 union(_:)方法来创建一个包含两个合集所有值的新合集;
  • 使用 subtracting(_:)方法来创建一个两个合集当中不包含某个合集值的新合集。

  • 使用“相等”运算符 ( == )来判断两个合集是否包含有相同的值;
  • 使用 isSubset(of:) 方法来确定一个合集的所有值是被某合集包含;
  • 使用 isSuperset(of:)方法来确定一个合集是否包含某个合集的所有值;
  • 使用 isStrictSubset(of:) 或者 isStrictSuperset(of:)方法来确定是个合集是否为某一个合集的子集或者超集,但并不相等;
  • 使用 isDisjoint(with:)方法来判断两个合集是否拥有完全不同的值。

Dictionary

字典的初始化

1
2
3
4
5

var namesOfIntegers = [Int: String]()
var numsOfIntegers = [:]

var namesDic = ["name":"1","sex":"girl","age":"19"]

字典的操作

  • dic.count count属性可以查看字典的元素数量
  • dic.isEmpty isEmpty可以直接查看字典是否为空
  • 可以直接添加新key来添加值: dic["新Key"] = "test"
  • removeValue(forkey:) 删除元素“ dic.removeValue(forKey: "key")
  • for (ley, value) in dic 遍历字典的键值对

控制流

for - in

for - in 可以遍历集合类型的元素

1
2
3
4
5
6
7
8

let list = [0,1,2,3]

for item int list {
print("\(item)")
}

// 0 1 2 3

While

while 循环通过判断单一的条件开始。如果条件为 true ,语句的合集就会重复执行直到条件变为 false

1
2
3
4

while x < y {
print("test")
}

repeat-while

功能上等同于do-while

if-else

if - else 和 OC 的条件语句的功能差不多,但是 Swift 中小括号不是必写的

Switch

Swift 的 Switch 与 OC 不一样,break不再是必须的情况,因为Swift中,进入case,执行完case内的语句后,接直接结束Switch语句了,不需要单独的break来结束,但是break在Swift中仍然有效,可根据习惯来写。

如果需要 OC 的Switch属性(case后会进入下个case,而不会直接结束),则需要在case语句中,在末尾加入fallthrough而不是break就可以了

Switch 的灵活运用

Swift 的 Switch 非常灵活

区间匹配
1
2
3
4
5
6
7
8
9
10
11
12
13

switch 15 {

case 0:
print("0")
case 1...10:
print("1")
case 11..<16: // right
print("2")
default:
print("3")

}
多条件
1
2
3
4
5
6
7
8
9
10

switch 3 {
case 0,1,2:
print("0")
case 3,4,5:
print("1") // right
default:
print("3")

}
元组
1
2
3
4
5
6
7
8
9
10
11
12
13
14

switch (1,9) {
case (0,0):
print("0")
case (1,0):
print("2")
case (1,1):
print("3")
case (1,_):
print("4") // right
default:
print("5")

}
值绑定
1
2
3
4
5
6
7
8
9
10
11

switch (9,0) {

case (let x, 0):
print("1") // right
case (0, let y):
print("2")
default:
print("3")

}
Where
1
2
3
4
5
6
7
8
9
10
11
12
13

switch (9,0) {

case let (x, y) where x == y:
print("0")
case let (x, y) where x < y:
print("1")
case let (x, y) where x > y:
print("2") // right
default:
print("3")

}

guard

guard 语句,类似于 if 语句,基于布尔值表达式来执行语句。使用 guard 语句来要求一个条件必须是真才能执行 guard 之后的语句。与 if 语句不同, guard 语句总是有一个 else 分句—— else 分句里的代码会在条件不为真的时候执行

1
2
3
4
5
6
7
8
9
10

let test = "test"

guard test == "value" else {
print("0")
}

print("1")

// 0

函数

1
2
3
4

func usingFunctionName(name: String, age: Int) -> Int {
return 0
}

在例子的函数中,如果不需要的部分都可以省略不写的:

1
2
3
4
5
6

func usingFunctionName() {

}

// 省略的返回值和形参,表示不需要返回值和输入参数

闭包

闭包的表达式

1
2
3
4

{ (parameters) -> (return type) in
statements
}

闭包作为参数

1
2
3
4
5
6
7
8
9
10
11
12

let testStr = [0,1,2,3,4,5,6,7,8,9]

func testFunction(handler: @escaping () -> String) {
print(handler())
}

testFunction { () -> String in
return "test"
}

// test

通常在参数中用 (type) -> retuntype 来作为闭包的表达式,并且需要加上 @escaping 来指明

@escaping 是表示这个闭包是逃逸闭包,允许在函数返回之后被调用

枚举

枚举为一组相关值定义了一个通用类型,从而可以让你在代码中类型安全地操作这些值。

Swift 枚举语法

用 enum关键字 来定义一个枚举,然后将其所有的定义内容放在一个大括号{}中

1
2
3
4
5
6
7
8
9
10
11

enum nTest {
case left
case right
case uppset
case bottom
}

print("\(nTest.left)")

// left

遍历枚举情况

Swift 中需要在枚举名字后面写上 CaseIterable 来允许枚举被遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

enum nTest: CaseIterable {
case left
case right
case uppset
case bottom
}

for item in nTest.allCases {
print("\(item)")
}

// left
// right
// uppset
// bottom

关联值

定义 Swift 枚举来存储任意给定类型的关联值,如果需要的话不同枚举成员关联值的类型可以不同。枚举其他语言中的 discriminated unions, tagged unions, 或者 variants 类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

enum nTest {
case name(String)
case ageSix(Int, String)
}

var showDetail = nTest.name("name")
showDetail = nTest.ageSix(19, "boy")

switch showDetail {
case let .ageSix(x, y):
print("\(x) \(y)")
case let .name(z):
print("\(z)")
}

// 19 boy

原始值

1
2
3
4
5

enum nTest {
case name = "lucy"
case age = "girl"
}

可以给枚举值给予一个 默认的值。

如果只给其中一个枚举赋予了默认值,那么其他枚举会有一个隐式的默认值

比如:

1
2
3
4
5
6
7

enum nTest {
case left = 0
case right // 1
case upset // 2
case bottom // 3
}

类和结构体

类和结构体是一种多功能且灵活的构造体。通过使用与现存常量、变量、函数完全相同的语法来在类和结构体当中定义属性和方法以添加功能.

Swift不需要你为自定义类和结构体创建独立的接口和实现文件。在 Swift 中,你在一个文件中定义一个类或者结构体, 则系统将会自动生成面向其他代码的外部接口

值得注意的是:

  • 类是引用类型
  • 结构体和枚举是值类型

定义

1
2
3
4
5
6
7
8
9
10
11
12

class Test {

var height = 100

}

struct TestStruct {

var width = 200

}

实例化

1
2
3
4

let item = Test()

let itemStruct = TestStruct()

属性访问

1
2
3
4

item.height

itemStruct.width

=== 和 !==

利用这两个恒等运算符来检查两个常量或者变量是否引用相同的实例

1
2
3
4

if obj_a === obj_b {
print("obj_a 和 obj_b 都是同一个对象的引用")
}

类和结构体之间的选择

结构体实例总是通过值来传递,而类实例总是通过引用来传递

符合以下内容就使用结构体:

  • 结构体的主要目的是为了封装一些相关的简单数据值;
  • 当你在赋予或者传递结构实例时,有理由需要封装的数据值被拷贝而不是引用;
  • 任何存储在结构体中的属性是值类型,也将被拷贝而不是被引用;
  • 结构体不需要从一个已存在类型继承属性或者行为。

反之使用类

Swift 的 String , Array 和 Dictionary类型是作为结构体来实现的,这意味着字符串,数组和字典在它们被赋值到一个新的常量或者变量,亦或者它们本身被传递到一个函数或方法中的时候,其实是传递了拷贝。

这种行为不同于基础库中的 NSString, NSArray和 NSDictionary,它们是作为类来实现的,而不是结构体。 NSString , NSArray 和 NSDictionary实例总是作为一个已存在实例的引用而不是拷贝来赋值和传递。

属性

属性可以将值与特定的类、结构体或者是枚举联系起来。存储属性会存储常量或变量作为实例的一部分,反之计算属性会计算(而不是存储)值。计算属性可以由类、结构体和枚举定义。存储属性只能由类和结构体定义。

存储属性

存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性(由 var 关键字引入)要么是常量存储属性(由 let 关键字引入)

lazy

延迟存储属性的初始值在其第一次使用时才进行计算。你可以通过在其声明前标注 lazy 修饰语来表示一个延迟存储属性。

与 OC 中的懒加载很像

1
2
3
4
5
6
7
8
9
10
11
12
13

class Test {

lazy var num = 1

func fuction() {
print("\(num)")
}
}

let tst = Test()

tst.fuction()

getter - setter (计算属性)

与 OC 不同,Swift的getter和setter是 计算属性 , 计算属性并不会存储属性的值。因此在每次调用计算属性时,都要计算该值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

class Test {

var calNum = 0
var curNum: Int {
get {
return calNum
}
set {
calNum = newValue
}

}
}

let tst = Test()
tst.curNum = 1
print("\(tst.curNum)")

计算属性总是需要一个 getter。如果缺少 setter,则该属性被称为只读属性。

属性观察者

属性观察者会观察并对属性值的变化做出回应。每当一个属性的值被设置时,属性观察者都会被调用,即使这个值与该属性当前的值相同。

  • willSet 会在改值被存储之前被调用
  • didSet 会在新值被存储后被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Test {

var curNum: Int = 0 {
willSet {
print("newValue : \(newValue)")
}
didSet {
print("oldValue : \(oldValue)")
}
}
}

let tst = Test()
tst.curNum = 100
print("\(tst.curNum)")

// newValue : 100
// oldValue : 0
// 100

继承

一个类可以从另一个类继承方法、属性和其他的特性。当一个类从另一个类继承的时候,继承的类就是所谓的子类,而这个类继承的类被称为父类。

在 Swift 中类可以调用和访问属于它们父类的方法、属性和下标脚本,并且可以提供它们自己重写的方法,属性和下标脚本来定义或修改它们的行为。Swift 会通过检查重写定义都有一个与之匹配的父类定义来确保你的重写是正确的。

类也可以向继承的属性添加属性观察器,以便在属性的值改变时得到通知。可以添加任何属性监视到属性中,不管它是被定义为存储还是计算属性。

1
2
3
4

class SomeSubclass: SomeSuperclass {
// subclass definition goes here
}

重写 overide

override 关键字会执行 Swift 编译器检查你重写的类的父类(或者父类的父类)是否有与之匹配的声明来供你重写。这个检查确保你重写的定义是正确的。

阻止重写 final

你可以通过标记为终点来阻止一个方法、属性或者下标脚本被重写。通过在方法、属性或者下标脚本的关键字前写 final 修饰符(比如 final var , final func , final class func , final subscript )。

初始化

初始化是为类、结构体或者枚举准备实例的过程。这个过需要给实例里的每一个存储属性设置一个初始值并且在新实例可以使用之前执行任何其他所必须的配置或初始化。

init()

初始化器在创建特定类型的实例时被调用。在这个简单的形式中,初始化器就像一个没有形式参数的实例方法,使用 init 关键字

1
2
3
4
5
6

var test: Int
init() {
// 初始化
test = 0
}

convenience 遍历构造器

使用convenience修饰的构造函数叫做便利构造函数

required 必要初始化器

在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器

当子类重写父类的必要初始化器时,必须在子类的初始化器前同样添加 required 修饰符以确保当其它类继承该子类时,该初始化器同为必要初始化器。在重写父类的必要初始化器时,不需要添加 override 修饰符

初始化过程

Swift 的类初始化是一个两段式过程。在第一个阶段,每一个存储属性被引入类为分配了一个初始值。一旦每个存储属性的初始状态被确定,第二个阶段就开始了,每个类都有机会在新的实例准备使用之前来定制它的存储属性。

两段式初始化过程的使用让初始化更加安全,同时在每个类的层级结构给与了完备的灵活性。两段式初始化过程可以防止属性值在初始化之前被访问,还可以防止属性值被另一个初始化器意外地赋予不同的值。

初始化器的自动继承

子类默认不会继承父类初始化器。但是在特定的情况下父类初始化器是可以被自动继承的

  • 如果你的子类没有定义任何指定初始化器,它会自动继承父类所有的指定初始化器。
  • 如果你的子类提供了所有父类指定初始化器的实现——要么是通过规则1继承来的,要么通过在定义中提供自定义实现的——那么它自动继承所有的父类便捷初始化器。

反初始化

在类实例被释放的时候,反初始化器就会立即被调用。你可以是用 deinit 关键字来写反初始化器,就如同写初始化器要用 init 关键字一样。反初始化器只在类类型中有效.

1
2
3
4

deinit {
// perform the deinitialization
}

自动引用计数

Swift 使用自动引用计数(ARC)机制来追踪和管理你的 App 的内存。 这点上,Swift 和 OC 非常相似。

引用计数只应用于类的实例。结构体和枚举是值类型,不是引用类型,没有通过引用存储和传递。

循环强引用

因为可能导致,两个类实例互相强引用的情况,这样的话,两个对象的引用计数就永远不能为0,也就是说对象不会被销毁,内存不能释放。这种情况被称为 循环强引用

而解决方法就是打断这个引用链,使用 ”weak“ 修饰其中某条引用即可。

1
2
3
4
5

weak var weakSelf = self
self.handler = { () -> () in
weakSelf.name = "Lucy"
}

可选链

可选链是一个调用和查询可选属性、方法和下标的过程,它可能为 nil 。如果可选项包含值,属性、方法或者下标的调用成功;如果可选项是 nil ,属性、方法或者下标的调用会返回 nil 。多个查询可以链接在一起,如果链中任何一个节点是 nil ,那么整个链就会得体地失败。

类型转换

类型转换可以判断实例的类型,也可以将该实例在其所在的类层次中视为其父类或子类的实例。Swift 中类型转换的实现为 is 和 as 操作符.

类型检查 is

使用类型检查操作符 ( is )来检查一个实例是否属于一个特定的子类。如果实例是该子类类型,类型检查操作符返回 true ,否则返回 false

1
2
3
4
5
6
7
8

var num:String = "0"

if num is String {
print("right")
}

// right

向下类型转换 as

由于向下类型转换能失败,类型转换操作符就有了两个不同形式。条件形式, as? ,返回了一个你将要向下类型转换的值的可选项。强制形式, as! ,则将向下类型转换和强制展开结合为一个步骤。

Any 和 AnyObject

  • AnyObject 可以表示任何类类型的实例。
  • Any 可以表示任何类型,包括函数类型。

Any类型表示了任意类型的值,包括可选类型。如果你给显式声明的Any类型使用可选项,Swift 就会发出警告。

内嵌类型

若要在一种类型中嵌套另一种类型,在其支持类型的大括号内定义即可。可以根据需求多级嵌套数个类型。

1
2
3
4
5
6
7
8
9
10
11

class Test {

enum typeNum {
case : one
case : two
}

}

let value = Test.typeNum.one

扩展

功能与 OC 中的分类差不多,但是不会像 分类 一样有一个命名

扩展可以向一个类型添加新的方法,但是不能重写已有的方法

1
2
3
4

extension SomeClass {
// add new methods
}

扩展还可以添加 计算属性。

1
2
3
4
5
6
7
8
9
10
11

extension SomeClass {

var height: Int {
return 100
}

var width: Int {
return 200
}
}

扩展可以添加新的计算属性,但是不能添加存储属性,也不能向已有的属性添加属性观察者。

异变实例方法 mutating

增加了扩展的实例方法仍可以修改(或异变)实例本身。结构体和枚举类型方法在修改 self 或本身的属性时必须标记实例方法为 mutating ,和原本实现的异变方法一样。

Swift中protocol的功能比OC中强大很多,不仅能再class中实现,同时也适用于struct、enum。但是struct、enum都是值类型,每个值都是有默认的,所以在实例方法中不能改变,因此就要用mutating关键字,这个关键字可以让在此方法中值的改变,会返回到原始结构里边

1
2
3
4
5
6
7
8
9
10
11
12

extension Int {

mutating func add() {
self = self + self
}

}

var num = 1

num.add() // 2

协议 protocol

1
2
3
4

protocol SomeName {

}

属性

协议可以要求所有遵循该协议的类型提供特定名字和类型的实例属性或类型属性。协议并不会具体说明属性是储存型属性还是计算型属性——它只具体要求属性有特定的名称和类型。协议同时要求一个属性必须明确是可读的或可读的和可写的。

属性要求定义为变量属性,在名称前面使用 var 关键字。可读写的属性使用 { get set } 来写在声明后面来明确,使用 { get } 来明确可读的属性。

1
2
3
4
5
6
7
8

protocol SomeProtocol {
var num: Int { get set }
}

struct Test: SomeProtocl {
var num: Int = 0
}

异变方法 mutating

有时一个方法需要改变(或异变)其所属的实例。例如值类型的实例方法(即结构体或枚举),在方法的 func 关键字之前使用 mutating 关键字,来表示在该方法可以改变其所属的实例,以及该实例的所有属性。

在TestPro协议的定义中, test() 方法使用 mutating 关键字标记,来表明该方法在调用时会改变遵循该协议的实例的状态:

1
2
3
4

protocol TestPro {
mutating func test()
}

初始化器

协议可以要求遵循协议的类型实现指定的初始化器。和一般的初始化器一样,只用将初始化器写在协议的定义当中,只是不用写大括号也就是初始化器的实体:

1
2
3
4

protocol SomeProtocol {
init(someParameter: Int)
}

将协议作为类型

实际上协议自身并不实现功能。不过你创建的任意协议都可以变为一个功能完备的类型在代码中使用。

  • 在函数、方法或者初始化器里作为形式参数类型或者返回类型;
  • 作为常量、变量或者属性的类型;
  • 作为数组、字典或者其他存储器的元素的类型。
1
2
3
4
5
6
7
8

class Test {

var item: SomeProtocol

}

// item 的类型是 协议SomeProtocol,这样所有实现了SomeProtocol协议 的类的实例都可以赋值 item

委托

委托是一个允许类或者结构体放手(或者说委托)它们自身的某些责任给另外类型实例的设计模式。这个设计模式通过定义一个封装了委托责任的协议来实现,比如遵循了协议的类型(所谓的委托)来保证提供被委托的功能。委托可以用来响应一个特定的行为,或者从外部资源取回数据而不需要了解资源具体的类型。

效果等同于 OC 中的代理

类专用的协议

通过添加 AnyObject 关键字到协议的继承列表,你就可以限制协议只能被类类型采纳(并且不是结构体或者枚举

1
2
3
4

protocol SomeClassOnlyProtocol: AnyObject,SomeInheritedProtocol {
// class-only protocol definition goes here
}

可选协议 optional

可选要求使用 optional 修饰符作为前缀放在协议的定义中。可选要求允许你的代码与 Objective-C 操作。协议和可选要求必须使用 @objc 标志标记。注意 @objc 协议只能被继承自 Objective-C 类或其他 @objc 类采纳。它们不能被结构体或者枚举采纳

提供默认实现

你可以使用协议扩展来给协议的任意方法或者计算属性要求提供默认实现。如果遵循类型给这个协议的要求提供了它自己的实现,那么它就会替代扩展中提供的默认实现。

1
2
3
4
5
6

extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}

泛型

泛型代码让你能根据你所定义的要求写出可以用于任何类型的灵活的、可复用的函数。你可以编写出可复用、意图表达清晰、抽象的代码。

比如:Array 、 Dictionary就支持泛型(Array<T>)

内存安全性

默认情况下,Swift 会阻止你代码中发生的不安全行为。

访问控制

访问控制限制其他源文件和模块对你的代码的访问。这个特性允许你隐藏代码的实现细节,并指定一个偏好的接口让其他代码可以访问和使用。

  • Open 访问 和 public 访问 允许实体被定义模块中的任意源文件访问,同样可以被另一模块的源文件通过导入该定义模块来访问。在指定框架的公共接口时,通常使用 open 或 public 访问。 open 和 public 访问 之间的区别将在之后给出;
  • Internal 访问 允许实体被定义模块中的任意源文件访问,但不能被该模块之外的任何源文件访问。通常在定义应用程序或是框架的内部结构时使用。
  • File-private 访问 将实体的使用限制于当前定义源文件中。当一些细节在整个文件中使用时,使用 file-private 访问隐藏特定功能的实现细节。
  • private 访问 将实体的使用限制于封闭声明中。当一些细节仅在单独的声明中使用时,使用 private 访问隐藏特定功能的实现细节。

元组类型的访问级别是所有类型里最严格的。例如,如果你将两个不同类型的元素组成一个元组,一个元素的访问级别是 internal,另一个是 private,那么这个元组类型是 private 级别的。

高级运算符

取反 运算符 ~

位取反运算符( ~ )是对所有位的数字进行取反操作:

与 运算符 &

位与运算符( & )可以对两个数的比特位进行合并。它会返回一个新的数,只有当这两个数都是 1 的时候才能返回 1

或 运算符

位或运算符( | )可以对两个比特位进行比较,然后返回一个新的数,只要两个操作位任意一个为 1 时,那么对应的位数就为 1 :

异或 运算符 ^

位异或运算符,或者说“互斥或”( ^ )可以对两个数的比特位进行比较。它返回一个新的数,当两个操作数的对应位不相同时,该数的对应位就为 1 :

左移 和 右移运算符 << >>

位左移运算符( << )和位右移运算符( >> )可以把所有位数的数字向左或向右移动一个确定的位数,但是需要遵守下面定义的规则。

位左和右移具有给整数乘以或除以二的效果。将一个数左移一位相当于把这个数翻倍,将一个数右移一位相当于把这个数减半。

例如:11111111 << 1 和 11111111 >> 1

1
2
3

var num = 2
print("\(num << 1)") // 4
理解排序算法 计算机科学 - 二进制和算数逻辑单元

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×