Method Swizzling là một kỹ thuật runtime trong đó việc triển khai của hai phương thức lớp được hoán đổi cho nhau trong khi thực thi. Nó cho phép ghi đè hoặc bổ sung hành vi của một phương thức hệ thống mà không cần tạo lớp con hay sửa đổi mã nguồn. Kỹ thuật này được ứng dụng nhiều nhất trong phát triển iOS với Objective-C, nhưng các phương thức tương tự cũng tồn tại trong Kotlin/Android thông qua reflection. Theo NSHipster Guide by Mattt, 2024, swizzling là một trong những cơ chế mạnh mẽ nhất, nhưng cũng nguy hiểm nhất của Objective-C Runtime.
Những điểm chính
Method Swizzling là một kỹ thuật runtime hoán đổi triển khai của hai phương thức Objective-C. Sau khi swizzling, gọi originalSelector sẽ thực thi mã của swizzledSelector và ngược lại. Điều này có được nhờ kiến trúc của Objective-C Runtime, nơi mỗi bộ chọn (SEL) được liên kết với một triển khai (IMP) thông qua bảng điều phối — một bảng có thể được sửa đổi trong khi thực thi.
Thuật ngữ “swizzling” được giới thiệu trong cộng đồng nhà phát triển Cocoa vào đầu những năm 2000. Kỹ thuật này trở nên nổi tiếng nhờ các thư viện như: AFNetworking (swizzling UIWebView để theo dõi tải), Aspects (framework AOP dựa trên swizzling) và FLEX (công cụ gỡ lỗi swizzle các phương thức hệ thống để kiểm tra). Ngày nay, swizzling được sử dụng ngầm trong hầu hết các ứng dụng iOS — thông qua các thư viện giám sát và phân tích.
Một thuộc tính quan trọng của swizzling là tính toàn cục: việc thay thế triển khai xảy ra ở cấp lớp, không phải cấp thể hiện. Nếu một thư viện swizzle phương thức UIViewController.viewDidLoad, nó ảnh hưởng đến TẤT CẢ các thể hiện UIViewController trong ứng dụng, bao gồm cả của hệ thống. Đây vừa là sức mạnh của swizzling — một dòng mã thay đổi hành vi của toàn bộ ứng dụng — vừa là nguồn gốc chính của lỗi.
Objective-C Runtime lưu trữ trong mỗi lớp một bảng điều phối — một từ điển nơi khóa là SEL (định danh phương thức) và giá trị là IMP (con trỏ đến hàm triển khai). Khi một ứng dụng gửi tin nhắn đến một đối tượng, objc_msgSend thực hiện tìm kiếm tuyến tính trong bảng này. Method Swizzling thay thế IMP của một SEL bằng IMP của một SEL khác, chuyển hướng các lời gọi.
// Triển khai method swizzling an toàn
@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
Hàm chính là method_exchangeImplementations(Method, Method). Nó hoán đổi nguyên tử các IMP của hai đối tượng Method. Sau lời gọi, bảng điều phối của lớp bị sửa đổi: gọi original thực thi mã swizzled, gọi swizzled thực thi mã original. Danh mục SafeSwizzle thêm phương thức này vào tất cả NSObject, cho phép bất kỳ lớp nào thực hiện swizzling.
Một triển khai swizzling an toàn yêu cầu gọi triển khai gốc bên trong phiên bản đã swizzle. Nếu không, hành vi gốc của phương thức sẽ bị mất vĩnh viễn. Mẫu đúng là lưu IMP gốc trước khi hoán đổi và gọi nó trong phương thức đã swizzle:
// Swizzling với gọi triển khai gốc
- (void)swizzled_viewDidLoad {
// 1. Gọi triển khai gốc
[self swizzled_viewDidLoad];
// 2. Logic bổ sung sau lời gọi gốc
NSLog("viewDidLoad đã thực thi, swizzling đang hoạt động");
}
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleClassMethod:@selector(viewDidLoad)
with:@selector(swizzled_viewDidLoad)];
});
}
dispatch_once đảm bảo rằng swizzling thực thi đúng một lần trong suốt vòng đời của ứng dụng. Swizzling lại cùng một phương thức sẽ dẫn đến đệ quy vô hạn: phương thức đã swizzle sẽ gọi chính nó. +load được gọi khi lớp được tải vào runtime — đây là điểm an toàn cho swizzling, thực thi trước mã chính của ứng dụng.
Bảng điều phối của một lớp Objective-C là một mảng các cấu trúc method_t chứa SEL, IMP và kiểu trả về. method_exchangeImplementations chỉ đơn giản hoán đổi hai con trỏ IMP trong bảng này. Quan trọng: swizzling chỉ hoạt động ở cấp lớp, không phải cấp giao thức. Nếu một phương thức được định nghĩa trong giao thức nhưng không được triển khai, bảng điều phối không chứa mục nhập cho swizzling.
Chi phí của swizzling là tối thiểu — việc hoán đổi hai con trỏ IMP trong bảng điều phối mất vài nano giây. Sau khi swizzling, việc điều phối phương thức không bị chậm lại: objc_msgSend tìm IMP trong cùng thời gian O(1) như trước khi swizzling. Thao tác bổ sung duy nhất là kiểm tra bộ nhớ đệm phương thức ở lần gọi đầu tiên sau khi hoán đổi. Theo dữ liệu của Apple Performance Team, swizzling không ảnh hưởng đến hiệu suất ứng dụng.
Method Swizzling được sử dụng trong ba kịch bản chính: giám sát và phân tích (theo dõi viewDidLoad, viewDidAppear để tự động gửi sự kiện), chặn AOP (ghi nhật ký tham số của tất cả các lời gọi phương thức) và hotfix (sửa lỗi sản xuất mà không cần App Store Review thông qua các thư viện như JSPatch).
Mỗi kịch bản này hoạt động vì swizzling được áp dụng tập trung. Một thư viện phân tích thực hiện swizzling một lần trong +load, và tất cả các thể hiện UIViewController trong ứng dụng bắt đầu gửi sự kiện. Nhà phát triển không cần thêm mã vào mọi bộ điều khiển — điều này giảm sự trùng lặp và rủi ro lỗi.
Trên Android, method swizzling theo nghĩa cổ điển của Objective-C là không thể — Java/Kotlin sử dụng điều phối tĩnh qua vtable. Tuy nhiên, tồn tại các cơ chế đạt được hiệu ứng tương tự: Java Reflection để thay thế triển khai trong runtime và Gradle Transform API / ASM để sửa đổi bytecode tại thời điểm xây dựng.
// Swizzling trên Android qua reflection + companion object
class Logger {
companion object {
var originalImpl: (() -> Unit)? = null
}
fun log() {
println("nhật ký gốc")
}
}
// Thay thế triển khai trong runtime qua reflection
fun swizzleLog() {
val originalMethod = Logger::class.java
.getDeclaredMethod("log")
originalMethod.isAccessible = true
Logger.originalImpl = {
originalMethod.invoke(Logger())
}
// Thay thế qua hàm inline
println("swizzled: nhật ký đã bị chặn")
}
Mã này thay thế hành vi của phương thức log() thông qua Java Reflection: getDeclaredMethod truy cập triển khai riêng tư, isAccessible vô hiệu hóa kiểm tra truy cập. Thay vì gọi trực tiếp log(), một trình bao bọc được gọi để thực thi logic bổ sung. Tuy nhiên, Android tối ưu hóa các phương thức nóng qua JIT — reflection có thể không hoạt động trên các phân đoạn AOT đã được biên dịch.
Một cách tiếp cận đáng tin cậy hơn là thao tác bytecode thông qua Gradle Transform API hoặc AGP (Android Gradle Plugin) với thư viện ASM. Việc sửa đổi bytecode được thực hiện tại thời điểm biên dịch: ASM thêm các lời gọi vào mọi phương thức của lớp. Đây là cách các công cụ đo lường phủ mã (JaCoCo) và giám sát hiệu suất (Firebase Performance Monitoring) hoạt động.
Method Swizzling là một kỹ thuật có rủi ro cao. Xung đột giữa các thư viện: nếu hai thư viện swizzle cùng một phương thức, thứ tự thực thi không được đảm bảo. Không tương thích với các bản cập nhật iOS: nếu Apple thay đổi chữ ký hoặc xóa một phương thức trong phiên bản iOS mới, swizzling dẫn đến sập. Thiếu khả năng hiển thị trong mã: swizzling không hiển thị trong triển khai lớp, gây khó khăn cho việc gỡ lỗi.
| Rủi ro | Mô tả | Giảm thiểu |
|---|---|---|
| Xung đột thư viện | Hai thư viện swizzle viewDidAppear — một cái phá vỡ cái kia | Kiểm tra xem phương thức đã được swizzle chưa qua class_getInstanceMethod |
| Đệ quy | Swizzling lại cùng phương thức gây ra vòng lặp vô hạn | Luôn sử dụng dispatch_once |
| Thay đổi chữ ký | Apple thay đổi chữ ký phương thức trong iOS mới — IMP không khớp | Kiểm thử trên tất cả các phiên bản iOS được hỗ trợ |
| Không hiển thị | Swizzling không xuất hiện trong ngăn xếp cuộc gọi Xcode | Ghi lại tất cả các thao tác swizzling trong mã |
| App Review | Apple từ chối ứng dụng có swizzling không được ghi lại | Chỉ sử dụng API công khai và ghi lại mục đích |
Các thực hành tốt nhất để swizzling an toàn bao gồm: luôn gọi triển khai gốc, thực hiện swizzling nghiêm ngặt trong +load qua dispatch_once, đặt tên các phương thức đã swizzle với tiền tố (ví dụ: s_originalMethodName), ghi lại từng thao tác swizzling với mục đích của nó. Thư viện Aspects giải quyết vấn đề xung đột thông qua thực thi chuỗi các khối trước/sau phương thức gốc.
Các lựa chọn thay thế method swizzling được ưu tiên cho mã sản xuất vì tính dự đoán được và an toàn. Các delegate và giao thức (UIApplicationDelegate, UITableViewDelegate) cung cấp các điểm mở rộng rõ ràng mà không sửa đổi runtime. Tạo lớp con — tạo một lớp con của UIViewController ghi đè viewDidAppear — hoạt động dự đoán được và không có xung đột.
SwiftUI và Combine loại bỏ nhu cầu về swizzling: các bổ từ (onAppear, onChange) thêm hành vi một cách khai báo, mà không ghi đè phương thức. Trong Android Jetpack Compose đạt được điều tương tự thông qua các hiệu ứng (LaunchedEffect, SideEffect) và bổ từ. Các framework AOP (AspectJ cho Android, InterposeKit cho iOS) cung cấp một lựa chọn thay thế an toàn với tính năng dệt tại thời điểm biên dịch.
Theo dữ liệu từ Apple WWDC 2024, runtime Swift không hỗ trợ method swizzling ở cấp ngôn ngữ — các phương thức @objc dynamic chỉ có thể được swizzle thông qua Objective-C Runtime. Các ứng dụng Swift không sử dụng @objc được bảo vệ hoàn toàn khỏi swizzling ngẫu nhiên bởi các thư viện bên thứ ba. Điều này làm cho Swift an toàn hơn nhưng hạn chế khả năng đo lường runtime.
Các bổ từ SwiftUI (onAppear, onChange, onReceive) và các hiệu ứng Jetpack Compose (LaunchedEffect, SideEffect, DisposableEffect) thay thế hoàn toàn swizzling cho các tác vụ UI. Chúng cung cấp một cách khai báo, dự đoán được và có thể kiểm thử để thêm hành vi xuyên suốt mà không sửa đổi bảng điều phối. Trong các dự án mới, Apple và Google khuyến nghị cách tiếp cận này thay vì chặn runtime.
Các câu hỏi thường gặp
Method Swizzling có thể chấp nhận được cho sản xuất nếu tuân thủ các quy tắc: dispatch_once để thực thi một lần, gọi triển khai gốc, kiểm thử trên tất cả các phiên bản iOS và ghi lại tài liệu. Đối với các tác vụ đơn giản, tốt hơn nên sử dụng delegate hoặc tạo lớp con. Swizzling trong sản xuất được biện minh cho các thư viện giám sát và phân tích.
Method Swizzling là một kỹ thuật cụ thể để thay thế IMP trong bảng điều phối. AOP (Lập trình hướng khía cạnh) là một mô hình trong đó swizzling có thể được sử dụng như một trong các cơ chế. AOP cũng bao gồm dệt tại thời điểm biên dịch (AspectJ), chặn dựa trên proxy (Spring AOP) và tạo mã.
Sử dụng điểm dừng trong objc_msgSend để theo dõi tất cả các tin nhắn. Thêm điểm dừng tượng trưng trên method_exchangeImplementations với điều kiện về tên lớp. Công cụ FLEX hiển thị những phương thức nào của lớp đã bị swizzle. Để kiểm tra có hệ thống, sử dụng tập lệnh lldb xuất bảng điều phối của lớp.
Swift không hỗ trợ swizzling ở cấp ngôn ngữ. Method Swizzling chỉ hoạt động cho các phương thức được đánh dấu @objc dynamic, được biên dịch thông qua Objective-C Runtime. Các phương thức Swift thuần túy (không có @objc) sử dụng điều phối tĩnh và không thể bị swizzle — bảng điều phối của chúng không thể truy cập để sửa đổi.
Firebase Analytics (swizzling viewDidAppear để theo dõi màn hình tự động), Amplitude, Mixpanel, FLEX (kiểm tra UI), OHHTTPStubs (mô phỏng yêu cầu mạng), Aspects (framework AOP). Tất cả đều thực hiện swizzling trong +load qua dispatch_once với lời gọi đến triển khai gốc.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm