美文网首页Swiftswift首页投稿(暂停使用,暂停投稿)
实现Swift的委托(Delegate)的三种方式

实现Swift的委托(Delegate)的三种方式

作者: 阿影 | 来源:发表于2016-12-11 15:46 被阅读2614次

在多线程编程中,常常会遇到这样一种场景:主界面开启一个新线程,在新线程执行过程中,需要调用主界面的某个方法(比如更新主界面的显示)。
我们可以使用通知来实现类似松耦合的对象之间的消息传递。不过我一般不喜欢使用通知,觉得它不太符合面向对象的思路。而且通知用得不好可能会造成意外Bug,比如接受通知的对象如果产生了多个副本(比如在iOS的UITableViewCell中),那就是一个坑。我更喜欢用委托来实现这一场景。

在Swift中,委托通常用如下方式实现:
首先定义一个接口:

protocol MyDelegate {
    func delegateNeedDo(strMessage:String) -> ()
}

然后在子线程的方法里我们要这样写:

class NewThread {
    var delegate:MyDelegate?
    
    func Execute() {
        delegate?.delegateNeedDo(strMessage: "Show something in main.")
    }
}

最后,主界面的ViewController需要实现MyDelegate。

class ViewController: UIViewController, MyDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        let nt = NewThread()
        nt.delegate = self
        DispatchQueue.global().async{
            nt.Execute()
        }
    }
    
    //当NewThread的Execute被执行后,此方法会被调用
    func delegateNeedDo(strMessage:String) {
        print(strMessage)          //Show something in main.
    }
}

这里的逻辑是,主界面将自己赋给了NewThread的一个变量,NewThread通过这个变量来调用主界面里的方法。这样两个类之间形成了关联关系。不过这有够麻烦的,我不过是想传递一个方法而已嘛,干嘛还要新建一个协议?

C#里就有专门的delegate关键字来声明委托。
我们不需要定义一个协议,子线程中直接声明代理即可:

public class NewThread
{
    public delegate void FormEventHandler(String strMessage);
    public FormEventHandler FormAction;

    public void Execute()
    {
        FormAction("Show something in main.");
    }
}

主界面上的调用如下:

//当NewThread的Execute被执行后,此方法会被调用
private void delegateNeedDo(String strMessage)
{
    Console.WriteLine(strMessage);        //Show something in main.
}

private void btnStart_Click(object sender, EventArgs e)
{
    NewThread nt = new NewThread();
    nt.FormAction = new NewThread.FormEventHandler(delegateNeedDo);

    Task.Factory.StartNew(nt.Execute);
}

C#的委托大致相当于函数指针。主界面把函数指针传递给子线程,子线程就可以直接调用这个函数了。
Objective-C里的Selector号称与函数指针很像,Swift里也有。不过这个东东保存的并不是函数地址,它只是保存了函数签名。这就意味着,我们在传递Selector的同时还是要传递函数所属的对象。

子线程这么写:

class NewThread {
    var Sender:NSObject?
    var sel:Selector?
    
    func Execute() {
        if sel != nil {
            _ = Sender?.perform(sel, with: "Show something in main.")
        }
    }
}

然后在主界面上:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let nt = NewThread()
        nt.Sender = self
        nt.sel = #selector(ViewController.doSomethingInMain(strMessage:))
        
        DispatchQueue.global().async{
            nt.Execute()
        }
    }
    
    func doSomethingInMain(strMessage:String) {
        print(strMessage)          //Show something in main.
    }
}

如果在NewThread里也有一个一模一样的doSomethingInMain方法,并且在Execute()执行self.perform(而不是Sender.perfom)那么调用的就是NewThread里的doSomethingInMain方法了。这里就可以看出Selector的局限性了。

这种方法,主界面和子线程之间依然是关联关系。只是我们可以不用写协议了。其实在Swift里,函数是一等类型,可以像变量一样传递,包括直接传递给函数。
让我们来实现第三种方案。
子线程:

class NewThread {
    var delegateFunc: ((String) -> ())?
    
    func Execute() {
        delegateFunc!("Show something in main.")
    }
}

主线程直接把需要执行的函数赋给NewThread实例的delegateFunc即可。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let nt = NewThread()
        nt.delegateFunc = doSomethingInMain
        
        DispatchQueue.global().async{
            nt.Execute()
        }
    }

    func doSomethingInMain(strMessage:String) {
        print(strMessage)          //Show something in main.
    }
}

由于函数定义有随意性,为了规范化,可以使用typealias

typealias ThreadLink = (_ strMessage: String) -> ()

然后改写两处定义

class NewThread3 {
    var delegateFunc: ThreadLink?
…………
    let doSomethingInMain: ThreadLink = {strMessage in
        print(strMessage)
    }

相关文章

网友评论

本文标题:实现Swift的委托(Delegate)的三种方式

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