【问题标题】:Swift runtime - calling superclass methodSwift 运行时 - 调用超类方法
【发布时间】:2019-01-02 06:53:39
【问题描述】:

我正在运行时创建 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));

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

【问题讨论】:

    标签: swift objective-c-runtime


    【解决方案1】:

    As Martin says, 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 = class_getMethodImplementation_stret(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和non-stret。

    例如:

    let impl = method_getImplementation(class_getInstanceMethod(superclass, selector)!)
    

    但请记住,the documentation notes:

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

    【讨论】:

    • 作为补充说明: objc_msgSend(Super) 在 Swift 中不可用,因为它需要一个变量参数列表。这个问题可能有解决方法,但您的方法看起来更简单、更安全:您不必考虑 objc_msgSendSuper 与 objc_msgSendSuper_stret 与 objc_msgSendSuper_fpret ...
    • @MartinR 是的——尽管如此,如果被调用的方法返回了一个结构,你将不得不使用class_getMethodImplementation_stret(这似乎是因为它可以返回一个 thunk for在对象不响应选择器的情况下进行消息转发)。尽管如果您改用class_getMethodImplementation,看起来这种区别是自动进行的。我会在我的回答中添加注释。
    • 我没有注意到 class_getMethodImplementation 也有这些变体......
    • @MartinR 奇怪的是没有class_getMethodImplementation_fpret¯\_(ツ)_/¯
    • (与这个问题无关:你看过stackoverflow.com/q/54283053/1187415?
    猜你喜欢
    • 2018-11-17
    • 1970-01-01
    • 2011-03-28
    • 1970-01-01
    • 2012-12-30
    • 2011-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多