Swift Language
Metod Swizzling
Sök…
Anmärkningar
När du använder metodswizzling i Swift finns det två krav som dina klasser / metoder måste uppfylla:
- Din klass måste utöka
NSObject
- Funktionerna du vill swizzla måste ha det
dynamic
attributet
För en fullständig förklaring av varför detta krävs, kolla in Använda Swift med kakao och Objekt-C :
Kräver dynamisk avsändning
Medan
@objc
attributet exponerar ditt Swift API för Objekt-C-runtime, garanterar det inte dynamisk skick av en egenskap, metod, subscript eller initialiserare. Swift-kompilatorn kan fortfarande avinstallera eller ansluta medlemstillträde för att optimera prestandan för din kod genom att kringgå Objekt-C-körtiden . När du markerar en medlemsdeklaration med dendynamic
modifieraren skickas alltid åtkomst till den medlemmen dynamiskt. Eftersom deklarationer markerade med dendynamic
modifieraren skickas med Objekt-C-körtiden är de implicit markerade med@objc
attributet.Det är sällan nödvändigt att kräva dynamisk avsändning. Du måste dock använda den
dynamic
modifieraren när du vet att implementeringen av en API ersätts vid körning . Till exempel kan du användamethod_exchangeImplementations
funktionen i objektiv-C-runtime för att byta ut implementeringen av en metod medan en app körs. Om Swift-kompilatorn anvisade implementeringen av metoden eller devirtualiserade åtkomst till den, skulle den nya implementeringen inte användas .
länkar
Utöka UIViewController och Swizzling viewDidLoad
I Objekt-C är metodswizzling processen att ändra implementeringen av en befintlig väljare. Detta är möjligt på grund av hur väljare mappas på ett sändningstabell eller en tabell med pekare till funktioner eller metoder.
Pure Swift-metoder skickas inte dynamiskt med Objekt-C-runtime, men vi kan fortfarande dra nytta av dessa trick på alla klasser som ärver från NSObject
.
Här kommer vi att utöka UIViewController
och swizzle viewDidLoad
att lägga till lite anpassad loggning:
extension UIViewController {
// We cannot override load like we could in Objective-C, so override initialize instead
public override static func initialize() {
// Make a static struct for our dispatch token so only one exists in memory
struct Static {
static var token: dispatch_once_t = 0
}
// Wrap this in a dispatch_once block so it is only run once
dispatch_once(&Static.token) {
// Get the original selectors and method implementations, and swap them with our new method
let originalSelector = #selector(UIViewController.viewDidLoad)
let swizzledSelector = #selector(UIViewController.myViewDidLoad)
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
// class_addMethod can fail if used incorrectly or with invalid pointers, so check to make sure we were able to add the method to the lookup table successfully
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
}
// Our new viewDidLoad function
// In this example, we are just logging the name of the function, but this can be used to run any custom code
func myViewDidLoad() {
// This is not recursive since we swapped the Selectors in initialize().
// We cannot call super in an extension.
self.myViewDidLoad()
print(#function) // logs myViewDidLoad()
}
}
Grunderna i Swift Swizzling
Låt oss byta implementering av methodOne()
och methodTwo()
i vår TestSwizzling
klass:
class TestSwizzling : NSObject {
dynamic func methodOne()->Int{
return 1
}
}
extension TestSwizzling {
//In Objective-C you'd perform the swizzling in load(),
//but this method is not permitted in Swift
override class func initialize()
{
struct Inner {
static let i: () = {
let originalSelector = #selector(TestSwizzling.methodOne)
let swizzledSelector = #selector(TestSwizzling.methodTwo)
let originalMethod = class_getInstanceMethod(TestSwizzling.self, originalSelector);
let swizzledMethod = class_getInstanceMethod(TestSwizzling.self, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
let _ = Inner.i
}
func methodTwo()->Int{
// It will not be a recursive call anymore after the swizzling
return methodTwo()+1
}
}
var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())
Grunderna i Swizzling - Objekt-C
Objektivt-C-exempel på svarrande UIViews initWithFrame:
-metod
static IMP original_initWithFrame;
+ (void)swizzleMethods {
static BOOL swizzled = NO;
if (!swizzled) {
swizzled = YES;
Method initWithFrameMethod =
class_getInstanceMethod([UIView class], @selector(initWithFrame:));
original_initWithFrame = method_setImplementation(
initWithFrameMethod, (IMP)replacement_initWithFrame);
}
}
static id replacement_initWithFrame(id self, SEL _cmd, CGRect rect) {
// This will be called instead of the original initWithFrame method on UIView
// Do here whatever you need...
// Bonus: This is how you would call the original initWithFrame method
UIView *view =
((id (*)(id, SEL, CGRect))original_initWithFrame)(self, _cmd, rect);
return view;
}