You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Method Swizzling后OC内存持续增长问题求助

方法交换导致NSMutableArray调用时内存持续增长的问题分析与解决

问题场景

我通过Method Swizzling(方法交换)来解决NSMutableArray数组越界问题,实现代码如下:

static inline void swizzleSelectorKK(Class theClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

// 在load方法中调用交换逻辑
+(void)load{
    [super load];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        swizzleSelectorKK(NSClassFromString(@"__NSArrayM") , @selector(objectAtIndex:), @selector(kk_objectAtIndex:));
    });
}

// 替换方法实现
- (id)kk_objectAtIndex:(NSUInteger)index {
    if (self.count == 0) {
        return nil;
    }
    if (index >= self.count) {
        return nil;
    }
    return [self kk_objectAtIndex:index];
}

ViewController中的测试代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.arr = [[NSMutableArray alloc]init];
    [self.arr addObject:@"this is a array"];
    [self.arr addObject:@"this 2 array"];
    [self.arr addObject:@"this 3"];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    button.backgroundColor = [UIColor systemBlueColor];
    [button setTitle:@"click" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(reloadDataxxx) forControlEvents:UIControlEventTouchUpInside];
    button.frame = CGRectMake(200, 100, 200, 200);
    [self.view addSubview:button];
}
- (void)reloadDataxxx {
    self.index = self.index % 3;
    NSLog(@"index of is %ld data = %@", self.index, [self.arr objectAtIndex:self.index]);
    self.index ++;
}

当持续点击按钮调用objectAtIndex:方法时,内存持续增长;移除方法交换逻辑后,内存则不再变化,求问题原因及解决办法。

问题原因分析

  • 方法交换实现不规范:当前的swizzle函数未处理目标类不存在自定义方法的情况。__NSArrayM是NSMutableArray的私有子类,本身没有kk_objectAtIndex:方法,class_getInstanceMethod会返回父类(NSMutableArray分类)的方法对象,直接交换会导致方法IMP关联异常,每次调用都会产生额外的内存开销。
  • 私有类操作风险:直接针对私有类__NSArrayM做方法交换违反苹果开发规范,私有类的内部实现可能随系统版本变化,此类操作易引发内存泄漏等未知问题。
  • 消息转发额外开销:由于自定义方法来自父类,每次调用时消息转发机制会产生额外的对象引用计数变化或临时对象创建,长期积累导致内存持续增长。

解决办法

1. 规范方法交换流程,避免操作私有类

直接对NSMutableArray进行方法交换,其子类(包括__NSArrayM)会继承交换后的方法,同时修正swizzle逻辑,确保方法交换的正确性:

修正后的swizzle实现:

static inline void swizzleSelectorKK(Class theClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
    
    // 尝试将自定义方法添加到目标类,成功则说明目标类原本无此方法
    BOOL didAddMethod = class_addMethod(theClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        // 将自定义方法的IMP替换为原始方法的IMP
        class_replaceMethod(theClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        // 已有该方法,直接交换实现
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

2. 修改load方法的交换目标

将交换目标改为NSMutableArray,不再直接操作私有类:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        swizzleSelectorKK([NSMutableArray class], @selector(objectAtIndex:), @selector(kk_objectAtIndex:));
    });
}

3. 优化自定义方法逻辑(可选)

可以添加越界日志,方便调试,逻辑保持不变:

- (id)kk_objectAtIndex:(NSUInteger)index {
    if (self.count == 0 || index >= self.count) {
        NSLog(@"数组越界警告:index = %ld,数组长度 = %ld", (long)index, (long)self.count);
        return nil;
    }
    return [self kk_objectAtIndex:index];
}

效果说明

修正后,既能实现数组越界的拦截处理,又不会出现内存持续增长的问题。同时避开了私有类操作的风险,符合苹果开发规范。

内容的提问来源于stack exchange,提问作者Kirin_CN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 01:05:12