5

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?

Hamish
  • 78,605
  • 19
  • 187
  • 280
Ruslan Serebriakov
  • 640
  • 1
  • 4
  • 12

1 Answers1

4

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 than method_getImplementation(class_getInstanceMethod(cls, name)).

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • 1
    Just as an additional remark: objc_msgSend(Super) is not available in Swift because it takes a variable argument list. There may be workarounds for this problem, but your approach looks simpler and safer: you don't have to think about objc_msgSendSuper vs objc_msgSendSuper_stret vs objc_msgSendSuper_fpret ... – Martin R Jan 02 '19 at 12:42
  • @MartinR Yup – although that being said, you would have to use `class_getMethodImplementation_stret` instead if the method being called returned a structure (this appears to be due to the fact that it could return a thunk for message forwarding in the case where the object doesn't respond to the selector). Though it looks like this distinction is made automatically if you use `class_getMethodImplementation` instead. I'll add a note to my answer regarding this. – Hamish Jan 02 '19 at 13:40
  • I hadn't noticed that class_getMethodImplementation has those variants as well ... – Martin R Jan 02 '19 at 13:57
  • @MartinR What's weird is that there's no `class_getMethodImplementation_fpret` ¯\\_(ツ)_/¯ – Hamish Jan 02 '19 at 14:12
  • (Unrelated to this question: Did you have a look at https://stackoverflow.com/q/54283053/1187415?) – Martin R Jan 23 '19 at 10:31
  • @MartinR Yes! I was going to write an answer, but never got round to it (I have exams going on at the moment). Essentially the answer is https://lists.swift.org/pipermail/swift-evolution-announce/2017-June/000386.html though, if you want to go ahead and write an answer yourself (I don’t mind if you do) :) – Hamish Jan 23 '19 at 12:00
  • Thanks for the link. I “knew” that I had seen that somewhere but hadn't found it yet. I'm busy right now, I'll see if I can answer it later. – Good luck with your exams! – Martin R Jan 23 '19 at 12:07