学习Swift一个月总结
个人感悟:一个月的学习,学了很多内容,但是并没能很好的消化,所以说现在脑子里还是一片混乱,不能很好的将学习过的东西应用在编写程序上,所以说必须要一遍遍的去敲代码练习,fighting!
语言基础
程序是指令的集合,写程序就是写一系列的指令去控制计算机做我们想做的事情。
编译:将程序设计语言转换成计算机能够理解的机器语言或者某种中间代码的过程。
冯诺依曼体系结构的计算机:
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}、"\t"
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
// IT从业人员等级评定
print("请输入你的月薪: ", terminator: "")
let salary = inputInt()
if salary >= 0 {
switch salary {
case 0:
print("你是一个无业游民")
case 1...30000:
print("你是一个小码畜")
case 30001...60000:
print("你是一个小码奴")
case 60001...100000:
print("你是一个大码农")
case 100001...200000:
print("你是一个光荣的IT民工")
case 200001...500000:
print("你是一个优秀的IT工程师")
case 500001..<10000000:
print("你是IT精英")
case 10000000..<100000000:
print("你是IT大哥, 我服")
default:
print("你是头上有光环的IT领袖, 求带")
}
}
else {
print("你丫有病!!!")
}
循环
- 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)
// 冒泡排序:两两比较,前面的元素比后面的元素大就交换位置
var array = [98, 29, 35, 12, 47, 66, 53, 79]
for i in 0..<array.count - 1 {
var swapped = false
for j in 0..<array.count - 1 - i {
if array[j] > array[j + 1] {
(array[j], array[j + 1]) = (array[j + 1], array[j])
}
}
if !swapped {
break
}
}
print(array)
// 简单选择排序:每次从剩下的元素中找最小的元素放到对应位置
var array = [98, 29, 35, 12, 47, 66, 53, 79]
for i in 0..<array.count - 1 {//比较次数
var minIndex = i
for j in i + 1..<array.count {
if array[j] < array[minIndex] {
minIndex = j
}
}
(array[i], array[minIndex]) = (array[minIndex], array[i])
}
print(array)
//1-10000之间所有的完美数
func cube(n: Int) -> Int {
return n * n * n
}
for i in 100...999 {
let a = i % 10 // 个位
let b = i / 10 % 10 // 十位
let c = i / 100 // 百位
if i == cube(a) + cube(b) + cube(c) {
print(i)
}
}
穷举法:穷尽所有可能性直到找到正确答案。
下面的程序实现了"百钱百鸡"
for x in 0...20 {
for y in 0...33 {
let z = 100 - x - y
if 5 * x + y * 3 + 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
}
var money = 1000
var needsGoOn = false
repeat{
print("资产:\(money)元")
var debt: Int
repeat{
print("请下注:", terminator: "")
debt = inputInt()
} while debt <= 0 || debt > money
let firstPoint = roll() + roll()
print("玩家摇出了\(firstPoint)点")
switch firstPoint {
case 7, 11:
money += debt
print("玩家胜")
case 2, 3, 12:
money -= debt
print("庄家胜")
default:
needsGoOn = true
}
while needsGoOn {
let currentPoint = roll() + roll()
print("玩家摇出了\(currentPoint)点")
if currentPoint == 7 {
money -= debt
print("庄家胜")
needsGoOn = false
}
else if currentPoint == firstPoint {
money += debt
print("玩家胜")
needsGoOn = false
}
}
} while money > 0
print("你boom了")
容器
数组
数组是使用连续的内存空间保存多个同类型的元素的容器,因为数组中的元素在内存中是连续的,所以可以使用下标
- 创建数组
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])
}
2.方式2:
for temp in array3 {
print(temp)
}
for temp in array3[1...3] {
print(temp)
}
说明:for-in循环是一个只读循环,这也就意味着在循环的过程中不能对数组中的元素进行修改
3.方式3:
for (i, temp) in array3.enumerate() {
if i == 0 {
array3[i] = 1
}
print("\(i). \(temp)")
}
提醒:操作数组时最重要的式不要越界访问元素。数组对象的count属性表明了数组中有多少个元素,那么有效的索引(下标)范围是0到count-1
数组中的元素也可以是数组,因此我们可以构造多维数组。最常见的是二维数组,它相当于是一个有行有列的数组,数组中的每个元素代表一行,该数组中的每个元素代表行里面的列。二维数组可以模拟现实世界中的表格、数学上的矩阵、棋牌类游戏的棋盘、格子类游戏的地图(植物大战僵尸),所以在实际开发中,使用非常广泛。
下面的程序是用二维数组模拟表格的例子。
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)
- 交集(a和b都有的元素):
print(a.intersect(b)) // 输出为:[5, 3]
- 并集(a和b的所有元素):
print(a.union(b)) // 输出为:[2, 9, 5, 7, 3, 1, 11]
- 差集(a有b没有的元素):
print(a.subtract(b)) // 输出为:[2, 1]
字典
字典是以键值对的方式保存数据的容器,字典中的每个元素都是键值对组合,通过键可以找到对应的值。
- 创建字典
let dict: [Int: String] = [
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 (key, value) in dict {
print("\(key) ---> \(value)")
}
重要操作
- 排序
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 newArray1 = 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
- inout参数
~inout - 输入输出参数(不仅将数据传入函数还要从函数中取出数据)
~inout类型的参数前要加上&符号
func swap(inout a: Int, inout _ b: Int) -> Void {
(a, b) = (b, a)
// let temp = a
// a = b
// b = temp
}
var a = 300, b = 500
swap(&a, &b)
print("a = \(a)") // 输出为:a = 500
print("b = \(b)") // 输出为:b = 300
func createX(inout x: Int) {
x = 1000
}
var x = 1
createX(&x)
print(x) // 输出为: 1000
- 可变参数列表
func sum(nums: Int...) -> Int {
var total = 0
for num in nums {
total += num
}
return total
}
print(sum()) // 输出为: 0
print(sum(999)) // 输出为:999
print(sum(1, 2, 3)) // 输出为:6
print(sum(90, 82, 37, 68, 55, 11, 99)) // 输出为:442
闭包就是没有名字的函数或者称之为函数表达式(Lambda表达式),Objectiv-C中与之对应的概念叫block。如果一个函数的参数类型是函数我们可以传入一个闭包;如果一个函数的返回类型是函数我们可以返回一个闭包;如果一个类的某个属性是函数我们也可以讲一个闭包表达式赋值给它。
{([参数列表]) [-> 返回类型] in 代码 }
面向对象编程(OOP)
基本概念
对象:接收消息的单元,对象是一个具体的的概念。
类:对象的蓝图和模板,累世一个抽象概念。
消息:对象之间通信的方式,通过给对象发消息可以让对象执行对应的操作来解决问题。
四大支柱
抽象:定义类的过程就是一个抽象的过程,需要做数据抽象和行为抽象,数据抽象找到对象的属性(保存对象状态的存储属性),行为抽象找到对象的方法(可以给对象发消息)。
封装:
- 观点1:我们在类中写方法其实就是在封装API,方法的内部实现可能会很复杂,但是这些对调用者来说是不可见的,调用只能看到方法有一个简单清晰的接口。
- 观点2:将对象的属性和操作这些属性的方法绑定在一起。
- 观点3:隐藏一切可以隐藏的实现细节,只提供简单清晰的接口(界面)。
继承:从已有的类创建新类的过程称为继承
多态:同样的对象类型接收相同的消息(调用相同的方法),但是做了不同的事情 这就是多态(polymorphism)
- 实现多态的关键步骤:
①方法重写(子类在继承父类的过程中对父类已有的方法进行重写, 而且不同的子类给出各自不同的实现版本)
②对象造型(将子类对象当成父类型来使用)
~可以通过if+as?将父类型安全的转换成子类型然后再调用子类特有方法
三个步骤
- 定义类
- 数据抽象
- 存储属性
- 行为抽象
- 方法(写到类里面的函数就是方法或者说跟对象绑定的行为)
- 对象方法:给对象法的消息要先创建对象才能调用,与对象状态有关
- 类方法:给类法的消息所以不用创建对象直接通过类名调用,与对象的状态无关的方法 方法前加 static或class,作用相同
- 方法(写到类里面的函数就是方法或者说跟对象绑定的行为)
- 构造器
- 指派构造器
- 便利构造器(convenience)
- 必要构造器(required)
- 创建对象
- 给对象发消息
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
}
}
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("无法创建三角形")
}
相关内容
- 枚举
~枚举是定义符号常量的最佳方式
~符号常量总是优于字面常量
~示例(枚举和文档注释的应用):
/**
花色的枚举
- Spade: 黑桃
- Heart: 红心
- Club: 草花
- Diamond: 方块
*/
enum Suite: String {
case Spade = "♠️"
case Heart = "❤️"
case Club = "♣️"
case Diamond = "♦️"
}
/// 一张牌
class Card {
var suite: Suite
var face: Int
/**
初始化方法
- parameter suite: 花色
- parameter face: 点数
*/
init(suite: Suite, face: Int) {
self.suite = suite
self.face = face
}
/// 牌的信息
var info: String {
get {
var str = suite.rawValue
switch face {
case 1: str += "A"
case 11: str += "J"
case 12: str += "Q"
case 13: str += "K"
default: str += "\(face)"
}
return str
}
}
}
- 结构(体)
区别1: 结构的对象是值类型, 类的对象是引用类型
值类型在赋值的时候会在内存中进行对象的拷贝
引用类型在赋值的时候不会进行对象拷贝只是增加了一个引用
结论: 我们自定义新类型时优先考虑使用类而不是结构除非我们要定义的是一种底层的数据结构(保存其他数据的类型)
区别2: 结构会自动生成初始化方法
区别3: 结构中的方法在默认情况下是不允许修改结构中的属性除非加上mutating关键字
总结:类和结构的区别到底有哪些?什么时候应该使用结构?什么时候应该使用类?
- 扩展(extension)
~如果在某个特定的应用场景中你发现现有的类缺少了某项功能
那么可以通过类扩展(extension)的方式现场添加这项功能 - 运算符重载 (要写在类的外面)(为自定义的类型定义运算符)
class Fraction {
private var _num: Int
private var _den: Int
var info: String {
get {
return _num == 0 || _den == 1 ? "\(_num)" : "\(_num)/\(_den)"
}
}
init(num: Int, den: Int) {
_num = num
_den = den
simplify()
normalize()
}
func add(other: Fraction) -> Fraction {
return Fraction(num: _num * other._den + other._num * _den, den: _den * other._den)
}
func sub(other: Fraction) -> Fraction {
return Fraction(num: _num * other._den - other._num * _den, den: _den * other._den)
}
func mul(other: Fraction) -> Fraction {
return Fraction(num: _num * other._num, den: _den * other._den)
}
func div(other: Fraction) -> Fraction {
return 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) -> Fraction {
return one.div(two)
}
-
下标运算(subscript)
-
访问修饰符
- private
- internal
- public
面向协议编程(POP)
协议
protocol 协议名[:父协议1,父协议2,...] {
// 方法的集合(计算属性相当于就是方法)
}
1.能力: 遵循了协议就意味着具备了某种能力
2.约定: 遵循了协议就一定要实现协议中的方法
3.角色: 一个类可以遵循多个协议, 一个协议可以被多个类遵循, 遵循协议就意味着扮演了某种角色, 遵循多个协议就意味着可以扮演多种角色
注意:Swift中的继承是单一继承(一个类只能有一个父类), 如果希望让一个类具备多重能力可以使用协议来实现(C++里面是通过多重继承来实现的, 这是一种非常狗血的做法)
依赖倒转原则
依赖倒转原则(面向协议编程)
- 声明变量的类型时应该尽可能使用协议类型
- 声明方法参数类型时应该尽可能使用协议类型
- 声明方法返回类型时应该尽可能使用协议类型
用协议实现委托回调
一个对象想做某件事但是自身没有能力做这件事,就可以使用委托回调,具体步骤是:
- 设计一个协议,让被委托方遵循协议并实现协议中的方法
- 委托方有一个属性是协议类型的,通过该属性可以调用协议中的方法
示例:五子棋App
import UIKitA
// RenjuBoard.swift
/**
棋盘交叉点的状态
- Space: 空格
- Black: 黑棋
- White: 白棋
*/
enum PointState {
case Space, Black, White
}
/// 棋盘
class RenjuBoard {
var board: [[PointState]]
var isBlackTurn = true
var isGameOver = false
init() {
board = [[PointState]](count: 15, repeatedValue: [PointState](count: 15, repeatedValue: .Space))
}
// 索引器语法 - 可以直接对棋盘对象做下标运算来放置棋子
subscript(row: Int, col: Int) -> Bool {
get { return board[row][col] == .Space }
set(isBlack) {
if board[row][col] == .Space {
board[row][col] = isBlack ? .Black : .White
isBlackTurn = !isBlackTurn
}
}
}
func reset() {
isGameOver = false
isBlackTurn = true
for i in 0..<board.count {
for j in 0..<board[i].count {
board[i][j] = .Space
}
}
}
func judge(row: Int, _ col: Int) -> Bool {
return _judgeH(row, col) || _judgeV(row, col) || _judgeX1(row, col) || _judgeX2(row, col)
}
private func _judgeH(row: Int, _ col: Int) -> Bool {
var counter = 1
var currentCol = col - 1
while currentCol >= 0 {
if board[row][currentCol] == board[row][col] {
counter += 1
currentCol -= 1
}
else {
break
}
}
currentCol = col + 1
while currentCol < board.count {
if board[row][currentCol] == board[row][col] {
counter += 1
currentCol += 1
}
else {
break
}
}
return counter >= 5
}
private func _judgeV(row: Int, _ col: Int) -> Bool {
var counter = 1
var currentRow = row - 1
while currentRow >= 0 {
if board[currentRow][col] == board[row][col] {
counter += 1
currentRow -= 1
}
else {
break
}
}
currentRow = row + 1
while currentRow < board.count {
if board[currentRow][col] == board[row][col] {
counter += 1
currentRow += 1
}
else {
break
}
}
return counter >= 5
}
private func _judgeX1(row: Int, _ col: Int) -> Bool {
var counter = 1
var currentRow = row - 1
var currentCol = col - 1
while currentRow >= 0 && currentCol > 0 {
if board[currentRow][currentCol] == board[row][col] {
counter += 1
currentRow -= 1
currentCol -= 1
}
else {
break
}
}
currentRow = row + 1
currentCol = col + 1
while currentRow < board.count && currentCol < board.count {
if board[currentRow][currentCol] == board[row][col] {
counter += 1
currentRow += 1
currentCol += 1
}
else {
break
}
}
return counter >= 5
}
private func _judgeX2(row: Int, _ col: Int) -> Bool {
var counter = 1
var currentRow = row - 1
var currentCol = col + 1
while currentRow >= 0 && currentCol < board.count {
if board[currentRow][currentCol] == board[row][col] {
counter += 1
currentRow -= 1
currentCol += 1
}
else {
break
}
}
currentRow = row + 1
currentCol = col - 1
while currentRow < board.count && currentCol >= 0 {
if board[currentRow][currentCol] == board[row][col] {
counter += 1
currentRow += 1
currentCol -= 1
}
else {
break
}
}
return counter >= 5
}
func draw() {
let lineBP = UIBezierPath()
// 绘制15条横线和15条竖线来构造一个棋盘
for i in 0..<board.count {
lineBP.moveToPoint(CGPointMake(10, 10 + 50 * CGFloat(i)))
lineBP.addLineToPoint(CGPointMake(710, 10 + 50 * CGFloat(i)))
lineBP.moveToPoint(CGPointMake(10 + 50 * CGFloat(i), 10))
lineBP.addLineToPoint(CGPointMake(10 + 50 * CGFloat(i), 710))
}
lineBP.stroke()
// 绘制棋盘的边框
let rectBP = UIBezierPath(rect: CGRectMake(3, 3, 714, 714))
rectBP.lineWidth = 6
rectBP.stroke()
// 绘制天元和星
let starsRectArray = [
CGRectMake(155, 155, 10, 10),
CGRectMake(555, 155, 10, 10),
CGRectMake(155, 555, 10, 10),
CGRectMake(555, 555, 10, 10),
CGRectMake(355, 355, 10, 10)
]
for starRect in starsRectArray {
let ovalBP = UIBezierPath(ovalInRect: starRect)
ovalBP.fill()
}
// 绘制棋盘上的棋子
for i in 0..<board.count {
for j in 0..<board[i].count {
if board[i][j] != .Space {
let ovalBP = UIBezierPath(ovalInRect: CGRectMake(-10 + CGFloat(j) * 50, -10 + CGFloat(i) * 50, 40, 40))
(board[i][j] == .Black ? UIColor.blackColor() : UIColor.whiteColor()).set()
ovalBP.fill()
}
}
}
}
}
// Canvas.swift
// 有的时候某个对象要做某件事情但其自身又没有能力做这件事情
// 这个时候就可以使用委托回调的编程模式让别的对象来做这件事情
// 实现委托回调的编程模式有以下几个步骤:
// 1. 设计一个协议(被委托方必须要遵循协议才能给别的对象当委托)
protocol CanvasDelegate: class {
// 协议里面的方法就是要委托其他对象做的事情
func showMessage(canvas: Canvas, message: String)
}
class Canvas: UIView {
// 2. 委托方添加一个属性其类型是遵循了协议的被委托方
weak var delegate: CanvasDelegate?
var renjuBoard = RenjuBoard()
var isAutoMode = false
func clearBoard() {
renjuBoard.reset()
setNeedsDisplay()
}
func randomMove() {
let row = Int(arc4random_uniform(15))
let col = Int(arc4random_uniform(15))
if renjuBoard[row, col] {
renjuBoard[row, col] = renjuBoard.isBlackTurn
setNeedsDisplay()
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Swift 2中的guard大法, Swift 3中据说要废掉
guard !isAutoMode else { return }
// guard !renjuBoard.isGameOver else { return }
if !renjuBoard.isGameOver {
if let touch = touches.first {
let point = touch.locationInView(self)
let row = lround(Double(point.y - 10) / 50)
let col = lround(Double(point.x - 10) / 50)
if renjuBoard[row, col] {
renjuBoard[row, col] = renjuBoard.isBlackTurn
setNeedsDisplay()
if renjuBoard.judge(row, col) {
renjuBoard.isGameOver = true
// 3. 自己做不了的事情委托给别的对象来做
delegate?.showMessage(self, message: renjuBoard.isBlackTurn ? "白棋胜" : "黑棋胜")
}
}
}
}
}
override func drawRect(rect: CGRect) {
renjuBoard.draw()
}
}
// ViewController.swift
// 4. 让视图控制器遵循协议成为被委托方(协议表能力)
class ViewController: UIViewController, CanvasDelegate {
var timer: NSTimer?
var canvas: Canvas!
override func viewDidLoad() {
super.viewDidLoad()
canvas = Canvas(frame: CGRectMake(0, 0, 720, 720))
// canvas.isAutoMode = true
// 6. 给画布对象绑定委托(self就是视图控制器对象它遵循了协议所以有充当委托的能力也就是说可以扮演被委托方的角色)
canvas.delegate = self
canvas.center = self.view.center
canvas.backgroundColor = UIColor(red: 254.0 / 255.0, green: 209.0 / 255.0, blue: 46.0 / 255.0, alpha: 1)
self.view.addSubview(canvas)
// timer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: canvas, selector: "randomMove", userInfo: nil, repeats: true)
}
// 5. 遵循协议就必须要实现协议中的方法(协议表约定)
func showMessage(canvas: Canvas, message: String) {
let alertController = UIAlertController(title: message, message: "", preferredStyle: .Alert)
let okAction = UIAlertAction(title: "确定", style: .Default) { action in
// 此处通过尾随闭包来定义点击确定按钮后要做什么
canvas.clearBoard()
}
alertController.addAction(okAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
// deinit在销毁对象的时候调用
deinit {
// 销毁计时器
timer?.invalidate()
}
}
注意:委托方的协议类型的属性通常是可空类型,因为要写成弱引用(weak)。
其他
- 协议组合: protocol<协议1, 协议2, ...>
- 可选方法
- 协议扩展:对协议中的方法给出默认实现,也就是说如果某个类遵循了协议但是没有实现这个方法就直接使用默认实现,那么这个方法也就相当于是一个可选方法(可以实现也可以不实现)
- 协议中全是抽象概念(只有声明没有实现) 遵循协议的类可以各自对协议中的计算属性和方法给出自己的实现版本 这样当我们面向协议编程时就可以把多态的优势发挥到淋漓尽致 可以写出更通用更灵活的代码(符合开闭原则)
- 实现开闭原则最关键有两点:
- 抽象是关键(在设计系统的时候一定要设计好的协议);
- 封装可变性(桥梁模式 - 将不同的可变因素封装到不同的继承结构中)
- 接口(协议)隔离原则: 协议的设计要小而专不要大而全,协议的设计也要高度内聚
- 示例:打折策略协议
import Foundation
/**
* 打折策略协议
*/
protocol DiscountStrategy {
/**
计算折扣
- parameter price: 原价
- returns: 折扣的金额
*/
func discount(price: Double) -> Double
}
/// 百分比折扣策略
class PercentageDiscount: DiscountStrategy {
var percentage: Double
init(percentage: Double) {
self.percentage = percentage
}
func discount(price: Double) -> Double {
return price * (1 - percentage)
}
}
// 固定金额折扣策略
class FixedDiscount: DiscountStrategy {
var fixedMoney: Double
init(fixedMoney: Double) {
self.fixedMoney = fixedMoney
}
func discount(price: Double) -> Double {
return price >= fixedMoney ? fixedMoney : 0
}
}
// 分段折后策略
class SegmentedDiscount: DiscountStrategy {
func discount(price: Double) -> Double {
if price < 20 {
return 0
}
else if price < 50 {
return 3
}
else if price < 100 {
return 10
}
else {
return 30
}
}
}
/// 图书
class Book {
var name: String
var price: Double
var type: String
// 四人帮设计模式 - 策略模式
var strategy: DiscountStrategy?
/**
初始化方法
- parameter name: 书名
- parameter price: 价格
- parameter type: 类型
*/
init(name: String, price: Double, type: String) {
self.name = name
self.price = price
self.type = type
}
/// 减多少钱
var discountValue: Double {
get {
if let s = strategy {
return s.discount(price)
}
else {
return 0
}
}
}
/// 折后价格
var discountedPrice: Double {
get { return price - discountValue }
}
}
let booksArray = [
Book(name: "C语言程序设计", price: 24.0, type: "计算机"),
Book(name: "名侦探柯南", price: 98.5, type: "漫画"),
Book(name: "Swift从入门到住院", price: 35.8, type: "计算机"),
Book(name: "黄冈数学密卷", price: 34.2, type: "教材"),
Book(name: "中国股市探秘", price: 58.5, type: "金融")
]
let discountDict: [String: DiscountStrategy] = [
"计算机": PercentageDiscount(percentage: 0.78),
"教材": PercentageDiscount(percentage: 0.85),
"漫画": SegmentedDiscount(),
"科普": FixedDiscount(fixedMoney: 2)
]
var totalPrice = 0.0
var totalDiscount = 0.0
for book in booksArray {
if let strategy = discountDict[book.type] {
book.strategy = strategy
}
print("《\(book.name)》原价: ¥\(book.price)元")
print("《\(book.name)》折后价: ¥\(book.discountedPrice)元")
totalPrice += book.discountedPrice
totalDiscount += book.discountValue
}
print(String(format: "总计: ¥%.1f元", totalPrice))
print(String(format: "为您节省了: ¥%.1f元", totalDiscount))
泛型
让类型不再是程序中的硬代码(hard code),可以设计出更通用的代码。
- 泛型函数
通过示例来理解泛型函数
import Foundation
// 泛型 (generic) - 让类型不再是程序中的硬代码(写死的东西)
func bubbleSort<T: Comparable>(array: [T]) -> [T] {
var newArray = array
for i in 0..<newArray.count - 1 {
var swapped = false
for j in 0..<newArray.count - 1 - i {
if newArray[j] > newArray[j + 1] {
mySwap(&newArray[j], &newArray[j + 1])
swapped = true
}
}
if !swapped {
break
}
}
return newArray
}
// 定义一个虚拟类型T, 调用函数时根据传入的参数类型来决定T到底是什么
func mySwap<T>(inout a: T, inout _ b: T) {
let temp = a
a = b
b = temp
}
// 泛型限定
// <T: Comparable>限定T类型必须是遵循了Comparable协议的类型
func myMin<T: Comparable>(a: T, _ b: T) -> T {
return a < b ? a : b
}
let array1: Array<Int> = [23, 45, 99, 12, 68, 51, 70, 66]
let array2 = bubbleSort(array1)
print(array1)
print(array2)
let array3 = ["hello", "zoo", "kiss", "apple", "good"]
let array4 = bubbleSort(array3)
print(array3)
print(array4)
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: "Luo Hao", age: 35)
var stu2 = Student(name: "Wang Dachui", age: 18)
let minStu = myMin(stu1, stu2)
print(minStu.name)
var x = "hello", y = "good"
mySwap(&x, &y)
print(x, y)
print(myMin(x, y))
var a = 3.5, b = 1.2345
mySwap(&a, &b)
print(a, b)
print(myMin(a, b))
var c = 10, d = 100
mySwap(&c, &d)
print(c, d)
print(myMin(c, d))
// Swift中的类、结构和枚举都可以使用泛型
struct Stack<T> {
var data: [T] = []
// 入栈
mutating func push(elem: T) {
data.append(elem)
}
// 出栈
mutating func pop() -> T {
return data.removeLast()
}
var isEmpty: Bool {
get { return data.count == 0 }
}
}
var stack = Stack<String>()
stack.push("hello")
stack.push("good")
stack.push("zoo")
while !stack.isEmpty {
print(stack.pop())
}
- 泛型类/结构/枚举
相关知识
- 泛型限定
- where子句
错误处理
enum MyError: ErrorType {
case A
case B
case C
}
- throw
- throws / rethrows
- do
- catch
- try
示例:
// 定义一个遵循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)
}
func foo() {
// 如果能够保证代码不出错可以在try后面加!
// 如果不确定代码是否出错可以在try后面加?
// 需要注意的是有?的地方会产生Optional(可空类型)
// 稍后可能还需要对可空类型进行拆封, 拆封方式有二:
// 1. 不安全的做法: xxx!
// 2. 安全的做法: 用if let = xxx { }进行拆封
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!!!")
}
catch FractionError.DivideByZero {
print("卵球了, 除以0是不行的!!!")
}
catch {
print("出错了! 我也不知道什么问题")
}
边角知识
-
ARC
通过一个示例来理解ARC:
import Foundation
class GrandFather {
}
class Father: GrandFather {
}
class Son: Father {
override init() {
// 可以调用Father中的初始化方法
// 不能调用GrandFather中的初始化方法
}
}
class Person {
var name: String
var age: Int
// 指派构造器前面加上required可以将构造器指定为必要构造器
// 所谓的必要构造器意味着子类也要提供一模一样的构造器
// 指派构造器(designated)
required init(name: String, age: Int) {
print("创建一个人!")
self.name = name
self.age = age
}
// 便利构造器(convenience)
convenience init() {
self.init(name: "无名氏", age: 20)
}
deinit {
print("人嗝屁了!")
}
}
class Student: Person {
var major: String
required init(name: String, age: Int) {
major = "未知"
super.init(name: name, age: age)
}
convenience init(name: String, age: Int, major: String) {
// 下面的语句必须写在调用自己的初始化方法之后否则major属性会被赋上不正确的值
// self.major = major
self.init(name: name, age: age)
self.major = major
// 初始化的第一阶段
// 1. 初始化自己特有的属性
// self.major = major
// // 子类只能调用直接父类的构造器
// // 子类构造器必须调用父类的非便利构造器(指派构造器)
// // super.init() // compiler error
// // 2. 调用父类的初始化方法
// super.init(name: name, age: age)
// // 初始化的第二阶段
// // 此处可以调用对象的方法因为对象已经完成了初始化
// study()
}
func study() {
print("\(name)正在学习.")
}
deinit {
print("学生对象嗝屁了!")
}
}
class Teacher: Person {
deinit {
print("老师对象嗝屁了!")
}
}
//// 创建一个学生对象 然后用stu1去引用它 所以此时学生对象引用计数为1
//var stu1: Student? = Student()
//// 此处没有创建新的学生对象 原来的学生对象的引用计数+1
//var stu2 = stu1
//// 同上 原来的学生对象的引用计数+1
//var stu3 = stu2
//
//// 学生对象引用计数-1
//stu1 = nil
//// 学生对象引用计数-1
//stu2 = nil
//// 学生对象引用计数-1
//// 当学生对象引用计数为0时 ARC会自动清理内存释放学生对象
//// ARC即时性的内存清理 优于Java中的Garbage Collection(垃圾回收)
//stu3 = nil
// var stu1: Student? = Student()
// weak修饰的引用(弱引用)不会增加引用计数 默认是强引用(会增加引用计数)
// weak var stu2 = stu1
// weak var stu3 = stu2
// stu1 = nil
// 如果想释放内存 程序员可以手动将一个引用赋值为nil
// func foo() {
// stu是一个局部变量/常量 在函数调用结束后局部变量就消失了
// 所以学生对象的引用计数也就变成0了 所以会被ARC释放掉
// let stu = Student()
// print(stu)
// }
// foo()
// 栈 - FILO 先进后出的结构
// 创建任何子类对象的时候一定是先创建了父类对象
// var stu: Person = Student()
// 引用转移(会导致原来对象上的引用计数-1 新对象引用计数+1)
// stu = Teacher()
// stu = Person()
// Swift的自动释放池
// 通过向autoreleasepool函数中传入一个闭包来实现
// autoreleasepool { () -> () in
// 自动释放池中的对象引用在池的边界会收到引用计数-1的消息
// 将来做iOS开发时如果某个地方会创建很多的临时对象
// 那么最好在此处设置一个自动释放池避免内存瞬时峰值过高造成闪退
// let stu1 = Student()
// let stu2 = stu1
// }
// 离开自动释放池时 stu1会收到引用计数-1消息 stu2也会收到引用计数-1消息
- 双向关联关系
如果程序中出现了类与类之间双向关联关系 必须要将其中一端设置为weak引用
否则将会形成循环引用导致ARC无法释放内存
示例:
class Emp {
// 推荐使用
// 如果允许使用可空类型通常使用weak来破除循环引用
// 如果员工关联的部门对象被释放了那么dept会被赋值为nil
// 如果要继续给dept对象发消息程序不会崩溃
// weak var dept: Dept?
// 谨慎使用
// 如果不允许使用可空类型就必须使用unowned来破除循环引用
// 需要注意的是如果员工对象关联的部门对象被释放了
// 如果还要通过员工对象去操作它所关联的部门对象将导致程序崩溃
// EXC_BAD_ACCESS
unowned var dept: Dept
init(dept: Dept) {
print("创建一个员工")
self.dept = dept
}
deinit {
print("销毁一个员工")
}
}
class Dept {
var manager: Emp?
init() {
print("创建一个部门")
}
deinit {
print("销毁一个部门")
}
}
func bar() {
// let person = Person()
let dept = Dept()
let emp = Emp(dept: dept)
dept.manager = emp
}
bar()
- 正则表达式
- 嵌套类型