Swift有动态调度和虚拟方法吗?

时间:2014-06-03 11:29:49

标签: virtual-method swift dynamic-dispatch

来自C ++ / Java / C#背景我希望在Swift中看到虚拟方法,但是阅读swift文档我没有提到虚拟方法。

我错过了什么?

6 个答案:

答案 0 :(得分:17)

与C ++不同,没有必要在Swift中指定方法是虚拟的。编译器将解决以下哪个问题:

(性能指标当然取决于硬件)

  • 内联方法:0 ns
  • 静态调度:< 1.1ns
  • 虚拟调度1.1ns(指定时为Java,C#或C ++)。
  • 动态调度4.9ns(如Objective-C)。

Objective-C当然总是使用后者。 4.9ns开销通常不是问题,因为这将占整个方法执行时间的一小部分。但是,必要时开发人员可以无缝地回退到C或C ++。然而,在Swift中,编译器将分析哪个最快可以使用并尝试代表您做出决定,支持内联,静态和虚拟,但保留Objective-C互操作性的消息。可以用dynamic标记方法以鼓励消息传递。

这样做的一个副作用是,动态调度提供的一些强大功能可能无法使用,因为之前可以假设任何Objective-C方法都是如此。动态分派用于方法拦截,而后者又用于:

  • 可可式财产观察员。
  • CoreData模型对象检测。
  • 面向方面编程

上述功能的类型是late binding语言提供的功能。请注意,虽然Java使用vtable dispatch进行方法调用,但它仍然被认为是一种后期绑定语言,因此具有上述功能,因为它具有虚拟机和类加载器系统,这是提供运行时检测的另一种方法。 “Pure”Swift(没有Objective-C interop)就像C ++一样,是一种带有静态调度的直接可执行编译语言,然后这些动态特性在运行时是不可能的。在ARC的传统中,我们可能会看到更多这类功能转向编译时间,这为“每瓦性能”提供了优势 - 这是移动计算中的一个重要考虑因素。

答案 1 :(得分:4)

所有方法都是虚拟的;但是你需要声明使用override关键字覆盖基类中的方法:

来自Swift Programming Guide

  

重写

     

子类可以提供自己的实例自定义实现   它会的方法,类方法,实例属性或下标   否则从超类继承。这被称为重写。

     

要覆盖否则将继承的特征,您   使用override关键字为您的覆盖定义添加前缀。这样做   澄清您打算提供覆盖并且未提供   错误的匹配定义。意外覆盖可能导致   意外行为以及没有override关键字的任何覆盖   在编译代码时被诊断为错误。

     

override关键字还会提示Swift编译器检查它   你的重写类的超类(或其父类之一)有一个   与您为覆盖提供的声明匹配的声明。这个   检查确保您的重写定义正确。

答案 2 :(得分:4)

class A {
    func visit(target: Target) {
        target.method(self);
    }
}

class B: A {}

class C: A {
    override func visit(target: Target) {
        target.method(self);
    }
}

class Target {
    func method(argument: A) {
        println("A");
    }

    func method(argument: B) {
        println("B");
    }

    func method(argument: C) {
        println("C");
    }
}

let t = Target();
let a:  A = A();
let ab: A = B();
let b:  B = B();
let ac: A = C();
let c:  C = C();

a.visit(t);
ab.visit(t);
b.visit(t);
ac.visit(t);
c.visit(t);

请注意selfvisit()的{​​{1}}中的A引用。就像在Java中一样,它不会被复制,而是C保持相同的类型,直到它再次用于覆盖。

结果是 A,A,A,C,C ,因此没有可用的动态调度。不幸的是

答案 3 :(得分:2)

让我们从定义动态分派开始。

动态调度被认为是面向对象语言的主要特征。 它是选择在运行时调用多态操作(方法/函数)的哪个实现的过程,according to Wikipedia。 我强调运行时间是有原因的,因为这是它与静态调度的区别。使用静态分派,在编译时解析对方法的调用。在 C++ 中,这是默认的调度形式。对于动态调度,该方法必须声明为 virtual

现在让我们来看看虚函数是什么以及它在 C++ 上下文中的行为

在 C++ 中,虚函数是在基类中声明并被派生类覆盖的成员函数。 它的主要特点是,如果我们有一个在基类中声明为虚拟的函数,并且在派生类中定义了相同的函数,那么派生类中的函数会被派生类的对象调用,即使它是使用对基类的引用。

考虑这个例子,taken from here

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

如果我们在 eat() 对象上调用 Cat,但我们使用指向 Animal 的指针,输出将是“我正在吃一只老鼠。

现在我们可以研究这一切在 Swift 中是如何进行的。 我们有四种调度类型,如下(从最快到最慢):

  1. 内联调度
  2. 静态调度
  3. 虚拟调度
  4. 动态调度

让我们仔细看看动态分派。作为初步,您必须了解引用类型 之间的区别。为了使这个答案保持合理的长度,假设一个实例是值类型,它会保留其数据的唯一副本。如果是引用类型,它会与所有其他实例共享数据的单个副本。 值类型和引用类型都支持静态调度。

然而,对于动态调度,您需要一个引用类型。这样做的原因是动态调度需要继承,而对于值类型不支持的继承,需要引用类型。

如何在 Swift 中实现动态调度?有两种方法可以做到。 第一种是使用继承:子类化一个基类,然后重写基类的一个方法。我们之前的 C++ 示例在 Swift 中看起来像这样:

class Animal {
    init() {
        print("Animal created.")
    }
    func eat() {
        print("I'm eating generic food.")
    }
}
class Cat: Animal {
    override init() {
        print("Cat created.")
    }
    override func eat() {
        print("I'm eating a rat.")
    }
}

如果您现在运行以下代码:

let cat = Cat()
cat.eat()

控制台输出将是:

Cat created.
Animal created.
I'm eating a rat.

如您所见,无需将基类方法标记为 virtual,编译器会自动决定使用哪个调度选项。

实现动态调度的第二种方法是使用 dynamic 关键字和 @objc 前缀。我们需要 @objc 将我们的方法暴露给 Objective-C 运行时,它完全依赖于动态分派。然而,Swift 只有在别无选择的情况下才会使用它。如果编译器可以在编译时决定使用哪个实现,它就会选择退出动态分派。我们可能会将 @objc dynamic 用于 Key-Value Observingmethod swizzling,这两者都超出了本答案的范围。

答案 4 :(得分:1)

从Xcode 8.x.x和9 Beta开始,C ++中的虚拟方法可能会在Swift 3和4中翻译成这样:

class CustomNavBar: UINavigationBar {
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = .red
    }
}

// In the viewController 

let navBar = CustomNavBar()

override func viewDidLoad() {
    super.viewDidLoad()
    setupNavBar()
}

func setupNavBar() {
    view.addSubview(navBar)
    navBar.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 80)
    let backButton = UIBarButtonItem(image: UIImage(named:"backThick"), style: .plain, target: self, action: #selector(popControllerOffStack))
    self.navigationItem.leftBarButtonItem = backButton
}

试一试。

protocol Animal: AnyObject {  // as a base class in C++; class-only protocol in Swift
  func hello()
}

extension Animal {  // implementations of the base class
  func hello() {
    print("Zzz..")
  }
}

class Dog: Animal {  // derived class with a virtual function in C++
  func hello() {
    print("Bark!")
  }
}

class Cat: Animal {  // another derived class with a virtual function in C++
  func hello() {
    print("Meow!")
  }
}

class Snoopy: Animal {  // another derived class with no such a function
  //
}

总而言之,我认为我们在C ++中所做的类似事情可以通过Swift中的协议 Generic 来实现。

我也来自C ++世界并面临同样的问题。上面似乎有效,但它看起来像C ++方式,但不是有点Swifty方式。

欢迎任何进一步的建议!

答案 5 :(得分:-1)

对于Objective-C程序员来说,Swift很容易学习,而在Objective-C中,没有虚拟方法,至少不是你想象的方式。如果您在SO上寻找有关如何在Objective-C中创建抽象类或虚拟方法的指令,通常它只是抛出异常并使应用程序崩溃的常规方法。 (哪种方式有道理,因为你不应该称之为虚拟方法)

因此,如果Swift文档没有说明虚拟方法,我的猜测是,就像在Objective-C中一样,没有。