@interface MyView : NSView @end
@implementation MyView
- (void)drawRect:(NSRect)r { /* 自定義繪製 */ }
@end
/* 必須控制實例化:[[MyView alloc] init] */
❌ 無法影響現有實例或框架內部建立的實例
子類偽裝成父類,Runtime 將所有父類訊息轉發給子類。
❌ 64-bit / iOS 不支援,已廢棄。
@implementation NSView (MyOverride)
- (void)drawRect:(NSRect)r {
[[NSColor blueColor] set];
NSRectFill(r);
// ❌ 無法呼叫原始實現
// ❌ 多個 Category 重寫同一方法 → 行為未定義
}
@end
核心思想:用新選擇器實現替換原選擇器,並保留原實現供呼叫。
prefix_original)@implementation NSView (Swizzle)
- (void)my_drawRect:(NSRect)r {
/* 呼叫原實現:此時 my_drawRect 已與 drawRect 交換,
所以呼叫 my_drawRect 實際執行原始 drawRect */
[self my_drawRect:r];
[[NSColor blueColor] set];
NSRectFill(r);
}
@end
#import <objc/runtime.h>
void MethodSwizzle(Class c, SEL orig, SEL override) {
Method origMethod = class_getInstanceMethod(c, orig);
Method overrideMethod = class_getInstanceMethod(c, override);
/* 情況 A:原方法未在類中實現(繼承自父類) */
if (class_addMethod(c, orig,
method_getImplementation(overrideMethod),
method_getTypeEncoding(overrideMethod))) {
/* 成功添加 → 類中現有 orig 指向新實現
再將 override 指向原父類實現 */
class_replaceMethod(c, override,
method_getImplementation(origMethod),
method_getTypeEncoding(origMethod));
}
/* 情況 B:原方法已在類中實現 */
else {
method_exchangeImplementations(origMethod, overrideMethod);
}
}
/* 在 +load 中自動執行 */
+ (void)load {
MethodSwizzle(self, @selector(drawRect:), @selector(my_drawRect:));
}
| API | 用途 |
|---|---|
class_getInstanceMethod | 獲取 Method 結構體(含 IMP、type encoding) |
class_addMethod | 添加新方法;若已存在返回 NO(用於檢測情況 A/B) |
class_replaceMethod | 替換現有方法實現 |
method_exchangeImplementations | 交換兩個 Method 的 IMP |
method_getImplementation | 取得函數指標 (IMP) |
method_getTypeEncoding | 取得型別編碼字串(如 "v@:{CGRect={CGPoint=dd}{CGSize=dd}}") |
不依賴選擇器交換,改用全域函數指標保存原 IMP。
/* 全域儲存原始 IMP */
static void (*gOrigDrawRect)(id, SEL, NSRect);
+ (void)load {
Method m = class_getInstanceMethod(self, @selector(drawRect:));
gOrigDrawRect = (void*)method_getImplementation(m);
/* 直接替換:class_replaceMethod 內部處理 add/replace */
class_replaceMethod(self, @selector(drawRect:),
(IMP)OverrideDrawRect,
method_getTypeEncoding(m));
}
/* C 風格函數作為新實現 */
static void OverrideDrawRect(NSView *self, SEL _cmd, NSRect r) {
gOrigDrawRect(self, _cmd, r); // 呼叫原實現
[[NSColor blueColor] set];
NSRectFill(r);
}
| 維度 | Swizzling | 直接 Override |
|---|---|---|
| 概念複雜度 | 高(選擇器交換、遞迴呼叫假象) | 低(函數指標直觀) |
| 多重 Swizzling 安全性 | 需注意順序、鏈式呼叫 | 各自保存原 IMP,互不干擾 |
| 除錯體驗 | 堆疊顯示交換後選擇器 | 堆疊顯示 C 函數名 |
| 適用場景 | 框架級、需與其他 Swizzling 共存 | 應用層、簡單替換、團隊熟悉 C |