I'm creating a subclass of UIView in runtime and providing my implementation of the layoutSubviews
method for it. One imporant thing that I need to do is to perform super.layoutSubviews()
. In Objective-C I can do it using the objc_msgSendSuper
function:
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));
But objc_msgSendSuper
method is unavailable in Swift. What should I use for performing the same thing?
As Martin says, objc_msgSendSuper
isn't available in Swift because it's a C variadic function, which Swift doesn't import due to the lack of type safety.
One alternative is to use class_getMethodImplementation
in order to get a pointer to the function to call for a selector on a given class type. From there, you can cast it to a function type which Swift can call using unsafeBitCast
, taking care that the parameter and return types match up.
For example:
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
Note that like objc_msgSendSuper
this assumes that the return type bridged to Obj-C is layout compatible with a pointer. This is true in most cases (including yours), but wouldn't be true for a method returning a type such as CGRect
, which is represented in Obj-C using a C structure type.
For those cases, you would need to use class_getMethodImplementation_stret
instead:
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)
The distinction between class_getMethodImplementation
and class_getMethodImplementation_stret
is due to the difference in calling convention – a word sized type can be passed back through a register, however a structure of larger size needs to be passed back indirectly. This matters for class_getMethodImplementation
because it could pass back a thunk for message forwarding in the case where the object doesn't respond to the selector.
Another option is to use method_getImplementation
, which doesn't perform message forwarding and therefore doesn't need to distinguish between stret and non-stret.
For example:
let impl = method_getImplementation(class_getInstanceMethod(superclass, selector)!)
However bear in mind that the documentation notes:
class_getMethodImplementation
may be faster thanmethod_getImplementation(class_getInstanceMethod(cls, name))
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With