Swift全面总结

语言基础

程序是指令的集合,写程序就是写一系列的指令去控制计算机做我们想做的事情。
编译:将程序设计语言转换成计算机能够理解的机器语言或者某种中间代码的过程。

冯诺依曼体系结构的计算机:

  1. 使用二进制
  2. 程序存储执行

变量和常量

定义变量和常量是为了保存数据,变量和常量就是某种类型的值的存储空间。

var a: Int = 10
a = 100
var b: Int
b = 1000
var c = 10000

let d: Int = 10
// d = 100      // compiler error
let e = 1000

说明:1. Swift有非常强大的类型推断,所以定义变量或常量时如果可以的话应该直接使用类型推断不用手动指定类型;2. 如果可以的话应该尽可能使用常量而不是变量。

语言元素

var a: Int = 10

关键字:有特殊含义的单词
标识符:给变量、常量、函数、类、结构、协议、枚举、方法、属性等起的名字

  1. 字母(Unicode字符)、数字、下划线,数字不能开头
  2. 大小写敏感(区分大小写)
  3. 不能使用关键字做标识符
  4. 使用驼峰命名法(命名变量、常量、函数、方法、属性第一个单词小写,从第二个单词开始每个单词首字母大写;命名类、结构、协议、枚举每个单词首字母都要大写)
  5. 见名知意
  6. 命名私有的属性和方法时以下划线开头

运算符:Swift中的运算符其实都是函数

  1. 赋值运算符:=、+=、-=、……
  2. 算术运算符:+、-、*、/、%
  3. 比较运算符:==、!=、<、<=、>、>=
  4. 逻辑运算符:&&、||、!
  5. 条件运算符:?:
  6. 其他运算符:[]、.、??、?、!

字面(常)量:

  1. 整数字面量:10、1_234_567、0x10、0o10、0b10
  2. 小数字面量:123.45、1.2345e2、0xab.cdp2
  3. 字符字面量:"c"、"\n"、"\u{41}"、"\u{9a86}"
  4. 字符串字面量:"Hello"、"caf\u{e9}"
  5. 布尔字面量:true、false
  6. 空值字面量:nil
  7. 类型字面量:String.self、UILabel.self

分隔符:将不同的语言元素符号分开

说明:Swift中每个语句后面的分号是可写可不写的,写代码时尽量保证一行只有一条语句这样就可以省略掉分号。

分支和循环

分支

  • if...else...

下面的程序实现了分段函数求值。

let x = 3.2
let y: Double
if x < -1 {
    y = 3 * x + 5
}
else if x <= 1 {
    y = 5 * x - 3
}
else {
    y = 7 * x + 1
}
print("f(\\(x))=\\(y)")
  • switch...case...default

下面的程序实现了将百分制的成绩转换成A-E的等级。

let score = 92.5
let level: String
switch score {
case 0..<60:
    level = "E"
case 60..<70:
    level = "D"
case 70..<80:
    level = "C"
case 80..<90:
    level = "B"
case 90...100:
    level = "A"
default:
    level = "输入错误"
}
print(level)

循环

  • while

下面的程序实现了1-100求和。

var sum = 0
var i = 1
while i <= 100 {
    sum += i
    i += 1
}
print(sum)
  • repeat...while...

下面的程序实现了1-100求和。

var sum = 0
var i = 1
repeat {
    sum += i
    i += 1
} while i <= 100
print(sum)
  • for

下面的程序实现了1-100求和。

var sum = 0
for i in 1...100 {
    sum += i
}
print(sum)

穷举法:穷尽所有可能性直到找到正确答案。

下面的程序实现了"百钱百鸡"问题的求解。

for x in 0...20 {
    for y in 0...33 {
        let z = 100 - x - y
        if 5 * x + 3 * y + z / 3 == 100 && z % 3 == 0 {
            print("公鸡: \\(x), 母鸡: \\(y), 小鸡: \\(z)")
        }
    }
}

说明:在循环中可以使用break关键字来提前终止循环,也可以使用continue关键字使循环直接进入下一轮,但是应该尽量减少对break和continue的使用,因为它们不会让你的程序变得更好。

综合案例:Craps赌博游戏。

游戏规则:玩家摇两颗色子,如果第一次摇出了7点或11点,玩家胜;如果摇出2点、3点或12点,庄家胜;其他点数游戏继续。在继续的过程中玩家重新摇色子,如果摇出了7点,庄家胜;如果摇出了第一次摇的点数,玩家胜;否则玩家继续摇色子直到分出胜负。

func roll() -> Int {
    return Int(arc4random_uniform(6)) + 1
}

let firstPoint = roll() + roll()
print("玩家摇出了\\(firstPoint)点")
var needsGoOn = false
switch firstPoint {
case 7, 11: print("玩家胜!")
case 2, 3, 12: print("庄家胜!")
default: needsGoOn = true
}
while needsGoOn {
    let currentPoint = roll() + roll()
    print("玩家摇出了\\(currentPoint)点")
    if currentPoint == 7 {
        print("庄家胜!")
        needsGoOn = false
    }
    else if currentPoint == firstPoint {
        print("玩家胜!")
        needsGoOn = false
    }
}

容器

数组

数组是使用连续的内存空间保存多个同类型的元素的容器,因为数组中的元素在内存中是连续的,所以可以使用下标运算来访问数组中的元素,实现随机存取。

  • 创建数组
var array1: [Int] = []
var array2: Array<Int> = []
var array3 = [1, 2, 3, 4, 5]
var array4 = [Int](count: 5, repeatedValue: 0)
var array5 = Array<Int>(count: 5, repeatedValue: 0)
  • 添加元素
array1.append(2)
array1.append(3)
array1.insert(1, atIndex: 0)
array1.insert(4, atIndex: array1.count)
array1 += [5]
array1 += [6, 7, 8]
  • 删除元素
array1.removeAtIndex(2)
array1.removeFirst()
array1.removeFirst(2)
array1.removeLast()
array1.removeRange(1...2)
array1.removeAll()
  • 修改元素
array3[0] = 100
array3[array3.count - 1] = 500
  • 遍历数组
  1. 方式1
for i in 0..<array3.count {
    print(array3[i])
}
  1. 方式2
for temp in array3 {
    print(temp)
}
for temp in array3[1...3] {
    print(temp)
}

说明:for-in循环是一个只读循环,这也就意味着再循环的过程中不能对数组中的元素进行修改

  1. 方式3
for (i, temp) in array3.enumerate() {
    if i == 0 {
        array3[i] = 1
    }
    print("\\(i). \\(temp)")
}

提醒:操作数组时最重要的是不要越界访问元素。数组对象的count属性表明了数组中有多少个元素,那么有效的索引(下标)范围是0到count-1。

数组中的元素也可以是数组,因此我们可以构造多维数组。最常见的是二维数组,它相当于是一个有行有列的数组,数组中的每个元素代表一行,该数组中的每个元素代表行里面的列。二维数组可以模拟现实世界中的表格、数学上的矩阵、棋类游戏的棋盘、2D游戏中的地图,所以在实际开发中使用非常广泛。

下面的程序是用二维数组模拟表格的例子。

func randomInt(min: UInt32, max: UInt32) -> Int {
    return Int(arc4random_uniform(max - min + 1) + min)
}

let namesArray = ["关羽", "张飞", "赵云", "马超", "黄忠"]
let coursesArray = ["语文", "数学", "英语"]

var scoresArray = [[Double]](count: namesArray.count, repeatedValue: [Double](count: coursesArray.count, repeatedValue: 0))

for i in 0..<scoresArray.count {
    for j in 0..<scoresArray[i].count {
        scoresArray[i][j] = Double(randomInt(50, max: 100))
    }
}

for (index, array) in scoresArray.enumerate() {
    var sum = 0.0
    for score in array {
        sum += score
    }
    let avg = sum / Double(coursesArray.count)
    print("\\(namesArray[index])的平均成绩为: \\(avg)")
}

for i in 0..<coursesArray.count {
    var sum = 0.0
    for row in 0..<scoresArray.count {
        sum += scoresArray[row][i]
    }
    let avg = sum / Double(namesArray.count)
    print("\\(coursesArray[i])课的平均成绩为: \\(avg)")
}

集合

集合在内存中是离散的,集合中的元素通过计算Hash Code(哈希码或散列码)来决定存放在内存中的什么位置,集合中不允许有重复元素。

  • 创建集合
var set: Set<Int> = [1, 2, 1, 2, 3, 5]
  • 添加和删除元素
set.insert(100)

set.remove(5)
set.removeFirst()
set.removeAll()
  • 集合运算(交集、并集、差集)
var set1: Set<Int> = [1, 2, 1, 2, 3, 4, 5]
var set2: Set<Int> = [1, 3, 5, 7]

set1.intersect(set2)
set1.union(set2)
set1.subtract(set2)

字典

字典是以键值对的方式保存数据的容器,字典中的每个元素都是键值对组合,通过键可以找到对应的值。

  • 创建字典
var dict = [
    1: "hello",
    2: "good",
    3: "wonderful",
    5: "delicious"
]
  • 添加、删除、修改元素
dict[3] = "terrible"
dict[4] = "shit"
dict[5] = nil
  • 遍历元素
for key in dict.keys {
    print("\\(key) ---> \\(dict[key]!)")
}
for value in dict.values {
    print(value)
}
for (index, value) in dict.enumerate() {
    print("\\(index). \\(value.0) ---> \\(value.1)")
}

重要操作

  • 排序
  1. sort
  2. sortInPlace

说明:排序方法的参数是一个闭包(closure),该闭包的作用是比较数组中两个元素的大小。

let array = [23, 45, 12, 89, 98, 55, 7]
array.sort({ (one: Int, two: Int) -> Bool in
    return one < two
})
array.sort({ (one, two) in one < two })
array.sort({ one, two in one < two })
array.sort({ $0 < $1 })
array.sort { $0 < $1 }
array.sort(<)
  • 过滤
let array = [23, 45, 12, 89, 98, 55, 7]
// 筛选掉不满足条件的数据
let newArray = array.filter { $0 > 50 }
print(newArray) // [89, 98, 55]
  • 映射
let array = [23, 45, 12, 89, 98, 55, 7]
// 通过映射对数据进行变换处理
let newArray = array.map { $0 % 10 }
print(newArray)
  • 归约
let array = [23, 45, 12, 89, 98, 55, 7]
let result = array.reduce(0, combine: +)
print(result)

函数和闭包

函数是独立的可重复使用的功能模块,如果程序中出现了大量的重复代码,通常都可以将这部分功能封装成一个独立的函数。在Swift中,函数是"一等公民",可以作为类型来使用,也就是说函数可以赋值给一个变量或常量,可以将函数作为函数的参数或者返回值,还可以使用高阶函数。

func 函数名([参数1: 类型, 参数2: 类型, ...]) [throws|rethrows] [-> 返回类型] {
    函数的执行体
    [return 表达式]
}
  • 外部参数名
    • 函数名(外部参数名 内部参数名: 类型, 外部参数名 内部参数名: 类型)
    • 如果不写外部参数名那么内部参数名也是外部参数名
    • 可以使用_来作为外部参数名表示省略外部参数名
func myMin(a x: Int, b y: Int) -> Int {
    return x < y ? x : y
}

// 调用函数的时候要写函数的外部参数名
print(myMin(a: 3, b: 5))
  • inout参数

inout - 输入输出参数(不仅将数据传入函数还要从函数中取出数据)


func createX(inout x: Int) {
    x = 1000
}

var x = 1
// inout类型的参数前要加上&符号
createX(&x)
print(x)
  • 可变参数列表

Swift中函数的参数列表可以是可变参数列表(参数的个数是任意多个)


func sum(nums: Int...) -> Int {
    var total = 0
    for num in nums {
        total += num
    }
    return total
}

闭包就是没有名字的函数(匿名函数)或者称之为函数表达式(Lambda表达式),Objective-C中与之对应的概念叫block。如果一个函数的参数类型是函数我们可以传入一个闭包;如果一个函数的返回类型是函数我们可以返回一个闭包;如果一个类的某个属性是函数我们也可以将一个闭包表达式赋值给它。

{ ([参数列表]) [-> 返回类型] in 代码 }

面向对象编程(OOP)

基本概念

对象:接收消息的单元,对象是一个具体的概念。

类:对象的蓝图和模板,类是一个抽象概念。

消息:对象之间通信的方式,通过给对象发消息可以让对象执行对应的操作来解决问题。

四大支柱

抽象:定义类的过程就是一个抽象的过程,需要做数据抽象和行为抽象,数据抽象找到对象的属性(保存对象状态的存储属性),行为抽象找到对象的方法(可以给对象发的消息)。

封装:

  • 观点1: 我们在类中写方法其实就是在封装API,方法的内部实现可能会很复杂,但是这些对调用这来说是不可见的,调用只能看到方法有一个简单清晰的接口。
  • 观点2: 将对象的属性和操作这些属性的方法绑定在一起。
  • 观点3: 隐藏一切可以隐藏的实现细节,只提供简单清晰的接口(界面)。

继承: 从已有的类创建新类的过程,提供继承信息的称为父类(超类/基类),得到继承信息的称为子类(派生类/衍生类),通常子类除了得到父类的继承信息还会增加一些自己特有的东西,所以子类的能力一定比父类更强大,继承的意义在于子类可以复用父类的代码并且增强系统现有的功能

多态:同样的对象类型,
接收相同的消息(调用相同的方法),但是做了不同的事情 这就是多态(polymorphism)
实现多态的关键步骤:
1.方法重写(子类在继承父类的过程中对父类已有的方法进行重写, 而且不同的子类给出各自不同的实现版本)
2.对象造型(将子类对象当成父类型来使用)

三个步骤

  1. 定义类
  • 数据抽象
    • 存储属性
  • 行为抽象
    • 方法(写到类里面的函数或者说跟对象绑定的行为就是方法)
      • 对象方法:给对象发的消息
      • 类方法:给类发的消息,与对象的状态无关的方法
    • 计算属性
  • 构造器
    • 指派构造器
    • 便利构造器(convenience)
    • 必要构造器(required)
      2创建对象
class Triangle {
    var a: Double
    var b: Double
    var c: Double

    init(a: Double, b: Double, c: Double) {
        self.a = a
        self.b = b
        self.c = c
    }

    // 类方法(发给类的消息与对象状态无关)
    // 此处的static也可以换成class作用相同
    static func isValid(a: Double, _ b: Double, _ c: Double) -> Bool {
        return a + b > c && b + c > a && c + a > b
    }
    // 对象方法(发给对象的消息与对象状态有关)
    func perimeter() -> Double {
        return a + b + c
    }
}

3给对象发消息


let a = 1.0
let b = 2.0
let c = 3.0
// 在创建对象前先调用类方法判定给定的三条边能否构成三角形
// 类方法是发给类的消息所以不用创建对象直接通过类名调用
if Triangle.isValid(a, b, c) {
    let t = Triangle(a: a, b: b, c: c)
    // 对象方法是发给对象的消息要先创建对象才能调用
    print(t.perimeter())
}
else {
    print("无法创建三角形")
}

相关内容

  • 枚举
    • 枚举是定义符号常量的手段,它把一堆相似的值组织在一起。例如你在指定文字对齐方式的时候通常有三种可选的值:左对齐、右对齐和居中对齐;你在处理游戏中的方法时可能的取值有东、西、南、北。Swift中的枚举比你了解的其他语言的枚举更加强大,它的行为类似于类和结构体,它甚至可以有自己的方法,包括构造器。

我们先通过一个简单的例子 性别的男女 代码如下所示:

enum Gender{
    case Male
    case Female
}


  • 结构(体)
    Swift中的类和结构体定义的语法是非常相似的。类使用class关键词定义类,使用struct关键词定义结构体,它们的语法格式如下:
class 类名 {
    定义类的成员
}
struct 结构体名 {
    定义结构体的成员
}

总结:类和结构的区别到底有哪些?什么时候应该使用结构?什么时候应该使用类?

  • 扩展(extension)

扩展就是向一个已有的类、结构体或枚举类型添加新功能。
扩展可以对一个类型添加新的功能,但是不能重写已有的功能。

拓展随机颜色为例

extension UIColor {
    
    static func randomColor() -> UIColor {
        let r = CGFloat(randomInt(0, 255)) / 255.0
        let g = CGFloat(randomInt(0, 255)) / 255.0
        let b = CGFloat(randomInt(0, 255)) / 255.0
        return UIColor(red: r, green: g, blue: b, alpha: 1)
    }
}
  • 运算符重载

    • 我们可以规定面积较小的图形排在前面,面积较大的图形排在后面,于是我们可以通过运算符重载的方式定义两个图形比较大小的规则,代码如下所示。
import Foundation

protocol Shape {
    var area: Double { get }
    var perimeter: Double { get }
}

// 为遵循Shape协议的类型重载<运算符
func <(a: Shape, b: Shape) -> Bool {
    return a.area < b.area
}

/**圆*/
class Circle : Shape {
    var radius: Double

    init(radius: Double) {
        self.radius = radius
    }

    var area: Double {
        return M_PI * radius * radius
    }

    var perimeter: Double {
        return 2 * M_PI * radius
    }
}

/**矩形*/
class Rectangle: Shape {
    var width, height: Double

    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double {
        return width * height
    }

    var perimeter: Double {
        return (width + height) * 2
    }
}

var shapeArray:[Shape] = [Circle(radius: 3), Rectangle(width: 5, height: 6), Circle(radius: 2), Rectangle(width: 2, height: 8)]

shapeArray.sortInPlace(<);

// 按面积从小到大输出图形的面积和周长
for currentShape in shapeArray {
    print("面积: \\(currentShape.area)")
    print("周长: \\(currentShape.perimeter)")
}
  • 下标运算(subscript):就是对一个东西通过索引,快速取值的一种语法,例如数组的 a[0]。这就是一个下标脚本。通过索引 0 来快速取值。在 Swift 中,我们可以对类(Class)、结构体(structure)和枚举(enumeration)中自己定义下标脚本的语法。

重点
下标脚本使用 get、set 来定义读、写属性,并不需要 2 个属性都有,可以只读,并且读必须有。
定义 set 属性时,传入的参数默认名称为 newValue。并且 newValue 的类型和 subscript 函数返回值相同。

  • 访问修饰符
    • private 私有,只有当前源文件中能够访问。
    • internal 默认的访问修饰符,除了当前源文件中的代码,同一个应用程序或者同一个库文件中的其他代码也能访问。
    • public 可以在任何地方被访问。

面向协议编程(POP)

协议

protocol 协议名[: 父协议1, 父协议2, ...] {
    // 方法的集合(计算属性相当于就是方法)
}
  1. 能力 - 遵循了协议就意味着具备了某种能力
  2. 约定 - 遵循了协议就一定要实现协议中的方法
  3. 角色 - 一个类可以遵循多个协议, 一个协议可以被多个类遵循, 遵循协议就意味着扮演了某种角色, 遵循多个协议就意味着可以扮演多种角色

依赖倒转原则

  1. 声明变量的类型时应该尽可能使用协议类型
  2. 声明方法参数类型时应该尽可能使用协议类型
  3. 声明方法返回类型时应该尽可能使用协议类型

用协议实现委托回调

一个对象想做某件事情但是自身没有能力做这件事情就可以使用委托回调,具体的步骤是:

  1. 设计一个协议,让被委托方遵循协议并实现协议中的方法
  2. 委托方有一个属性是协议类型的,通过该属性可以调用协议中的方法

注意:委托方的协议类型的属性通常是可空类型,因为要写成弱引用(weak)。

  • 委托回调
    1 设计一个协议(被委托方必须要遵循协议才能给别的对象当委托)
protocol ExamDelegate: class {

    func answerTheQuestion()
}

class Student {
    var name: String
    weak var delegate: ExamDelegate?//2委托方添加一个属性其类型是遵循了协议的被委托方
    init(name: String) {
        self.name = name
    }
    func joinExam() {
        print("姓名: \\(name)")
        delegate?.answerTheQuestion()
    }
}


3自己做不了的事情委托给别的对象来做

class Gunman: ExamDelegate {//4让类遵循协议成为被委托方(协议表能力)
    func answerTheQuestion() {//5 遵循协议就必须要实现协议中的方法(协议表约定)
        print("奋笔疾书各种答案")
    }
}

6它遵循了协议所以有充当委托的能力也就是说可以扮演被委托方的角色

let stu = LazyStudent(name: "老王")
let gun = Gunman()
stu.delegate = gun
stu.joinExam()

运行结果

屏幕快照 2016-08-19 下午5.49.57.png
  • 代理模式
protocol ExamCandidate: class {
    
    func answerTheQuestion()
}

class LazyStudent: ExamCandidate {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func answerTheQuestion() {
    }
}

class Gunman: ExamCandidate {
    var name: String
    var target: LazyStudent?
    
    init(name: String) {
        self.name = name
    }
    
    func answerTheQuestion() {
        if let stu = target {
            print("姓名: \\(stu.name)")
            print("奋笔疾书答案")
            print("提交试卷")
        }
    }
}

let stu = LazyStudent(name: "狒狒")
let gun = Gunman(name: "小强")
gun.target = stu
gun.answerTheQuestion()

其他

  • 协议组合:protocol<协议1, 协议2, ...>
let array: [protocol<Flyable, Fightable>] = [
    Superman()
]

for obj in array {
    obj.fly()
    obj.fight()
}
  • 可选方法
  • 协议扩展:
    • 可以在协议扩展中给协议中的方法提供默认实现
      也就是说如果某个类遵循了协议但是没有实现这个方法就直接使用默认实现
      那么这个方法也就相当于是一个可选方法(可以实现也可以不实现)
extension Fightable {
    func fight() {
        print("正在打架")
    }
}

泛型

让类型不再是程序中的硬代码(hard code),可以设计出更通用的代码。

  • 泛型函数
    • 通常在泛型函数中,Swift 允许你定义你自己的泛型类型。(下面有相应的例子)
  • 泛型类/结构/枚举
    • 泛型 (generic) - 让类型不再是程序中的硬代码, Swift中的类、结构和枚举都可以使用泛型

相关知识

  • 泛型限定
 泛型限定<T: Comparable>限定T类型必须是遵循了Comparable协议的类型

定义一个虚拟类型T, 调用函数时根据传入的参数类型来决定T到底是什么

func myMin<T: Comparable>(a: T, _ b: T) -> T {//自定义泛型函数
    return a < b ? a : b
}
class Student: Comparable {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

func ==(one: Student, two: Student) -> Bool {
    return one.name == two.name
}

func <(one: Student, two: Student) -> Bool {
    return one.name < two.name
}

func <=(one: Student, two: Student) -> Bool {
    return one.name <= two.name
}

func >(one: Student, two: Student) -> Bool {
    return one.name > two.name
}

func >=(one: Student, two: Student) -> Bool {
    return one.name >= two.name
}

var stu1 = Student(name: "小强", age: 35)
var stu2 = Student(name: "狒狒", age: 18)

let minStu = myMin (stu1,stu2)
print(minStu.age)
  • where子句

错误处理

以分数为例

定义一个遵循ErrorType协议的枚举
通过不同的case定义程序中可能出现的若干种异常状况

enum FractionError: ErrorType {
    case ZeroDenominator    // 分母为0
    case DivideByZero       // 除以0
}
class Fraction {
    private var _num: Int
    private var _den: Int
    
    var info: String {
        get {
            return _num == 0 || _den == 1 ? "\\(_num)" : "\\(_num)/\\(_den)"
        }
    }
    
    // 如果一个方法抛出了异常 那么在声明方法时必须要写上throws关键字
    // throws关键字是提醒方法的调用者方法可能会出状况 调用时要写try
    init(num: Int, den: Int) throws {
        _num = num
        _den = den
        if _den == 0 {
            // 如果程序中出现问题就抛出错误(异常)
            // 被throw关键字抛出的必须是遵循ErrorType协议的东西
            throw FractionError.ZeroDenominator
        }
        else {
            simplify()
            normalize()
        }
    }
    
    func add(other: Fraction) -> Fraction {
        // 如果能够确保方法调用时不出异常那么可以在try关键字后加!
        // 这样就可以在不写do...catch的情况下调用可能出状况的方法
        return try! Fraction(num: _num * other._den + other._num * _den, den: _den * other._den)
    }
    
    func sub(other: Fraction) -> Fraction {
        return try! Fraction(num: _num * other._den - other._num * _den, den: _den * other._den)
    }
    
    func mul(other: Fraction) -> Fraction {
        return try! Fraction(num: _num * other._num, den: _den * other._den)
    }
    
    func div(other: Fraction) throws -> Fraction {
        if other._num == 0 {
            throw FractionError.DivideByZero
        }
        return try! Fraction(num: _num * other._den, den: _den * other._num)
    }
    
    func normalize() -> Fraction {
        if _den < 0 {
            _num = -_num
            _den = -_den
        }
        return self
    }
    
    func simplify() -> Fraction {
        if _num == 0 {
            _den = 1
        }
        else {
            let x = abs(_num)
            let y = abs(_den)
            let g = gcd(x, y)
            _num /= g
            _den /= g
        }
        return self
    }
}

运算符重载(为自定义的类型定义运算符)

func +(one: Fraction, two: Fraction) -> Fraction {
    return one.add(two)
}

func -(one: Fraction, two: Fraction) -> Fraction {
    return one.sub(two)
}

func *(one: Fraction, two: Fraction) -> Fraction {
    return one.mul(two)
}

func /(one: Fraction, two: Fraction) throws -> Fraction {
    return try one.div(two)
}

如果能够保证代码不出错可以在try后面加!
如果不确定代码是否出错可以在try后面加?
需要注意的是有?的地方会产生Optional(可空类型)
稍后可能还需要对可空类型进行拆封, 拆封方式有二:

  1. 不安全的做法: xxx!
  2. 安全的做法: 用if let = xxx { }进行拆封

func foo() {
    
    let f1 = try? Fraction(num: 3, den: 0)
    let f2 = try? Fraction(num: 0, den: 9)
    
    if let a = f1, b = f2 {
        let f3 = a + b
        print(f3.info)
    }
    else {
        print("无效的分数无法进行加法运算")
    }
}

foo()

对于可能出状况的代码要放在do...catch中执行
在可能出状况的方法前还要写上try表示尝试着执行
如果在do中没有出现任何状况那么catch就不会执行
如果do中出现了状况代码就不会再向下继续执行而是转移到catch中
在do的后面可以跟上多个catch用于捕获不同的异常状况 但是最多只有一个catch会被执行

do {
    let f1 = try Fraction(num: 3, den: 4)
    let f2 = try Fraction(num: 0, den: 9)

    print(f1.info)
    print(f2.info)

    let f3 = f1 + f2
    print(f3.info)
    let f4 = f1 - f2
    print(f4.info)
    let f5 = f1 * f2
    print(f5.info)
    let f6 = try f1 / f2
    print(f6.info)
}
catch FractionError.ZeroDenominator {
    print("分母不能为0!!!")
}
```Swift
enum MyError: ErrorType {
    case A
    case B
    case C
}

以上内容包含了一下几点的用法

  • throw
  • throws / rethrows
  • do
  • catch
  • try

边角知识

  • ARC:
    Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Swift 会替我们管理和分配内存。而释放的原则遵循了自动引用计数 (ARC) 的规则:当一个对象没有引用的时候,其内存将会被自动回收。这套机制从很大程度上简化了我们的编码,我们只需要保证在合适的时候将引用置空 (比如超过作用域,或者手动设为 nil 等)

注意如果程序中出现了类与类之间双向关联关系 必须要将其中一端设置为weak引用 否则将会形成循环引用导致ARC无法释放内存

  • 推荐使用
    • 如果允许使用可空类型通常使用weak来破除循环引用
    • 如果员工关联的部门对象被释放了那么dept会被赋值为nil
    • 如果要继续给dept对象发消息程序不会崩溃
  • 谨慎使用
    • 如果不允许使用可空类型就必须使用unowned来破除循环引用
    • 需要注意的是如果员工对象关联的部门对象被释放了
    • 如果还要通过员工对象去操作它所关联的部门对象将导致程序崩溃(EXC_BAD_ACCESS)

员工与部门为例

class Emp {
weak var dept: Dept?//weak 破除循环
init(dept: Dept) {
        print("创建一个员工")
        self.dept = dept
    }
    deinit { //在销毁对象的时候调用
        print("销毁一个员工")
    }
}

class Dept {
    var manager: Emp?
    
    init() {
        print("创建一个部门")
    }

    deinit {
        print("销毁一个部门")
    }
}

func bar() {
    let dept = Dept()
    let emp = Emp(dept: dept)
    dept.manager = emp
}

bar()
  • 正则表达式
  • 嵌套类型
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,565评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,021评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,003评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,015评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,020评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,856评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,178评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,824评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,264评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,788评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,913评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,535评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,130评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,102评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,334评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,298评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,622评论 2 343

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,867评论 6 13
  • 我会做我自己的镜子,真的成为镜中人后,还希望可以做你的镜子。 2017年9月12日 星期二 晴 我平时很少照镜子,...
    默默喜欢你阅读 1,083评论 1 3
  • —1— 一个人伤心的时候,说什么话都觉得很伤心,看到的也都是绝望眼泪。 好朋友对我说,她听过最伤心的话是:咱们分手...
    卞林骢阅读 403评论 0 1
  • 今天周五啊,白天做了一天实验。用超声波清洗仪清洗水稻种子,去别人实验室用仪器查参数,发现外国人都很nice,都会特...
    最佳姐妹阅读 232评论 0 0
  • 正则表达式是用于描述字符排列和匹配模式的一种语法规则。 它主要用于字符串的模式分割、匹配、查找及替换操作。 正则表...
    随玉而安_gao阅读 237评论 0 0