Swift第三周总结

Swift.jpg

:本周对于协议和委托回调的学习使我感悟颇深,一开始由于都是简单的举例,自己也是完全跟着老师的节奏敲代码。对于简单的例子,我接受的似乎过于快(毕竟很简单的例子,使得我对于其用法有一种莫名的掌握感),在课下又缺乏系统的总结和概括。当面对实题测试的时候,我发现自己只能按照笔记的步骤去创建和实现。但是就算这样,在一些细节和忽略的地方我并不能把握好什么是重点,什么是关键。以至于我很失落,我不知道如何去抓住这门语言的某些知识点,我认识了三周的Swift,现在对我来说却是个陌生人。

协议

  • 协议是方法的集合(计算属性相当于就是方法)
  • 可以把看似不相关的对象的公共行为放到一个协议中(去掉重复的代码)
  • 协议在Swift开发中大致有三种作用:
  1. 能力 - 遵循了协议就意味着具备了某种能力
  2. 约定 - 遵循了协议就一定要实现协议中的方法
  3. 角色 - 一个类可以遵循多个协议, 一个协议可以被多个类遵循, 遵循协议就意味着扮演了某种角色, 遵循多个协议就意味着可以扮演多种角色
  • Swift中的继承是单一继承(一个类只能有一个父类), 如果希望让一个类具备多重能力可以使用协议来实现(C++里面是通过多重继承来实现的, 这是一种非常狗血的做法)
protocol 协议名 {
    // 协议的方法
}
  • 协议的扩展
    • 可以在协议扩展中给协议中的方法提供默认实现
    • 也就是说如果某个类遵循了协议但是没有实现这个方法就直接使用默认实现
    • 那么这个方法也就相当于是一个可选方法(可以实现也可以不实现)
extension 扩展的协议名 {
    // 协议中的默认实现方法
}
  • 协议的继承
    • 协议也可以向类一样进行继承
protocol 子协议名: 父协议名1, 父协议名2, ... {
    // 协议的方法
}

下面是小雨康桥的一首诗,过于悲观,我们帮小雨康桥渡劫到来生吧。

我想做燕子 只需简单思想 只求风中流浪
我想做树 不长六腑五脏 不会寸断肝肠

我做不成燕子 所以我躲不过感情的墙
我做不成树 因此也撑不破伤心的网

来生做燕子吧 随意找棵树休息翅膀 然后淡然飞向远方
来生做树吧 当枝头燕子飞走时 再不去留恋张望

/// 生物
class Creature {
}

// 来生

// 将燕子和树设计成两个协议
protocol Swallow {
    
    func thinkSimply()
    
    func wanderInWind()
}

protocol Tree {
    
    func beHeartless()
}

// 人类
class Person: Creature {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

// 小雨康桥除了继承人之外还遵循了燕子和树的协议(类名必须在协议名之前)
class Xiaoyu: Person, Swallow, Tree {
    
    func thinkSimply() {
        print("随意找棵树休息翅膀")
    }
    
    func wanderInWind() {
        print("然后淡然飞向远方")
    }
    
    func beHeartless() {
        print("当枝头燕子飞走时 再不用留恋张望")
    }
}

// 来生的小雨康桥(遵循了燕子和树协议的人)
let xiaoyu: Creature = Xiaoyu(name: "小雨康桥")
// 判定小雨康桥可不可以变成燕子
if let swallow = xiaoyu as? Swallow {
    swallow.thinkSimply()
    swallow.wanderInWind()
}
else {
    if let person = xiaoyu as? Person {
        print("\(person.name)躲不过感情的墙")
    }
}
// 判定小雨康桥可不可以变成树
if let tree = xiaoyu as? Tree {
    tree.beHeartless()
}
else {
    if let person = xiaoyu as? Person {
        print("\(person.name)撑不破伤心的网")
    }
}

Note:协议中全是抽象概念(只有声明没有实现) 遵循协议的类可以各自对协议中的计算属性和方法给出自己的实现版本 这样当我们面向协议编程时就可以把多态的优势发挥到淋漓尽致 可以写出更通用更灵活的代码(符合开闭原则)

实现开闭原则最关键有两点:

  1. 抽象是关键(在设计系统的时候一定要设计好的协议);
  2. 封装可变性(桥梁模式 - 将不同的可变因素封装到不同的继承结构中)

接口(协议)隔离原则: 协议的设计要小而专不要大而全
协议的设计也要高度内聚

结构

要学习结构,就必须先了解计算机的内存机制。
计算机的硬件由五大部件构成:

  • 运算器、控制器、存储器、输入设备、输出设备

    • 运算器 + 控制器 => CPU (中央处理器)
    • 存储器 => 内存 (RAM - Random Access Memory)
  • 程序员可以使用的内存大致分为五块区域:

    • 栈 (stack) - 我们定义的局部变量/临时变量都是放在栈上
    • 特点: 小、快
    • 堆 (heap) - 我们创建的对象都是放在堆上的
    • 特点: 大、慢
    • 数据段 - 全局量
    • 只读数据段 - 常量
    • 代码段 - 函数和方法

结构和类的比较

  • 区别1: 结构的对象是值类型, 类的对象是引用类型
    • 值类型在赋值的时候会在内存中进行对象的拷贝
    • 引用类型在赋值的时候不会进行对象拷贝只是增加了一个引用

结论: 我们自定义新类型时优先考虑使用类而不是结构除非我们要定义的是一种底层的数据结构(保存其他数据的类型)

// 学生类
class StuClass {
    var name: String
    var age: Int
    var tel: String?
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    func getOlder() {
        age += 1
    }
    
    func study(courseName: String) {
        print("\(name)正在学习.")
    }
}

// 学生结构
struct StuStructure {
    var name: String
    var age: Int
    
//  区别2: 结构会自动生成初始化方法,所以不需要在结构内部初始化
    
//  区别3: 结构中的方法在默认情况下是不允许修改结构中的属性除非加上mutating关键字
    mutating func getOlder() {
        age += 1
    }
}
//StuStructure
// 引用类型的类
let stu1 = StuClass(name: "高圆圆", age: 18)
var stu3 = stu1     // 此处内存中仍然只有一个学生对象
stu3.name = "Frank"
stu3.age = 19
print(stu1.name)    // Frank
print(stu1.age)     // 19

// 值类型的结构
let stu2 = Student2(name: "高圆圆", age: 18)
var stu4 = stu2     // 此处内存中会复制一个新的学生对象
stu4.name = "Frank"
stu4.age = 19
print(stu2.name)    // 高圆圆
print(stu2.age)     // 18

委托回调 / 代理

代码是最好笔记,直接上代码,语言表达只会更抽象。

// 假设的学生委托枪手代考的例子

// 委托回调
// 1. 设计一个协议(被委托方必须要遵循协议才能给别的对象当委托)
// 代考协议
protocol ExamDelegate: class {
    // 协议里面的方法就是要委托其他对象做的事情
    // 答题
    func anserTheQuestion()
}

class Student {
    // 2. 委托方添加一个属性其类型是遵循了协议的被委托方
    weak var dalegate: ExamDelegate?
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func joinExam() {
        print("\(name)啥都不会做啊!")
        
        // 3. 自己做不了的事情委托给别的对象来做
        dalegate?.anserTheQuestion()
    }
}

//  4. 让枪手遵循协议成为被委托方(协议表能力)
class Gunner: ExamDelegate {
    
    //  5. 遵循协议就必须要实现协议中的方法(协议表约定)
    func anserTheQuestion() {
        print("枪手帮忙弹无虚发!")
    }
}

let stu = Student(name: "高圆圆")
let gun = Gunner()
stu.dalegate = gun
stu.joinExam()
// 高圆圆啥都不会做啊!
// 枪手帮忙弹无虚发!

// 代理
// 1. 设计一个协议(被代理方必须要遵循协议才能给别的对象当代理)
protocol ExamCandidate: class {
    func answerTheQuestion()
}

class Student {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func answerTheQuestion() {
        print("\(name)啥都不会做啊!")
    }
}

// 2. 让枪手遵循协议成为代理方
class Gunman: ExamCandidate {
    var name: String
    
    // 3. 代理方添加一个属性其类型是被代理方
    var target: Student?
    
    init(name: String) {
        self.name = name
    }
    
    // 4. 遵循协议就必须要实现协议中的方法
    func answerTheQuestion() {
        if let stu = target {
            print("姓名:\(stu.name)")
            print("奋笔疾书挥汗如雨!")
            print("提交试卷")
        }
    }
}

let stu = Student(name: "高圆圆")
let gun = Gunman(name: "Frank")
// 5.自己做不了的事让代理方来做
gun.target = stu
gun.answerTheQuestion()
// 姓名:高圆圆
// 奋笔疾书挥汗如雨!
// 提交试卷

构造器

  • 指派构造器(designated)
  • 便利构造器(convenience)
    • 指派构造器前面加上convenience可以将构造器指定为便利构造器
    • 可以有多个便利构造器
  • 必要构造器(required)
    • 指派构造器前面加上required可以将构造器指定为必要构造器
    • 所谓的必要构造器意味着子类也要提供一模一样的构造器

初始化

  • 初始化的第一阶段
    • 初始化自己特有的属性
    • 子类只能调用直接父类的构造器
    • 子类构造器必须调用父类的非便利构造器(指派构造器)
    • 调用父类的初始化方法
  • 初始化的第二阶段
    • 此处可以调用对象的方法因为对象已经完成了初始化

ARC即时性的内存清理

如果程序中出现了类与类之间双向关联关系,将会形成循环引用导致ARC无法释放内存。

解决方法:

  • 如果允许使用可空类型通常使用weak来破除循环引用(推荐使用)原因如下:

    • 如果与之关联的对象被释放了,那么该对象会被赋值为nil
    • 如果要继续给该对象发消息程序不会崩溃
  • 如果不允许使用可空类型就必须使用unowned来破除循环引用(谨慎使用)原因如下:

    • 如果与之关联的对象被释放了,仍然通过该对象向其发消息,那么会导致程序崩溃

泛型(generic)

让类型不再是程序中的硬代码(写死的东西)
Swift中的类、结构和枚举都可以使用泛型

// 冒泡排序
// 定义一个虚拟类型T, 调用函数时根据传入的参数类型来决定T到底是什么类型
// 泛型限定
// <T: Comparable>限定T类型必须是遵循了Comparable协议的类型
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] {
                (newArray[j], newArray[j + 1]) = (newArray[j + 1], newArray[j])
                swapped = true
            }
        }
        if !swapped {
            break
        }
    }
    return newArray
}

let array1: Array<Int> = [23, 45, 99, 12, 68, 51, 70, 66]
let array2 = bubbleSort(array1)
print(array2)     // [12, 23, 45, 51, 66, 68, 70, 99]

let array3 = ["hello", "zoo", "kiss", "apple", "good"]
let array4 = bubbleSort(array3)
print(array4)     // ["apple", "good", "hello", "kiss", "zoo"]

异常处理

以上周自定义的分数运算的类为例

// 短除法(欧几里得算法)
// 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)
}

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("出错了! 我也不知道是什么问题!!!")
}

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


自省

突然发现自己的很多不足之处,对Swift语法的学习算是告一段落了,但是我对该语言的运用却是很有限的,我只能在playground中写一些类似于排序、简单的逻辑运算还有一些小的数字游戏等。对于复杂一点的逻辑以及需要较困难的综合运用,我都是比较难以入手的,但有时候在老师的指点下也会有一种豁然开朗的感觉,我觉得那正是我喜欢这行的证明。我个人认为经验和技术是需要大量的代码堆积起来的,而我才刚刚起步。我怕走错路,更怕被牵着跑,欲速则不达。不管怎样总算是开了个头,此后的时间虽然有限但是我会好好把握的。
周末,我整理一下对于委托回调和代理的关系,以及如何分别实现两者。虽然写得很简单,但是实现的步骤却很清晰明了,我喜欢这种感觉。

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

推荐阅读更多精彩内容