在多线程编程中,常常会遇到这样一种场景:主界面开启一个新线程,在新线程执行过程中,需要调用主界面的某个方法(比如更新主界面的显示)。
我们可以使用通知来实现类似松耦合的对象之间的消息传递。不过我一般不喜欢使用通知,觉得它不太符合面向对象的思路。而且通知用得不好可能会造成意外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)
}