内存增长之谜(Objective-C)

Memory Growth Mystery (Objective-C)

我的应用程序存在内存增长问题。

从 在这里描述完整的代码是令人生畏的, 我将其缩小到这个简单的场景,我在两个视图控制器之间来回切换以学习基本的内存动态。

 - (void)viewDidLoad {
    [super viewDidLoad];

for (int i=0; i<100000; i++) { __weak NSString* str = [NSString stringWithFormat:@"abcsdf"]; str = nil; } }

这应该不会显示内存增长,因为我分配 'str' 并通过使 'str' 变为零来释放 'str',从而失去所有者。

但是,内存一直在增长。 每次我加载这个视图控制器时,内存都会不断增长并且永远不会回来。

谁能告诉我这是为什么? 我正在使用 ARC。

您的代码片段包含一些关于 iOS/OS X 内存管理的有趣内容。

__weak NSString* str = [NSString stringWithFormat:@"abcsdf"];
str = nil;

代码同下面没有ARC的

NSString* str = [[[NSString alloc] initWithFormat:@"abcsdf"] autorelease];
str = nil;

因为 stringWithFormat: class 方法不以 "alloc"、"new"、"copy" 或 "mutableCopy" 开头。这是命名规则。因此 NSString 对象由自动释放池保留。 Autorelease Pool 可能在主 Runloop 中。因此 NSString 对象没有立即释放。它会导致内存增长。 @autoreleasepool 解决了。

@autoreleasepool {
    __weak NSString* str = [NSString stringWithFormat:@"abcsdf"];
    str = nil;
}

NSString 对象在 @autoreleasepool 代码块的末尾被释放。

顺便说一下,[NSString stringWithFormat:@"abcsdf"] 可能不会每次都分配任何内存。原因是它是静态字符串。让我们用这个 class 做进一步的解释。

#import <Foundation/Foundation.h>

@interface Test : NSObject
+ (instancetype)test;
@end

@implementation Test
- (void)dealloc {
    NSLog(@"Test dealloc");
}

+ (instancetype)test
{
    return [[Test alloc] init];
}
@end

这是 __weak 的测试代码。

@autoreleasepool {
    NSLog(@"BEGIN: a = [Test test]\n");
    __weak Test *a = [Test test];
    NSLog(@"END: a = [Test test]\n");
    a = nil;
    NSLog(@"DONE: a = nil\n");
}

代码的结果。

BEGIN: a = [Test test]
END: a = [Test test]
DONE: a = nil
Test dealloc

你说的是deallocate 'str' by making 'str' becomes nil, thus losing the owner。这是不正确的。 a 弱变量没有对象的所有权。 Autorelease Pool 确实拥有该对象的所有权。这就是为什么对象在 @autoreleasepool 代码块的末尾被释放的原因。看看这个案例的其他测试代码。

NSLog(@"BEGIN: a = [[Test alloc] init]\n");
__weak Test *a = [[Test alloc] init];
NSLog(@"END: a = [[Test alloc] init]\n");
a = nil;
NSLog(@"DONE: a = nil\n");

从代码中可以看到编译警告

warning: assigning retained object to weak variable; object will be
         released after assignment [-Warc-unsafe-retained-assign]
            __weak Test *a = [[Test alloc] init];
                         ^   ~~~~~~~~~~~~~~~~~~~

[[Test alloc] init] 没有将对象注册到自动释放池。好吧,不再需要 @autoreleasepool 了。而 a__weak 变量,所以对象不会被保留。因此结果是

BEGIN: a = [[Test alloc] init]
Test dealloc
END: a = [[Test alloc] init]
DONE: a = nil

没有所有权就没有生命。该对象在分配后立即被释放。我认为您想编写没有 __weak 的代码,如下所示。

NSLog(@"BEGIN: a = [[Test alloc] init]\n");
Test *a = [[Test alloc] init];
NSLog(@"END: a = [[Test alloc] init]\n");
a = nil;
NSLog(@"DONE: a = nil\n");

结果符合预期。通过将 nil 分配给强变量 a 来释放对象。然后没有人拥有该对象的所有权,该对象被释放。

BEGIN: a = [[Test alloc] init]
END: a = [[Test alloc] init]
Test dealloc
DONE: a = nil