Swift中的代理模式?

145

如何在swift中创建委托,例如NSUserNotificationCenterDelegate


4
你的意思是实现一个委托还是定义你自己的委托? - drewag
15个回答

251

这里有一些关于在两个视图控制器之间使用代理的小帮助:

步骤1: 在您将删除/发送数据的UIViewController中创建一个协议。

protocol FooTwoViewControllerDelegate:class {
    func myVCDidFinish(_ controller: FooTwoViewController, text: String)
}

步骤2:在发送类(即UIViewcontroller)中声明委托。

class FooTwoViewController: UIViewController {
    weak var delegate: FooTwoViewControllerDelegate?
    [snip...]
}

步骤3:在类方法中使用委托将数据发送到接受协议的任何方法。

@IBAction func saveColor(_ sender: UIBarButtonItem) {
        delegate?.myVCDidFinish(self, text: colorLabel.text) //assuming the delegate is assigned otherwise error
}

第四步:在接收类中采用该协议

class ViewController: UIViewController, FooTwoViewControllerDelegate {

步骤 5: 实现委托方法

func myVCDidFinish(_ controller: FooTwoViewController, text: String) {
    colorLabel.text = "The Color is " +  text
    controller.navigationController.popViewController(animated: true)
}

步骤 6:在prepareForSegue中设置代理:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "mySegue" {
        let vc = segue.destination as! FooTwoViewController
        vc.colorString = colorLabel.text
        vc.delegate = self
    }
}

这应该可以工作。当然,这只是一些代码片段,但应该能够给你一个想法。如需详细解释,请参阅我的博客文章:

segues和delegates

如果您对代理的内部工作原理感兴趣,我在这里有写过:

了解代理的内部工作原理


23
Step2中不应该有弱引用到委托对象吗? 如果我没错的话,请编辑一下。另外,你可以将其设为可选值。这样会更符合Swift的风格。 弱引用代理(delegate)应该是弱引用,因为会出现保留环问题,子视图控制器不应该对父视图控制器持有强引用。 - Shial
1
当你将代理设置为可选时,可以解决错误的展开。使用 delegate?.myVCDidFinish,因为如果代理未设置,则代码不会执行。在您的版本中,它将尝试执行并在委托为空时无法展开。 - Shial
4
为了使委托协议FooTwoViewControllerDelegate支持弱引用,您需要像这样声明协议:protocol FooTwoViewControllerDelegate: class {} - codingrhythm
请问您能否在每个步骤中指明您所使用的 VC,例如 VC1 和 VC2。我不太确定应该把它们放在哪里。 - Cing
2
@Shial - 实际上这似乎有点复杂。 weak 只适用于类而不是结构体和枚举。如果委托将是一个结构体或枚举,则无需担心保留循环。但是,如果委托是一个类(这在许多情况下都是正确的,因为通常它是 ViewController),则需要使用 weak,但需要将您的协议声明为类。这里有更多信息:https://dev59.com/aGAf5IYBdhLWcg3w_W9j#34566876 - Robert

123

委托一直让我感到困惑,直到我意识到委托只是为另一个类执行某些工作的类。就像有其他人帮你做你不想亲自做的所有脏活一样。

我写了一个小故事来阐述这个概念。如果您愿意,可以在 Playground 中阅读它。

从前从前...

// MARK: Background to the story

// A protocol is like a list of rules that need to be followed.
protocol OlderSiblingDelegate: class {
    // The following command (ie, method) must be obeyed by any 
    // underling (ie, delegate) of the older sibling.
    func getYourNiceOlderSiblingAGlassOfWater()
}

// MARK: Characters in the story

class BossyBigBrother {
    
    // I can make whichever little sibling is around at 
    // the time be my delegate (ie, slave)
    weak var delegate: OlderSiblingDelegate?
    
    func tellSomebodyToGetMeSomeWater() {
        // The delegate is optional because even though 
        // I'm thirsty, there might not be anyone nearby 
        // that I can boss around.
        delegate?.getYourNiceOlderSiblingAGlassOfWater()
    }
}

// Poor little sisters have to follow (or at least acknowledge) 
// their older sibling's rules (ie, protocol)
class PoorLittleSister: OlderSiblingDelegate {

    func getYourNiceOlderSiblingAGlassOfWater() {
        // Little sis follows the letter of the law (ie, protocol),
        // but no one said exactly how she had to respond.
        print("Go get it yourself!")
    }
}

// MARK: The Story

// Big bro is laying on the couch watching basketball on TV.
let bigBro = BossyBigBrother()

// He has a little sister named Sally.
let sally = PoorLittleSister()

// Sally walks into the room. How convenient! Now big bro 
// has someone there to boss around.
bigBro.delegate = sally

// So he tells her to get him some water.
bigBro.tellSomebodyToGetMeSomeWater()

// Unfortunately no one lived happily ever after...

// The end.

回顾一下,使用委托模式涉及三个关键部分:

  1. 协议:定义工作类需要执行的任务。
  2. 老板类:拥有一个委托变量,并使用它告诉工人类要做什么。
  3. 工人类:采用此协议并执行所需任务。

实际应用

与上面的Bossy Big Brother故事相比,委托通常用于以下实际应用:

  1. 通信:一个类需要向另一个类发送一些信息。
  2. 定制:一个类想要允许另一个类对其进行定制。

重要的是,这些类之间事先不需要了解彼此的任何内容,只需要知道委托类符合所需的协议即可。

我强烈建议阅读以下两篇文章。它们帮助我更好地理解委托,甚至比文档还有用。

再补充一点

引用其他它所不拥有的类的委托应该使用 weak 关键字,以避免强引用循环。请参见此答案了解更多详细信息。


5
终于有人能够用常识解释协议和代表了!谢谢,伙计! - Engineeroholic
当Bossy Big Brother不知道他是兄弟(泛型)时会发生什么? - Marin
@Marin,我不太确定我理解你的问题。规则列表(协议)并不关心谁在呼吁遵守规则或者谁在遵守规则。它们只是规则。 - Suragch
基本上,我这里是在提及我的问题,稍作简化。 http://stackoverflow.com/questions/41195203/swift-3-delegate-within-tapgesturerecognizer-in-generics-doesnt-get-called - Marin

74

它与 obj-c 并没有太大的不同。 首先,您需要在类声明中指定协议,如下所示:

class MyClass: NSUserNotificationCenterDelegate

实现将类似于以下内容:

// NSUserNotificationCenterDelegate implementation
func userNotificationCenter(center: NSUserNotificationCenter, didDeliverNotification notification: NSUserNotification) {
    //implementation
}

func userNotificationCenter(center: NSUserNotificationCenter, didActivateNotification notification: NSUserNotification) {
    //implementation
}

func userNotificationCenter(center: NSUserNotificationCenter, shouldPresentNotification notification: NSUserNotification) -> Bool {
    //implementation
    return true
}

当然,您需要设置代理委托。例如:

NSUserNotificationCenter.defaultUserNotificationCenter().delegate = self;

1
当您想要扩展UIViewController时会发生什么,例如,在Objective-C中,您可以有像这样的东西@interface MyCustomClass: UIViewController <ClassIWantToUseDelegate>,允许您初始化/配置视图控制器,并在子视图上调用委托方法?类似于这个的东西。 - Mahmud Ahmad
1
嗨,亚当,我有一个快速问题,如果我不能实例化一个对象因为它是一个通用类,而我在另一个类中没有访问权限,那么我该如何设置delegate = self呢?然而,我希望通用类能够调用另一个类中的函数,因此需要使用delegate。 - Marin

48

我对 @MakeAppPie 的帖子有几个修改意见。

首先,在创建委托协议时,它应该符合 Class 协议。就像下面的例子一样。

protocol ProtocolDelegate: class {
    func myMethod(controller:ViewController, text:String)
}

其次,你的代理应该是弱引用,以避免出现循环引用。

class ViewController: UIViewController {
    weak var delegate: ProtocolDelegate?
}

最后,由于您的协议是可选值,所以您是安全的。这意味着它的 "nil" 消息不会发送到此属性。这类似于在 objC 中使用 respondToselector 的条件语句,但这里您可以在一行中完成所有操作:

if ([self.delegate respondsToSelector:@selector(myMethod:text:)]) {
    [self.delegate myMethod:self text:@"you Text"];
}

上面是一个 Obj-C 示例,下面是一个 Swift 示例,展示了它的外观。

delegate?.myMethod(self, text:"your Text")

你很安全,因为你的协议是一个可选值。因为你使用了可选链式调用 delegate?.myMethod,如果 delegate 是 nil,那么什么也不会发生,程序也不会崩溃。然而,如果你犯了错误,写成了 delegate!.myMethod,如果没有设置委托,你就有可能会崩溃,所以这基本上是一种让你更加安全的方式... - mfaani

35

这里是我整理的代码片段。我有同样的疑问,这对我的理解有所帮助。在Xcode Playground中打开它,看看发生了什么。

protocol YelpRequestDelegate {
    func getYelpData() -> AnyObject
    func processYelpData(data: NSData) -> NSData
}

class YelpAPI {
    var delegate: YelpRequestDelegate?

    func getData() {
        println("data being retrieved...")
        let data: AnyObject? = delegate?.getYelpData()
    }

    func processYelpData(data: NSData) {
        println("data being processed...")
        let data = delegate?.processYelpData(data)
    }
}

class Controller: YelpRequestDelegate {
    init() {
        var yelpAPI = YelpAPI()
        yelpAPI.delegate = self
        yelpAPI.getData()
    }
    func getYelpData() -> AnyObject {
        println("getYelpData called")
        return NSData()
    }
    func processYelpData(data: NSData) -> NSData {
        println("processYelpData called")
        return NSData()
    }
}

var controller = Controller()

喜欢这个。非常有帮助。 - Adrienne
@SeeMeCode 你好,首先这是一个很好的例子,但我仍然有一个问题。我该如何使我的任何UIViewController类符合我们创建的委托协议呢? 它们必须在一个Swift文件中声明吗?任何帮助都将意义重大。 - Faruk
@Faruk 我发表这篇文章已经有一段时间了,但我认为你在问的应该很简单(如果我误解了,请原谅)。只需在冒号后将代理添加到您的UIViewController中。所以类似这样:class ViewController:UIViewController NameOfDelegate - SeeMeCode
@SeeMeCode,没错,你理解了我的问题。顺便说一下,我尝试了你的建议,但是当我在a.swift中根据你上面的答案创建一个委托类时,它在b.swift中没有出现。我无法访问我的Swift文件之外的任何类。有什么想法吗? - Faruk
我不明白的一件事是,为什么我要创建一个新的YelpApi实例,只是为了调用YelpApi的代理?如果正在运行的实例与我刚刚创建的“新”实例不同...它如何知道哪个代理属于哪个YelpApi实例? - Marin

18

SWIFT 2中的代理

我将通过两个视图控制器的代理示例进行说明。在这种情况下,SecondVC对象将数据发送回第一个视图控制器。

带有协议声明的类

protocol  getDataDelegate  {
    func getDataFromAnotherVC(temp: String)
}


import UIKit
class SecondVC: UIViewController {

    var delegateCustom : getDataDelegate?
    override func viewDidLoad() {
        super.viewDidLoad()
     }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    @IBAction func backToMainVC(sender: AnyObject) {
      //calling method defined in first View Controller with Object  
      self.delegateCustom?.getDataFromAnotherVC(temp: "I am sending data from second controller to first view controller.Its my first delegate example. I am done with custom delegates.")
        self.navigationController?.popViewControllerAnimated(true)
    }
    
}

在第一个视图控制器中,协议的符合性是在这里完成的:

class ViewController: UIViewController, getDataDelegate

在第一个视图控制器(ViewController)中,协议方法的定义

func getDataFromAnotherVC(temp : String)
{
  // dataString from SecondVC
   lblForData.text = dataString
}

从第一个视图控制器(ViewController)推送SecondVC时

let objectPush = SecondVC()
objectPush.delegateCustom = self
self.navigationController.pushViewController(objectPush, animated: true)

你最后的三行代码帮助我理解了我的情况并解决了我的问题。谢谢,伙计!:) - iHarshil
为男性工作..... - M Hamayun zeb

6

第一类:

protocol NetworkServiceDelegate: class {
        
    func didCompleteRequest(result: String)
}


class NetworkService: NSObject {

    weak var delegate: NetworkServiceDelegate?
    
    func fetchDataFromURL(url : String) {
        delegate?.didCompleteRequest(result: url)
    }
}

第二类:

class ViewController: UIViewController, NetworkServiceDelegate {
    
    let network = NetworkService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        network.delegate = self
        network.fetchDataFromURL(url: "Success!")
    }

 
    
    func didCompleteRequest(result: String) {
        print(result)
    }


}

编译上述代码时出现错误“类型'ViewController'不符合协议'NetworkServiceDelegate'”,请建议。这是我学习Swift的第6天 :) - Vaibhav Saran

4

非常简单的步骤(100%有效且经过测试)

步骤1:在第一个视图控制器上创建方法

 func updateProcessStatus(isCompleted : Bool){
    if isCompleted{
        self.labelStatus.text = "Process is completed"
    }else{
        self.labelStatus.text = "Process is in progress"
    }
}

步骤2:在推到第二个视图控制器时设置委托

@IBAction func buttonAction(_ sender: Any) {

    let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! secondViewController
    secondViewController.delegate = self
    self.navigationController?.pushViewController(secondViewController, animated: true)
}

步骤三:设置委托,如下所示:

class ViewController: UIViewController, ProcessStatusDelegate {

步骤四:创建协议。

protocol ProcessStatusDelegate:NSObjectProtocol{
func updateProcessStatus(isCompleted : Bool)
}

步骤5:取一个变量

var delegate:ProcessStatusDelegate?

步骤6:返回到前一个视图控制器时,调用委托方法,以便第一个视图控制器使用数据通知。

@IBAction func buttonActionBack(_ sender: Any) {
    delegate?.updateProcessStatus(isCompleted: true)
    self.navigationController?.popViewController(animated: true)
}

@IBAction func buttonProgress(_ sender: Any) {
    delegate?.updateProcessStatus(isCompleted: false)
    self.navigationController?.popViewController(animated: true)

}

3

以下是Delegate的简单代码示例:

//MARK: - Protocol ShowResult
protocol ShowResult: AnyObject {
       func show(value: Int) 
  }

//MARK: - MyOperation Class
class MyOperation {
weak var delegate: ShowResult?

func sum(fNumber: Int, sNumber: Int) {
    delegate?.show(value: fNumber + sNumber)
  }
}

//MARK: - ViewController Class
class ViewController: UIViewController,ShowResult {
var myOperation: MyOperation?

override func viewDidLoad() {
    super.viewDidLoad()
    loadMyOperation()
    myOperation?.delegate = self
    myOperation?.sum(fNumber: 100, sNumber: 20)
 }

private func loadMyOperation() {
    if myOperation == nil {
        myOperation = MyOperation()
     }
 }

func show(value: Int) {
    print("value: \(value)")
   }
}

3

简单示例:

protocol Work: class {
    func doSomething()
}

class Manager {
    weak var delegate: Work?
    func passAlong() {
        delegate?.doSomething()
    }
}

class Employee: Work {
    func doSomething() {
        print("Working on it")
    }
}

let manager = Manager()
let developer = Employee()
manager.delegate = developer
manager.passAlong() // PRINTS: Working on it

为什么在协议描述中使用关键字“class”?使用和不使用有什么区别? - Volodymyr
2
class关键字表示这是一个仅限于类的协议。通过添加class关键字,您可以将协议采用限制为类类型,而不是结构体或枚举。我可能不应该添加它以避免任何混淆,但既然你问了,我会保留它。 - Bobby

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接