Swift运行时 - 调用超类方法

5

我正在运行时创建一个UIView的子类,并为其提供layoutSubviews方法的实现。其中一个重要的事情是执行super.layoutSubviews()。在Objective-C中,我可以使用objc_msgSendSuper函数来完成:

Class objectClass = object_getClass(object);
Class superclass = class_getSuperclass(objectClass);

struct objc_super superInfo;
superInfo.receiver = object;
superInfo.super_class = superclass;
typedef void *(*ObjCMsgSendSuperReturnVoid)(struct objc_super *, SEL);
ObjCMsgSendSuperReturnVoid sendMsgReturnVoid = (ObjCMsgSendSuperReturnVoid)objc_msgSendSuper;
sendMsgReturnVoid(&superInfo, @selector(layoutSubviews));

但是在Swift中,objc_msgSendSuper方法不可用。我应该使用什么来执行相同的操作?

1个回答

5

正如Martin所说的那样, objc_msgSendSuper 在Swift中不可用,因为它是一个C变参函数,而Swift由于缺乏类型安全性而无法导入。

其中一种替代方法是使用class_getMethodImplementation来获取在给定类类型上调用选择器的函数指针。从那里,您可以将其强制转换为Swift可以使用unsafeBitCast调用的函数类型,确保参数和返回类型匹配。

例如:

import Foundation

class C {
  @objc func foo() {
    print("C's foo")
  }
}

class D : C {
  override func foo() {
    print("D's foo")
  }
}

let d = D()

let superclass: AnyClass = class_getSuperclass(type(of: d))!
let selector = #selector(C.foo)

// The function to call for a message send of "foo" to a `C` object.
let impl = class_getMethodImplementation(superclass, selector)!

// @convention(c) tells Swift this is a bare function pointer (with no context object)
// All Obj-C method functions have the receiver and message as their first two parameters
// Therefore this denotes a method of type `() -> Void`, which matches up with `foo`
typealias ObjCVoidVoidFn = @convention(c) (AnyObject, Selector) -> Void

let fn = unsafeBitCast(impl, to: ObjCVoidVoidFn.self)
fn(d, selector) // C's foo

请注意,与objc_msgSendSuper类似,这假定返回类型在Obj-C中的桥接与指针具有相同的布局兼容性。 在大多数情况下(包括您的情况),这是正确的,但对于返回CGRect等类型的方法,这不是正确的,因为它在Obj-C中使用C结构类型表示。
对于这些情况,您需要改用class_getMethodImplementation_stret
import Foundation

class C {
  @objc func bar() -> CGRect {
    return CGRect(x: 2, y: 3, width: 4, height: 5)
  }
}

class D : C {
  override func bar() -> CGRect {
    return .zero
  }
}

let d = D()

let superclass: AnyClass = class_getSuperclass(type(of: d))!
let selector = #selector(C.bar)
let impl = <b>class_getMethodImplementation_stret</b>(superclass, selector)!

typealias ObjCVoidVoidFn = @convention(c) (AnyObject, Selector) -> CGRect

let fn = unsafeBitCast(impl, to: ObjCVoidVoidFn.self)
let rect = fn(d, selector)
print(rect) // (2.0, 3.0, 4.0, 5.0)
class_getMethodImplementationclass_getMethodImplementation_stret之间的区别在于调用约定的不同 - 一个字大小的类型可以通过寄存器传回,但是大于此大小的结构需要通过间接方式传回。这对于class_getMethodImplementation很重要,因为在对象不响应选择器的情况下,它可能会传回消息转发的thunk。
另一个选择是使用method_getImplementation,它不执行消息转发,因此不需要区分stret和非stret。
例如:
let impl = method_getImplementation(class_getInstanceMethod(superclass, selector)!)

然而请记住文档注释

class_getMethodImplementation 可能比 method_getImplementation(class_getInstanceMethod(cls, name)) 更快。


1
仅作补充说明:objc_msgSend(Super)在Swift中不可用,因为它采用可变参数列表。可能有解决此问题的方法,但您的方法看起来更简单、更安全:您不必考虑objc_msgSendSuper vs objc_msgSendSuper_stret vs objc_msgSendSuper_fpret... - Martin R
@MartinR 是的 - 尽管如此,如果被调用的方法返回一个结构体,则必须使用 class_getMethodImplementation_stret(这似乎是因为在对象不响应选择器的情况下,它可能会返回一个消息转发的 thunk)。虽然如果您使用 class_getMethodImplementation,则似乎会自动进行此区分。我将在我的答案中添加一条注释以说明这一点。 - Hamish
(与此问题无关:您是否查看了https://dev59.com/wrHma4cB1Zd3GeqPIkHt?) - Martin R
@MartinR 是的!我本来打算写个答案,但一直没时间(我现在正在参加考试)。基本上答案就是https://lists.swift.org/pipermail/swift-evolution-announce/2017-June/000386.html,如果你想自己写个答案的话(我不介意你这么做):) - Hamish
谢谢分享链接。我“知道”我之前在某个地方看到过,但一直没找到。我现在有点忙,等有时间了再回答你。祝你考试好运! - Martin R
显示剩余2条评论

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