当前位置: 首页 > article >正文

iOS - Method Swizzling

让我详细总结一下 Method Swizzling 的使用和注意事项:

1. 基本实现

// 基本的 Method Swizzling 实现
@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 1. 获取原始方法和替换方法
        Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:));
        Method swizzledMethod = class_getInstanceMethod(self, @selector(track_viewDidAppear:));
        
        // 2. 尝试添加方法实现
        BOOL didAddMethod = class_addMethod(self,
                                          @selector(viewDidAppear:),
                                          method_getImplementation(swizzledMethod),
                                          method_getTypeEncoding(swizzledMethod));
        
        // 3. 如果添加成功,直接替换实现
        if (didAddMethod) {
            class_replaceMethod(self,
                              @selector(track_viewDidAppear:),
                              method_getImplementation(originalMethod),
                              method_getTypeEncoding(originalMethod));
        } else {
            // 4. 添加失败,说明方法已存在,直接交换实现
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

// 新方法实现
- (void)track_viewDidAppear:(BOOL)animated {
    // 1. 调用原始实现
    [self track_viewDidAppear:animated];  // 注意:这里实际会调用原始的 viewDidAppear:
    
    // 2. 添加新的功能
    [self trackViewPageAppear];
}

@end

2. 注意事项

2.1 执行时机

// 1. 在 +load 方法中执行
+ (void)load {
    // 在这里执行 swizzling
    // 因为 +load 在类加载时调用,比较安全
}

// 2. 避免在 +initialize 中执行
+ (void)initialize {
    // 不要在这里执行 swizzling
    // 因为 +initialize 可能被调用多次
}

2.2 线程安全

// 使用 dispatch_once 确保只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // swizzling 代码
});

2.3 方法命名

// 使用清晰的前缀避免命名冲突
- (void)xyz_swizzledMethod {
    // 新的实现
}

// 避免使用
- (void)swizzledMethod {  // 可能造成命名冲突
}

2.4 子类处理

// 检查当前类是否真正实现了该方法
+ (void)swizzleMethod:(SEL)originalSelector {
    Class class = [self class];
    
    // 确保方法存在于当前类,而不是继承自父类
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    if (method_getImplementation(originalMethod) == method_getImplementation(class_getInstanceMethod([NSObject class], originalSelector))) {
        return;  // 方法来自基类,不进行 swizzling
    }
    
    // 进行 swizzling
}

2.5 方法签名

// 确保方法签名匹配
- (void)swizzled_method:(id)arg1 withObject:(id)arg2 {
    // 参数类型和数量必须与原方法完全匹配
}

3. 常见陷阱

3.1 递归调用

// 错误示例
- (void)swizzled_method {
    [self swizzled_method];  // 死循环!
}

// 正确示例
- (void)swizzled_method {
    // 在 swizzling 后,这里会调用原始实现
    [self swizzled_method];  
    // 添加新功能
}

3.2 父类方法

// 避免重复 swizzling
static BOOL isSwizzled = NO;

+ (void)load {
    if (!isSwizzled) {
        // 执行 swizzling
        isSwizzled = YES;
    }
}

4. 最佳实践

4.1 封装 Swizzling

@implementation NSObject (Swizzling)

+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)altSel {
    Method origMethod = class_getInstanceMethod(self, origSel);
    Method altMethod = class_getInstanceMethod(self, altSel);
    
    if (!origMethod || !altMethod) {
        return NO;
    }
    
    class_addMethod(self,
                    origSel,
                    class_getMethodImplementation(self, origSel),
                    method_getTypeEncoding(origMethod));
    class_addMethod(self,
                    altSel,
                    class_getMethodImplementation(self, altSel),
                    method_getTypeEncoding(altMethod));
    
    method_exchangeImplementations(class_getInstanceMethod(self, origSel),
                                 class_getInstanceMethod(self, altSel));
    
    return YES;
}

@end

4.2 文档化

// 清晰的文档说明
/**
 * 替换 viewDidAppear: 方法用于追踪页面显示
 * 警告:此方法会影响所有 UIViewController 实例
 * 在 +load 方法中调用,确保在应用启动时完成替换
 */
+ (void)swizzleViewDidAppear {
    // swizzling 实现
}

总结:

  1. 在 +load 中执行
  2. 使用 dispatch_once
  3. 检查方法存在性
  4. 正确处理方法签名
  5. 避免递归调用
  6. 注意子类影响
  7. 清晰的命名约定
  8. 完善的文档说明

Method Swizzling 是一个强大但危险的特性,需要谨慎使用。


http://www.kler.cn/a/502528.html

相关文章:

  • 【HarmonyOS之旅】基于ArkTS开发(二) -> UI开发二
  • Oracle FLOOR函数的用法
  • JSON.stringify(res,null,2)的含义
  • LeetCode-493. Reverse Pairs
  • 网络安全、Web安全、渗透测试之笔经面经总结
  • 开放词汇检测新晋SOTA:DOSOD实时检测算法详解
  • 省市区三级联动(后端)
  • Java内存与缓存
  • Qt的.pro文件中宏的作用
  • 英伟达在CES 2025上的技术发布与采访综述
  • 【Qt笔记】QTextEdit和QPlainTextEdit 控件详解
  • Android车机DIY开发之软件篇(八)单独编译
  • 【机器视觉】OpenCV 图像轮廓(查找/绘制轮廓、轮廓面积/周长、多边形逼近与凸包、外接矩形)
  • 2. Scala 高阶语法之集合与元组
  • 网络原理(三)—— 传输层 之 UDP 和 TCP协议
  • win10 Outlook(new) 企业邮箱登录 登录失败。请在几分钟后重试。
  • Rust调用Windows API制作进程挂起工具
  • python bs4 selenium 查找a href=javascript:();的实际点击事件和url
  • 后端:Spring(IOC、AOP)
  • DHCP详解和部署
  • 电脑分辨率调到为多少最佳?电脑分辨率最佳设置
  • 17.C语言输入输出函数详解:从缓存原理到常用函数用法
  • 深入详解人工智能自然语言处理(NLP)之文本处理:分词、词性标注、命名实体识别
  • R语言的面向对象编程
  • MMDetection框架下的常见目标检测与分割模型综述与实践指南
  • 【数字化】华为-用变革的方法确保规划落地