Быстрая среда выполнения - вызов метода суперкласса - PullRequest
0 голосов
/ 02 января 2019

Я создаю подкласс 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.Что я должен использовать для выполнения того же самого?

1 Ответ

0 голосов
/ 02 января 2019

Как говорит Мартин , objc_msgSendSuper недоступно в Swift, поскольку это функция с переменным числом Си, которую Swift не импортирует из-за отсутствия безопасности типов.

Oneальтернативой является использование 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_getMethodImplementation и class_getMethodImplementation_stret связано с различием в соглашении о вызовах - тип размера слова может быть передан обратно череззарегистрировать, однако структура большего размера должна быть передана обратно косвенно.Это имеет значение для class_getMethodImplementation, потому что он может передать обратно thunk для пересылки сообщений в случае, когда объект не отвечает на селектор.

Другой вариант - использовать method_getImplementation, который не выполняетпересылка сообщений и, следовательно, не нужно различать растяжение и не растяжение.

Например:

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

Однако имейте в виду, что в документации отмечает :

class_getMethodImplementation может быть быстрее, чем method_getImplementation(class_getInstanceMethod(cls, name)).

...