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

ARC环境下异步网络请求引发自定义对象内存泄漏问题求助

ARC环境下异步网络请求导致CustomObject内存泄漏问题分析与解决

问题描述

在iOS ARC环境中,通过链式方法传递CustomObject存储HTML文件:

  • 当文件存在本地时,流程正常,对象可被ARC正常释放;
  • 当需要发起异步网络请求获取文件时,CustomObject始终无法被ARC释放。

相关代码

CustomObject定义

@interface CustomObject : NSObject
@property   (strong)    NSString              *htmlPage1;
@property   (strong)    NSString              *htmlPage2;
@property   (strong)    NSString              *htmlPage3;
@property   (strong)    NSMutableArray        *array;
@property   (strong)    DifferentCustomObject *c1;
@end

对象创建与链式调用

- (void) loop: (int) n {
    CustomObject    *co;
    int         i;
    for (i = 0; i < n; ++i) {
        co  = [[CustomObject alloc] init];
        co.c1 = [[DifferentCustomObject alloc] init];
        [self startFileFetchChain: co];
    }
}

链式请求核心代码(示例)

- (void) startFileFetchChain: (CustomObject *) co {
     __block NSURLSessionTask    *task = nil;
             NSURL               *pageURL;
    // 本地文件存在时的同步流程
    if (localPageSource) {
        co.htmlPage1 = localPageSource;
        [self requestNextPageInTheChain: co];
    }
    else {
        // 异步网络请求流程
        task = [[NSURLSession sharedSession] dataTaskWithURL:pageURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
        switch ([httpResponse statusCode]) {
            case 200:
                co.htmlPage1 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                dispatch_async(dispatch_get_main_queue(),^{ 
                    [co.htmlPage1 writeToFile: localPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
                    [self requestNextPageInTheChain: co];
                });
                break;
            default:
                [self endFileFetchChain: co];
                break;
        }
        }];
        [task resume];
    }
}

尝试释放对象的方法

- (void) endFileFetchChain: (CustomObject *) co {
    if (co.array)
        [co.array removeAllObjects];
    if (co.c1)
        co.c1 = nil;
    co = nil;
}

问题解答

1. 你的推测是否正确?

不完全准确。核心问题不是“主循环创建新对象导致原对象引用计数无法归零”,而是异步block对CustomObject的强引用未被正确处理:

  • 在NSURLSession的completion handler和dispatch_async的block中,直接引用了co,ARC会自动为co添加强引用,直到block执行完毕;
  • 如果链式调用中存在多层异步block,这些强引用会形成持续的引用链,即使调用endFileFetchChain,也无法打破这条链(因为endFileFetchChain里的co = nil只是修改了方法参数的局部变量,对原对象的强引用没有影响)。

2. 如何重构代码解决内存泄漏?

方法一:使用弱引用打破block强引用

在异步任务前,对CustomObject创建弱引用,避免block持有强引用;同时在block内转为强引用,防止对象中途释放:

- (void) startFileFetchChain: (CustomObject *) co {
     __block NSURLSessionTask    *task = nil;
             NSURL               *pageURL;
    __weak typeof(CustomObject) weakCo = co; // 创建弱引用
    
    if (localPageSource) {
        co.htmlPage1 = localPageSource;
        [self requestNextPageInTheChain: co];
    }
    else {
        task = [[NSURLSession sharedSession] dataTaskWithURL:pageURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
        typeof(CustomObject) strongCo = weakCo; // 转为强引用,保证操作完整性
        if (!strongCo) return;
        
        switch ([httpResponse statusCode]) {
            case 200:
                strongCo.htmlPage1 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                dispatch_async(dispatch_get_main_queue(),^{ 
                    typeof(CustomObject) queueStrongCo = weakCo;
                    if (!queueStrongCo) return;
                    
                    [queueStrongCo.htmlPage1 writeToFile: localPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
                    [self requestNextPageInTheChain: queueStrongCo];
                });
                break;
            default:
                [self endFileFetchChain: strongCo];
                break;
        }
        }];
        [task resume];
    }
}

方法二:优化endFileFetchChain方法

原方法中的co = nil无效,需清空对象内部所有强引用,减少引用计数:

- (void) endFileFetchChain: (CustomObject *) co {
    // 清空所有内部强引用
    co.htmlPage1 = nil;
    co.htmlPage2 = nil;
    co.htmlPage3 = nil;
    [co.array removeAllObjects];
    co.array = nil;
    co.c1 = nil;
}

方法三:管理任务与对象的绑定生命周期

用NSMutableArray等容器持有所有未完成的NSURLSessionTask和对应的CustomObject,当任务完成或失败时,从容器中移除对应条目,确保外部引用被及时释放。

3. 自定义对象包含的其他对象会随父对象释放吗?

会。只要CustomObject被ARC正确释放(引用计数归零),它的所有strong属性(如c1、array等)会被ARC自动置为nil,这些子对象的引用计数会相应减少。如果子对象没有其他外部强引用,就会被ARC自动释放。

4. 是否需要为每个自定义对象编写自定义析构方法?

不需要。ARC会自动处理Objective-C对象的内存管理,只有当对象持有非ARC管理的资源(如Core Foundation对象、文件句柄、网络套接字等)时,才需要在dealloc方法中手动释放这些资源。对于纯Objective-C对象组成的属性,ARC会自动处理释放逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 14:19:50