Swift语言阶段学习小结

作者: Youth丶夏夏 | 来源:发表于2016-08-19 20:40 被阅读351次
    Swift-杨夏

    前言:原本这周应该写Swift第三周学习总结了,但是由于我已完成关于这门语言的基础学习,索性就做一个关于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. 字母(Unicod字符)、数字、下划线,数字不能开头
    2. 大小写敏感(区分大小写)
    3. 不能使用关键字做标识符
    4. 使用驼峰命名法(命名变量、常量、函数、方法、属性第一个单词小写,从第二个单词开始每个单词首字母大写;命名类、结构、协议、枚举每个单词首字母都要大写)
    5. 见名知意
    6. 命名私有的属性和方法时以下划线开头

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

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

    字面(常)量

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

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

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

    分支和循环

    分支

    • if...else...
    //输入年、月判断该月有多少天
    print("请输入年和月用空格隔开(例如: 1980 2)", terminator: "")
    let year = inputInt()
    let month = inputInt()
    let day: Int
    if month < 1 || month > 12 {
        print("你的输入有误!")
        exit(0) // 程序退出不再执行
    }
    else if month == 2 {
        day = year % 4 == 0 && year % 100 != 0 || year % 400 == 0 ? 29 : 28
    }
    else if month == 4 || month == 6 || month == 9 || month == 11 {
        day = 30
    }
    else {
        day = 31
    }
    print("\(year)年\(month)月有\(day)天")
    
    • switch...case...default
    //摇骰子做游戏
    let face = Int(arc4random_uniform(6)) + 1
    switch face {
    case 1:
        print("扮鬼脸")
    case 2:
        print("唱一首")
    case 3:
        print("亲一个")
    case 4:
        print("真心话")
    case 5:
        print("大冒险")
    default:
        print("脱一件")
    

    循环

    • while
    //0-100的求和
    var i = 1
    var sum = 0
    while i <= 100 {
        sum += i
        i += 1
    }
    print("1 + 2 + ... + 100 = \(sum)")
    
    • repeat...while...
    //0-100的求和
    var i = 1
    var sum = 0
    repeat {
        sum += i
        i += 1
    } while i <= 100
    print("1 + 2 + ... + 100 = \(sum)")
    
    • for
    //0-100的求和
    var sum = 0
    for i in 1...100 {
        sum += i
    }
    print("1 + 2 + ... + 100 = \(sum)")
    

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

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

    问题说明:
    公鸡5元1只、母鸡3元1只、小鸡1元3只,现在有100元买100只鸡,请列出所有可能的结果
    
    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点庄家胜
    
    import Foundation
    func roll() -> Int {
        return Int(arc4random_uniform(6)) + 1
    }
    var money = 1000
    repeat {
        print("玩家总资产: ¥\(money)元")
        var debt: Int
        repeat {
            print("请下注: ", terminator: "")
            debt = inputInt()
        } while debt <= 0 || debt > money
    
        var needsGoOn = false
        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("你破产了!!!")
    

    容器

    数组

    • 创建数组
    var array1: [Int] = []
    var array2: Array<Int> = []
    var array3 = [1, 2, 3, 4, 5]
    var array4 = [Int](count: 5, repeateValue: 0)
    var array5 = Array<Int>(count: 5, repeateValue: 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()
    
    • 修改元素
    array1[0] = 100
    array1[array1.count - 1] = 500
    print(array1)
    
    • 遍历数组
    1. 方式1
    for i in 0..<array1.count {
        print(array1[i])
    }
    
    1. 方式2
    for temp in array1 {
        print(temp)
    }
    for temp in array1[1...3] {
        print(temp)
    }
    

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

    1. 方式3
    for (i, temp) in array1.enumerate() {
        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 a: Set<Int> = [1, 2, 1, 5, 8, 5]
    
    • 添加和删除元素
    a.insert(100)           // 添加元素
    
    a.remove(2)             // 删除元素
    set.removeFirst()
    set.removeAll()
    
    • 集合的运算(交集、并集、差集)
    var a: Set<Int> = [1, 2, 1, 2, 3, 4, 5]
    var b: Set<Int> = [1, 3, 5, 7]
    
    a.intersect(b)  // 交集(a和b都有的元素)
    a.union(b)      // 并集(a和b的所有元素)
    a.subtract(b)   // 差集(a有b没有的元素)
    

    字典

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

    • 创建字典
    var dict: [String: String] = [
        "abacus": "算盘",
        "abnormal": "异常的",
        "hello" : "你好",
        "good": "好的"
        ]
    
    
    • 添加、删除、修改元素
    // 添加元素
    dict["shit"] = "狗屎"
    dict["delicious"] = "好吃的"
    
    // 删除元素
    // dict.removeValueForKey("hello")
    dict["hello"] = nil
    // 修改元素
    dict["shit"] = "牛粪"
    print(dict)
    
    • 遍历元素
    
    // 遍历字典中所有的值
    for value in dict.values {
        print(value)
    }
    // 遍历字典中所有的键
    for key in dict.keys {
        print("\(key) ---> \(dict[key])")
    }
    // 直接通过一个元组获得字典中的键和值(原始类型)
    for (key, value) in dict {
        print("\(key) ---> \(value)")
    }
    
    

    重要操作

    • 排序
    1. sort
    2. sortInPlace

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

    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, 37, 96, 55, 40, 92, 68, 88]
    //筛选不满足条件的数据
    let newArray1 = array.filter { $0 > 50 }
    let newArray2 = array.filter { $0 % 2 == 0 }
    print(newArray2)
    
    
    • 映射
    let array = [23, 37, 96, 55, 40, 92, 68, 88]
    //通过映射对数据进行变换处理
    let newArray3 = array.map { $0 * $0 }
    let newArray4 = array.map { sqrt(Double($0)) }
    
    • 归约
    let result1 = array.reduce(0, combine: +)
    print(result1)
    let result2 = array.reduce(1, combine: *)
    print(result2)
    let result3 = array.reduce(array[0]) {
        $1 > $0 ? $1 : $0
    }
    print(result3)
    let strArray = ["I", "love", "you"]
    let result4 = strArray.reduce("") { $0 + " " + $1 }
    print(result4)
    
    

    函数和闭包

    说明:函数是独立的可重复使用的功能模块,如果程序中出现了大量的重复代码,通常都可以将这部分功能封装成一个独立的函数。在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 - 输入输出参数(不仅将数据传入函数还要从函数中取出数据)
    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)  //inout类型的参数前要加上&符号
    print("a = \(a)")
    print("b = \(b)")
    
    • 可变参数列表
    func sum(nums: Int...) -> Int {
        var total = 0
        for num in nums {
            total += num
        }
        return total
    }
    
    print(sum())
    print(sum(999))
    print(sum(1, 2, 3))
    print(sum(90, 82, 37, 68, 55, 11, 99))
    

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

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

    面向对象编程(OOP)

    基本概念

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

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

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

    四大支柱

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

    封装:

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

    继承:

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

    注意:
    1.可以将子类型的对象赋值给父类型的变量(因为子类跟父类之间是IS-A关系)
    例如:学生是人, 老师是人, 所以学生和老师的对象可以赋值给人类型的变量
    2.如果要将父类型的变量转换成子类型需要用as运算符进行类型转换
      - 如果能够确认父类型的变量中就是某种子类型的对象可以用as!进行转换
      - 如果不确定父类型的变量中是哪种子类型可以用as?尝试转换(可以通过if+as?将
        父类型安全的转换成子类型然后再调用子类特有方法)
    

    多态:

    同样的对象类型(Pet类型)接收相同的消息(调用相同的方法)
    但是做了不同的事情 这就是多态(polymorphism)

    • 实现多态的关键步骤:
    1. 方法重写(子类在继承父类的过程中对父类已有的方法进行重写, 而且不同的子类给出各自不同的实现版本)
    2. 对象造型(将子类对象当成父类型来使用)
    • 重要方式
      重载 - overload
      重写 - override

    三个步骤

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

    举个例子。

    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("无法创建三角形")
    }
    
    

    相关内容

    • 枚举(enum)
    • 结构(体)(struct)

    总结: 类和结构的区别到底有哪些?什么时候应该使用结构?什么时候应该使用类?
    区别1: 结构的对象是值类型, 类的对象是引用类型
    区别2: 结构会自动生成初始化方法
    区别3: 结构中的方法在默认情况下是不允许修改结构中的属性除非加上mutating关键字
    值类型在赋值的时候会在内存中进行对象的拷贝
    引用类型在赋值的时候不会进行对象拷贝只是增加了一个引用
    结论: 我们自定义新类型时优先考虑使用类而不是结构除非我们要定义的是一种底层的数据结构(保存其他数据的类型)

    补充: 
        程序员可以使用的内存大致分为五块区域:
        栈 (stack) - 我们定义的局部变量/临时变量都是放在栈上
        - 特点: 小、快
        堆 (heap) - 我们创建的对象都是放在堆上的
        - 特点: 大、慢
        静态区 (static area)
          - 数据段 - 全局量
          - 只读数据段 - 常量
          - 代码段 - 函数和方法
    
    • 扩展(extension)

    如果在某个特定的应用场景中你发现现有的类缺少了某项功能那么可以通过类扩展(extension)的方式现场添加这项功能

    func randomInt(min: UInt32, _ max: UInt32) -> Int {
        return Int(arc4random_uniform(max - min + 1) + min)
    }
    //在UIColor类中添加一个产生随机色的静态方法
    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)
        }
    }
    
    • 运算符重载
    • 下标运算(subscript)
      • 直接通过对象的下标来对其进行操作
    • 访问修饰符
      • private
      • internal
      • public

    综合例子:求分数的加减乘除运算。

    说明:该例子中包含了文章后面所涉及到的错误处理的相关应用,感觉放在例子中更通俗易懂。

    Fraction.swift

    import Foundation
    
    // 短除法(欧几里得算法)
    // x和y的最大公约数跟y%x和x的最大公约数是一样的
    // Greatest Common Divisor
    func gcd(x: Int, _ y: Int) -> Int {
        if x > y {
            return gcd(y, x)
        }
        else if y % x != 0 {
            return gcd(y % x, x)
        }
        else {
            return x
        }
    }
    
    // 定义一个遵循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)
    }
    

    main.swift

    // 对于可能出状况的代码要放在do...catch中执行
    // 在可能出状况的方法前还要写上try表示尝试着执行
    // 如果在do中没有出现任何状况那么catch就不会执行
    // 如果do中出现了状况代码就不会再向下继续执行而是转移到catch中
    // 在do的后面可以跟上多个catch用于捕获不同的异常状况 但是最多只有一个catch会被执行
    import Foundation
    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("出错了! 我也不知道什么问题")
    }
    

    有关错误处理的补充

    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()
    

    面向协议编程(POP)

    协议

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

    Swift中的继承是单一继承(一个类只能有一个父类), 如果希望让一个类具备多重能力可以使用协议来实现(C++里面是通过多重继承来实现的, 这是一种非常不好的做法)

    依赖倒转原则(面向协议编程)

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

    用协议实现委托回调

    一个对象想做某件事情但是自身没有能力做这件事可以使用委托回调

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

    注意: 委托方的协议类型的属性通常是可空类型,因为要写成weak引用(委托回调和代理模式是有区别不可混淆

    • 委托回调
    import Foundation
    //委托回调
    protocol ExamDelegate: class {
        
        func answerTheQuestion()
    }
    
    class LazyStudent {
        var name: String
        weak var delegate: ExamDelegate?
        
        init(name: String) {
            self.name = name
        }
        
        func joinExam() {
            print("姓名: \(name)")
            delegate?.answerTheQuestion()
        }
    }
    
    class Gunman: ExamDelegate {
        
        func answerTheQuestion() {
            print("奋笔疾书各种答案")
        }
    }
    
    let stu = LazyStudent(name: "王大锤")
    let gun = Gunman()
    stu.delegate = gun
    stu.joinExam()
    
    • 代理模式
    import Foundation
    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,...>
    • 可选方案
    • 协议扩展

    下面是一个关于协议的例子。

    Book.swift

    /// 图书
    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 }
        }
    }
    

    DiscountStrategy.swift

    /**
     *  打折策略协议
     */
    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
            }
        }
    }
    

    main.swift

    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))
    

    运行截图:

    屏幕快照 2016-08-16 下午8.22.43.png

    泛型

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

    • 泛型函数
    // 定义一个虚拟类型T, 调用函数时根据传入的参数类型来决定T到底是什么
    func mySwap<T>(inout a: T, inout _ b: T) {
        let temp = a
        a = b
        b = temp
    }
    
    • 泛型类、结构、枚举
    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())
    }
    

    相关知识

    • 泛型限定

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

    • where子句

    错误处理

    提示:请看上述求分数的加减乘除运算的例子
    目标:只要明白以下关键字的用法即可

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

    边角知识

    • ARC - 自动引用计数
    • 正则表达式
    • 嵌套类型

    --更多精彩内容请关注:Youth丶夏夏--

    相关文章

      网友评论

      • Youth丶夏夏:这周学习UI部分,精彩内容正在紧张筹备中。。。。。

      本文标题:Swift语言阶段学习小结

      本文链接:https://www.haomeiwen.com/subject/xenvsttx.html