Как завладеть init в Swift
Я следую за Swift и Objective-C Runtime, он работает для обычных методов.
Мне нравится swizzle метод init, насколько я понимаю, init похож на метод класса. Так что я попробовал swizzling init как метод экземпляра и класса. Но это не похоже на работу
Я могу заставить его работать с помощью Objective C, просто интересно, как заставить это работать в Swift
Выдержка из моей сути
dispatch_once(&Static.token) {
let originalSelector = Selector("init:source:destination:")
let swizzledSelector = Selector("ftg_init:source:destination:")
let originalMethod = class_getClassMethod(self, originalSelector)
let swizzledMethod = class_getClassMethod(self, swizzledSelector)
let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
1 ответ
При создании селектора для метода вы должны основывать его на сигнатуре метода Obj C, так как Swizzling выполняется с использованием среды выполнения Obj C.
Таким образом, оригинальный селектор должен быть
initWithIdentifier:source:destination:
Теперь это становится немного странным, так как ваш метод init определен так, что необходима метка в первом аргументе (имея identifier
дважды), селектор, который вы хотите использовать, на самом деле
ftg_initWithIdentifier:source:destination:
Единственная документация, которую я мог найти об этом, говорит о переводе с Obj C на Swift, но похоже, что обратное происходит со Swift на Obj C.
Следующий, init...
это метод экземпляра, поэтому вам нужно сделать два изменения. Вам нужно изменить class_getClassMethod
в class_getInstanceMethod
и вам нужно удалить class
от твоего ft_init...
метод.
Поэтому, когда все сказано и сделано, ваш код должен выглядеть следующим образом (что сработало для меня)
dispatch_once(&Static.token) {
let originalSelector = Selector("initWithIdentifier:source:destination:")
let swizzledSelector = Selector("ftg_initWithIdentifier:source:destination:")
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
func ftg_init(identifier identifier: String!,
source: UIViewController,
destination: UIViewController) -> UIStoryboardSegue {
return ftg_init(identifier: identifier,
source: source,
destination: destination.ftg_resolve())
}
Вот способ сделать это в Swift 4.2 для инициализатора UIImage:
@objc static func ftg_imageNamed(_ name: String) -> UIImage? {
...
}
private static func swizzleInitImplementation() {
let originalSelector = #selector(UIImage.init(named:))
let swizzledSelector = #selector(UIImage.ftg_imageNamed(_:))
guard let originalMethod = class_getClassMethod(self, originalSelector), let swizzledMethod = class_getClassMethod(self, swizzledSelector) else {
assertionFailure("The methods are not found!")
return
}
let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}