Method Swizzling — runtime technika, při které si implementace dvou metod třídy vyměňují místa během provádění. Umožňuje přepsat nebo rozšířit chování systémové metody bez vytváření podtřídy a bez změny zdrojového kódu. Největší uplatnění technika našla ve vývoji iOS s Objective-C, ale analogy existují v Kotlin/Android pomocí reflection. Podle NSHipster Guide by Mattt, 2024 je swizzling jedním z nejmocnějších, ale také nejnebezpečnějších mechanismů Objective-C Runtime.
Hlavní body
Method Swizzling — runtime technika, která vyměňuje implementace dvou metod Objective-C za běhu. Po swizzlingu vede volání originalSelector k provedení kódu swizzledSelector a naopak. To je možné díky architektuře Objective-C Runtime, kde je každý selektor (SEL) propojen s implementací (IMP) prostřednictvím dispatch table — tabulky, kterou lze za běhu měnit.
Termín «swizzling» byl zaveden v komunitě Cocoa vývojářů na počátku 2000 let. Technika získala širokou popularitu díky knihovnám: AFNetworking (swizzling UIWebView pro sledování načítání), Aspects (AOP rámec založený na swizzlingu) a FLEX (nástroj pro ladění, který swizzluje systémové metody pro inspekci). Dnes je swizzling používán ve většině iOS aplikací implicitně — prostřednictvím knihoven pro monitorování a analytiku.
Důležitá vlastnost swizzlingu — globalita: výměna implementace probíhá na úrovni třídy, nikoli instance. Pokud knihovna swizzluje metodu UIViewController.viewDidLoad, ovlivňuje to VŠECHNY instance UIViewController v aplikaci, včetně těch systémových. To je zároveň síla swizzlingu — jeden řádek kódu mění chování celé aplikace — a hlavní zdroj chyb.
Objective-C Runtime ukládá v každé třídě dispatch table — slovník, kde klíčem je SEL (identifikátor metody) a hodnotou je IMP (ukazatel na funkci implementace). Když aplikace posílá zprávu objektu, objc_msgSend provádí lineární vyhledávání v této tabulce. Method Swizzling nahrazuje IMP jednoho SEL za IMP jiného SEL, čímž přesměruje volání.
// Implementace bezpečného method swizzlingu
@implementation NSObject (SafeSwizzle)
+ (void)swizzleClassMethod:(SEL)original
with:(SEL)swizzled {
Class cls = [self class];
SEL originalSel = original;
SEL swizzledSel = swizzled;
Method originalMethod = class_getInstanceMethod(cls, originalSel);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
@end
Klíčová funkce — method_exchangeImplementations(Method, Method). Ta atomicky vyměňuje IMP dvou objektů Method. Po zavolání je dispatch table třídy změněn: při přístupu k original se provádí swizzled kód, při přístupu k swizzled se provádí original kód. Kategorie SafeSwizzle přidává tuto metodu všem NSObject, což umožňuje jakékoli třídě provést swizzling.
Bezpečná implementace swizzlingu vyžaduje volání původní implementace uvnitř swizzled verze. V opačném případě je původní chování metody nenávratně ztraceno. Správný vzor — uložit původní IMP před výměnou a volat jej ve swizzled metodě:
// Swizzling s voláním původní implementace
- (void)swizzled_viewDidLoad {
// 1. Volání původní implementace
[self swizzled_viewDidLoad];
// 2. Další logika po původním volání
NSLog("viewDidLoad proveden, swizzling aktivní");
}
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleClassMethod:@selector(viewDidLoad)
with:@selector(swizzled_viewDidLoad)];
});
}
dispatch_once zaručuje, že swizzling proběhne přesně jednou za dobu života aplikace. Opakovaný swizzling stejné metody povede k nekonečné rekurzi: swizzled metoda bude volat samu sebe. +load je volán při načtení třídy do runtime — to je bezpečný bod pro swizzling, který se provádí před hlavním kódem aplikace.
Dispatch table třídy Objective-C — pole struktur method_t obsahujících SEL, IMP a typ návratové hodnoty. method_exchangeImplementations jednoduše vyměňuje dva ukazatele IMP v této tabulce. Důležité: swizzling funguje pouze na úrovni třídy, nikoli protokolu. Pokud je metoda definována v protokolu, ale není implementována — dispatch table neobsahuje záznam pro swizzling.
Režie swizzlingu je minimální — výměna dvou ukazatelů IMP v dispatch table trvá několik nanosekund. Po swizzlingu se volání metody nezpomaluje: objc_msgSend najde IMP ve stejném čase O(1) jako před swizzlingem. Jediná další operace — kontrola method cache při prvním volání po výměně. Podle Apple Performance Team swizzling neovlivňuje výkon aplikace.
Method Swizzling se používá ve třech hlavních scénářích: monitorování a analytika (sledování viewDidLoad, viewDidAppear pro automatické odesílání událostí), AOP záchyt (logování parametrů všech volání metody) a hotfix (oprava chyby v production bez App Store Review pomocí knihoven jako JSPatch).
Každý z těchto scénářů funguje díky tomu, že je swizzling aplikován centralizovaně. Knihovna analytiky provede swizzling jednou v +load a všechny UIViewController v aplikaci začnou odesílat události. Vývojář nemusí přidávat kód do každého kontroleru — to snižuje duplicitu a riziko chyb.
Na Androidu method swizzling v klasickém smyslu Objective-C není možný — Java/Kotlin používají statickou dispečerizaci přes vtable. Existují však mechanismy, které dosahují podobného účinku: Java Reflection pro náhradu implementace za běhu a Gradle Transform API / ASM pro úpravu bytekódu ve fázi sestavení.
// Swizzling na Androidu přes reflection + companion object
class Logger {
companion object {
var originalImpl: (() -> Unit)? = null
}
fun log() {
println("původní log")
}
}
// Náhrada implementace v runtime přes reflection
fun swizzleLog() {
val originalMethod = Logger::class.java
.getDeclaredMethod("log")
originalMethod.isAccessible = true
Logger.originalImpl = {
originalMethod.invoke(Logger())
}
// Náhrada přes inline funkci
println("swizzled: log zachycen")
}
Tento kód nahrazuje chování metody log() pomocí Java Reflection: getDeclaredMethod získá přístup k soukromé implementaci, isAccessible vypne kontrolu přístupu. Místo přímého volání log() se volá obal, který provádí další logiku. Android však optimalizuje horké metody pomocí JIT — reflection nemusí fungovat na již zkompilovaných částech AOT.
Spolehlivější přístup — bytecode manipulation pomocí Gradle Transform API nebo AGP (Android Gradle Plugin) s knihovnou ASM. Úprava bytekódu se provádí ve fázi kompilace: ASM přidává volání do každé metody třídy. Tak fungují nástroje pro pokrytí kódu (JaCoCo) a monitorování výkonu (Firebase Performance Monitoring).
Method Swizzling — technika s vysokými riziky. Konflikty mezi knihovnami: pokud dvě knihovny swizzlují stejnou metodu, pořadí provádění není zaručeno. Nekompatibilita s aktualizacemi iOS: pokud Apple změní signaturu nebo odstraní metodu v nové verzi iOS, swizzling vede k pádu. Neviditelnost v kódu: swizzling není viditelný v implementaci třídy, což komplikuje ladění.
| Riziko | Popis | Mitigace |
|---|---|---|
| Konflikt knihoven | Dvě knihovny swizzlují viewDidAppear — jedna rozbíjí druhou | Zkontrolovat, zda je metoda již swizzlována přes class_getInstanceMethod |
| Rekurze | Opakovaný swizzling stejné metody způsobí nekonečnou smyčku | Vždy používat dispatch_once |
| Změna signatury | Apple mění signaturu metody v novém iOS — IMP nesedí | Testovat na všech podporovaných verzích iOS |
| Neviditelnost | Swizzling se nezobrazuje v zásobníku volání Xcode | Dokumentovat všechny operace swizzlingu v kódu |
| App Review | Apple odmítá aplikace s nedokumentovaným swizzlingem | Používat pouze veřejná API a dokumentovat účel |
Best practices pro bezpečný swizzling zahrnují: vždy volat původní implementaci, provádět swizzling přísně v +load přes dispatch_once, pojmenovávat swizzled metody s prefixem (např. s_originalMethodName), dokumentovat každou operaci swizzlingu s uvedením cíle. Knihovna Aspects řeší problém konfliktů prostřednictvím řetězového provádění bloků před/po původní metodě.
Alternativy k method swizzlingu jsou preferovanější pro production kód kvůli předvídatelnosti a bezpečnosti. Delegáti a protokoly (UIApplicationDelegate, UITableViewDelegate) poskytují explicitní body rozšíření bez úprav runtime. Podtřída — vytvoření podtřídy UIViewController s přepsáním viewDidAppear — funguje předvídatelně a nemá konflikty.
SwiftUI a Combine eliminují potřebu swizzlingu: modifikátory (onAppear, onChange) přidávají chování deklarativně, bez přepisování metod. Na Androidu dosahuje Jetpack Compose stejného efektu pomocí efektů (LaunchedEffect, SideEffect) a modifikátorů. AOP rámce (AspectJ pro Android, InterposeKit pro iOS) poskytují bezpečnou alternativu s compile-time weaving.
Podle Apple WWDC 2024 Swift runtime nepodporuje method swizzling na úrovni jazyka — @objc dynamic metody lze swizzlovat pouze přes Objective-C Runtime. Swift aplikace, které nepoužívají @objc, jsou plně chráněny před náhodným swizzlingem knihovnami třetích stran. To činí Swift bezpečnějším, ale omezuje možnosti runtime instrumentace.
SwiftUI modifikátory (onAppear, onChange, onReceive) a Jetpack Compose efekty (LaunchedEffect, SideEffect, DisposableEffect) zcela nahrazují swizzling pro úkoly UI. Poskytují deklarativní, předvídatelný a testovatelný způsob přidání průřezového chování bez úprav dispatch table. V nových projektech Apple a Google doporučují právě tento přístup místo runtime záchytu.
Často kladené otázky
Method Swizzling je přijatelný pro production při dodržení pravidel: dispatch_once pro jednorázové provedení, volání původní implementace, testování na všech verzích iOS a dokumentace. Pro jednoduché úkoly je lepší použít delegáty nebo podtřídu. Swizzling v production je oprávněný pro knihovny monitorování a analytiky.
Method Swizzling — konkrétní technika náhrady IMP v dispatch table. AOP (Aspect-Oriented Programming) — paradigma, ve kterém může být swizzling použit jako jeden z mechanismů. AOP zahrnuje také compile-time weaving (AspectJ), proxy-based záchyt (Spring AOP) a generování kódu.
Použijte breakpoint v objc_msgSend pro sledování všech zpráv. Přidejte symbolický breakpoint na method_exchangeImplementations s podmínkou na název třídy. Nástroj FLEX ukazuje, které metody třídy jsou swizzlovány. Pro systematickou kontrolu použijte lldb skript, který zobrazí dispatch table třídy.
Swift nepodporuje swizzling na úrovni jazyka. Method Swizzling funguje pouze pro metody označené @objc dynamic, které jsou kompilovány přes Objective-C Runtime. Čisté Swift metody (bez @objc) používají statickou dispečerizaci a nelze je swizzlovat — jejich dispatch table není k dispozici pro úpravy.
Firebase Analytics (swizzling viewDidAppear pro automatické sledování obrazovky), Amplitude, Mixpanel, FLEX (inspekce UI), OHHTTPStubs (mockování síťových požadavků), Aspects (AOP rámec). Všechny provádějí swizzling v +load přes dispatch_once s voláním původní implementace.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také