来自C ++ / Java / C#背景我希望在Swift中看到虚拟方法,但是阅读swift文档我没有提到虚拟方法。
我错过了什么?
答案 0 :(得分:17)
与C ++不同,没有必要在Swift中指定方法是虚拟的。编译器将解决以下哪个问题:
(性能指标当然取决于硬件)
Objective-C当然总是使用后者。 4.9ns开销通常不是问题,因为这将占整个方法执行时间的一小部分。但是,必要时开发人员可以无缝地回退到C或C ++。然而,在Swift中,编译器将分析哪个最快可以使用并尝试代表您做出决定,支持内联,静态和虚拟,但保留Objective-C互操作性的消息。可以用dynamic
标记方法以鼓励消息传递。
这样做的一个副作用是,动态调度提供的一些强大功能可能无法使用,因为之前可以假设任何Objective-C方法都是如此。动态分派用于方法拦截,而后者又用于:
上述功能的类型是late binding
语言提供的功能。请注意,虽然Java使用vtable dispatch进行方法调用,但它仍然被认为是一种后期绑定语言,因此具有上述功能,因为它具有虚拟机和类加载器系统,这是提供运行时检测的另一种方法。 “Pure”Swift(没有Objective-C interop)就像C ++一样,是一种带有静态调度的直接可执行编译语言,然后这些动态特性在运行时是不可能的。在ARC的传统中,我们可能会看到更多这类功能转向编译时间,这为“每瓦性能”提供了优势 - 这是移动计算中的一个重要考虑因素。
答案 1 :(得分:4)
所有方法都是虚拟的;但是你需要声明使用override
关键字覆盖基类中的方法:
重写
子类可以提供自己的实例自定义实现 它会的方法,类方法,实例属性或下标 否则从超类继承。这被称为重写。
要覆盖否则将继承的特征,您 使用
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);
请注意self
和visit()
的{{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 中是如何进行的。 我们有四种调度类型,如下(从最快到最慢):
让我们仔细看看动态分派。作为初步,您必须了解值 和引用类型 之间的区别。为了使这个答案保持合理的长度,假设一个实例是值类型,它会保留其数据的唯一副本。如果是引用类型,它会与所有其他实例共享数据的单个副本。 值类型和引用类型都支持静态调度。
然而,对于动态调度,您需要一个引用类型。这样做的原因是动态调度需要继承,而对于值类型不支持的继承,需要引用类型。
如何在 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 Observing 或 method 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)
因此,如果Swift文档没有说明虚拟方法,我的猜测是,就像在Objective-C中一样,没有。