【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记17 Deledgation代理

上一话中介绍了扩展和协议的相关知识,这一话我们介绍一个很重要的概念delegation(代理),代理是协议的一个很重要的应用。我们来回顾一下代理的相关知识,它是控制器和试图通讯的关键。

那么代理是如何工作的呢

1.你需要创建一个代理协议,它描述了这个试图要帮别人做的事情。

2.在你的视图中创建一个属性,称作代理或者有时也叫数据源,这个属性的类型就是你创建的代理协议。

3.然后你使用这个属性去处理所有的代理,代理属性会去请求它所需要的数据,记得试图本身是不能拥有数据的。只要是遵循这个协议的对象都可以向这个协议中的属性设值,有了值之后我们的视图就知道该如何做了。

4.控制器首先会说它自身实现了这个协议,然后它将它自身作为代理对象

5.控制器实现所有的代理方法,这样它就遵循了协议

现在控制器已经和视图建立了连接,虽然它不知道控制它的类是什么样的,这些数据是什么,它唯一知道的是这个控制器实现了视图中的协议方法。

下面让我们回到我们的小人脸的Demo中看看如何实现上述的操作。

让faceView中的代理获得它的数据,这个数据就是它的smiliness,而HappinessViewController会成为它的控制器。

在FaceView中加入如下代码:

protocol FaceViewDataSource {
  func smilinessForFaceView(sender: FaceView) -> Double?
}

可能通常我们的命名应该是FaceViewDelegate,但是这个协议的作用主要是返回smiliness的值,这种代理叫DataSource,所以我们这么命名。传入的参数类型是FaceView,即将自己传入,在IOS中如果某个对象拥有一个Delegate或者DataSource的时候,它们都会传入它们自身。

第二步是,在我们的FaceView中我们需要有一个公开的变量:

 var dataSource:FaceViewDataSource?

可以看到这里把我们的协议作为了数据类型,那么如果我们想要让某个协议成为我们的代理,只需要将协议设置为类的变量的值,使用可选型是因为没有控制器去遵循我们的代理协议,那么我们的小人脸的嘴就会是一条直线不会变化,协议可以是nil,虽然通常我们不希望它是nil。

我们希望它是弱类型的:

 weak var dataSource:FaceViewDataSource?

这涉及到内存管理的知识,事实上内存是被操作系统自动管理的,所以不需要担心。但是需要注意的是如果控制器把它自身当做代理,然后它把指针指向自身,不幸的是如果我们的控制器在视图层上已经有一个FaceView的指针了,因为它有一个连接到FaceView的outlet,这样两个对象就互相指向了对方。这种情况下它们就会一直在内存中互相引用对方,内存中出现这样的循环是很可怕的,因为控制器和视图都无法释放内存,它们会永远呆在内存中,所以使用weak关键字意思是无论它指向了什么对象,它都不该留在内存中。通常我们不怎么使用weak,但是在IBOutlet中weak被大量使用,代理的使用是另一个应用到weak的地方。这样我们有一个代理的时候我们始终需要一个weak类型的变量。

你会发现上面这句话报错了,提示我们weak只能用在类中,虽然我们的代理是一个协议,但是前几话中讲到了你可以通过如下写法把它设定为只能被类遵循:

protocol FaceViewDataSource:class {
  func smilinessForFaceView(sender: FaceView) -> Double?
}

现在这个代理将不能被枚举和结构体实现了。

之前smiliness的值是写死的,现在我们希望smiliness的值由代理提供,做如下修改:

        let smiliness = dataSource?.smilinessForFaceView(self)

你会看到下面这句报错了:

let smilePath = bezierPathForSmile(smiliness)

这是因为现在我们的smiliness是一个可选值,而bezierPathForSmile的参数必须不为空,那么我们该怎么办呢,我们又要接触到Swift中一个很酷的特性,那就是??操作符了。

let smiliness = dataSource?.smilinessForFaceView(self) ?? 0.0

??操作符的意思是如果左边的表达式为nil那么就使用??右边的值来返回,如果左边的值不为nil就返回左边的值,现在如果代理传入的值为空的话,那么smiliness会得到0.0的值,小人脸的嘴是一条直线。

现在让我们来到实现代理的另一边,也就是HappinessViewController。

首先让它遵守协议:

class HappinessViewController: UIViewController,FaceViewDataSource{

一旦你这样写就会收到一个报错提示你没有实现代理方法。

import UIKit

class HappinessViewController: UIViewController,FaceViewDataSource{
    var happiness:Int = 50 {//0代表伤心,100代表开心
        didSet{
            happiness = max(min(happiness, 100), 0)
            println("happiness = \(happiness)")
            updateUI()
        }
    }
    func updateUI(){

    }
    func smilinessForFaceView(sender: FaceView) -> Double? {
        <#code#>
    }
}

只需要输入代理方法的前几个字母系统会自动关联。代理方法不知道happiness是做什么的,因为它是模型层,控制器的任务就是为视图解析这个模型,你也会为了模型去解析视图,这个在手势识别的时候会讲到。

那么代理方法中的内容:

 func smilinessForFaceView(sender: FaceView) -> Double? {
        return Double(happiness - 50)/50
    }

这样我们把模型中的数据转成了视图所需要的格式。现在最后的步骤就是控制器要把自己作为FaceView的数据源。

现在回到FaceView中,有个小窍门:按下command+shift+O键然后键入关键字就能快速找到工程中的文件,当你的工程逐渐复杂起来的时候这是个非常不错的办法。

注意现在我们的storyboard中显示的是FaceView,记得之前我们设置的@IBdesignable吧?我们需要在控制器上拖拽一个faceview来显示

@IBOutlet weak var faceView: FaceView!

然后给它增加一个属性检测器,这个检测器会在IOS启动这个应用并且加载这个storyboard的时候被调用,这是个很好的时机

@IBOutlet weak var faceView: FaceView!{
        didSet{
        faceView.dataSource = self
        }
    }

另外一件要做的事情是每次我们的模型有变动时,我们要让faceView重绘自身。我们需要补全updateUI这个方法:

func updateUI(){
    faceView.setNeedsDisplay()
    }

这里的意思就是调用我的drawRect函数,重绘我。如果不做这一步那么修改happiness的值小人脸的表情不会有变化。我们把happiness的值修改为75,来运行一下看看效果:

一个很可爱的笑脸

时间: 2024-07-31 00:35:53

【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记17 Deledgation代理的相关文章

【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记1 IOS8概述

首先感谢网易公开课和SwiftV课堂的朋友们辛苦翻译,这个系列是我学习斯坦福IOS8公开课的个人心得体会和笔记,希望能给大家带来启发. 首先我们要知道IOS系统中的结构情况,从贴近硬件的底层到贴近用户的顶层,分为四个层次: 1.Core OS层在最下层,很多人可能不知道IOS是一个基于UNIX的操作系统,它大量借鉴了Mac os X 的内核部分,Mac OS X我们肯定不会陌生,命令行的使用很好的证明了它是一个基于UNIX的系统.IOS针对移动设备对电池等硬件进行了系统的优化,但它仍可被看成是一

【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记2 Xcode、Auto Layout及MVC

原文链接不知道在哪, 接着上一话来讲,上一话中讲到了MVC,那么MVC在IOS8开发中是如何应用的呢?Paul Hegarty老师给我们展示了一个计算器的Demo,首先新建一个工程,老师把AppDelegate.swift.LaunchScreen.xib和Images.xcassests文件放到了supporting Files文件夹中,那么剩下的两个文件ViewController.swift就是MVC中的C(控制器),Main.storyboard就是MVC中的V(视图). 在Main.s

【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记13 Drawing绘制、UIColor颜色、Fonts字体

上一话介绍了视图绘制的一些基本原理,这一话继续展开.UIBezierPath可以绘制许多有趣的图形. 使用不同的构造器,比如roundedRect就是四个角被磨圆了的矩形,或者干脆是椭圆和圆.你甚至可以剪切任意的path,剪切使用addClip方法,在剪切了之后你可以针对剪切的这部分进行操作,例如你正在绘制一个卡片,这个卡片有小小的圆角效果,你可以把卡片绘制在一个矩形里面,然后把它剪切到一个小一点的圆角矩阵里,这样四角就修圆了.这是一个获得圆角卡片最简单的方法. 你也可以进行碰撞检测,它基于绕圈

【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记37 TableView Delegate

上一话介绍了tableView的datasource,本话来介绍另一个重要的部分delegate. 当我们点击一个cell的时候,如何跳转到另外一个mvc中呢? 像增加其他segue一样,点击cell按住control键,右键连线到另一个mvc上,然后松手,选择需要的segue类型. 如果你的cell上还有其他按钮,比如detail disclosure,你也可以选择它的segue: 然后设置你的segue: 接着去prepareForSegue中设置这个segue: 每一个case对应不同的i

【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记3 Xcode、Auto Layout及MVC

继续上一话中的计算器Demo.上一话讲到类必须被初始化.类中的属性也必须被初始化,所以你不能仅仅声明而不给它一个处置,那么问题来了,我们从storyboard中拖拽的@IBOutlet为什么仅仅有声明而不须要初始化呢,这是由于它的类型依然是一个optional,在你初始化之前已经被赋值为nil了,这也就是为什么你不须要再初始化它的原因. @IBOutlet weak var display: UILabel! 然而既然它是一个optional类型的,那为什么UILabel后面是"!"而

【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记39 Alert&amp;ActionSheet

Alert和ActionSheet是IOS中弹出消息的两个工具. 首先它们都是Modal的方式展示的. Alert用来向用户发起询问,可以有一个(比如取消)或两个选项(比如确定和取消),也可以附带一个文本框(比如要求用户输入密码) Action Sheet从屏幕底部滑出,提供一些分支的选项,选项的数量可以大于两个. 对比如图: Action Sheet和Alert都可以使用UIAlertController来创建,比如创建一个Action Sheet,在构造器中制定它的title和简介,注意St

【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记23 多MVC模式Demo的实现

上一话我们对Demo的选择界面做了自动布局的相关处理,现在开始连接多个MVC的操作.首先我们需要其他工程中的文件,那么让我们打开另一个app.点击下面这个文件 然后拖动我们需要的文件到新的工程目录下: 注意勾选第一行,不然只是做了引用,如果你不小心删除了目标目录的话,你就找不到这些文件了,所以还是推荐做复制,这样会把文件复制到我们自己的工程目录下. 那么storyboard中的内容怎么办呢,我们只需要在原工程的storyboard中选中控制器然后复制,粘贴到新工程的storyboard中即可.

【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记31 Multithreading多线程

在IOS中存在着许多队列,和我们数据结构中的队列一样,这里的队列概念也是先进先出的.而每一个方法(包括闭包)都被组织在这些不同的队列中,而每一个队列都有自己的线程去运行这些队列,这就造就了多线程环境. 其中有一个非常重要的队列叫做主队列,主队列是一个串行队列,所以主队列只会一个一个地执行主队列中的函数.所有的UI活动都必须发生在主队列中,所以当你想要一个函数或者是闭包的时候就会执行某些代码,这就会做任何关于UI的事,必须把它放到主队列中,这是保护UI的好办法,主队列绝对不想做任何可能被阻塞的事情

【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记6 init

这一话首先来讲写关于init的东西. 首先初始化并不会经常被用到,这是因为类和结构体中的大部分属性都会通过赋值被初始化,或者有些属性是Optional的,这样即使是nil也没关系,可以在之后再给它们赋值,就好比StoryBoard中的outlet,又或者可以使用闭包来初始化,或者使用lazy来避开init,所以有很多方法来避免init,除非你确实需要一个init的时候,那么该怎么做呢? 在一些情况下会自动生成init,其中一种情况是当类中的所有属性都有初始值的时候,你会自动得到一个没有参数的初始