这是我的测试代码:
for (int i = 0; i < 1000000000; ++i) {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
}
在ARC环境中,循环不会使内存爆炸。在我的情况下,运行此循环只需要1.2MB RAM。
但是在MRC中,循环会使内存爆炸,除非使用@autoreleasepool代码块。让我困惑的是有很多文章说需要将代码放在@autoreleasepool中,当代码在for中时环。但在这种情况下,如果没有@ autoreleasepool,那就无所谓了。请帮我解决这个问题。
更新: 如果我写这样的代码:
for (int i = 0; i < 1000000000; ++i) {
NSString *string = [NSString stringWithFormat:@"aaaaaaaaaaaaaaaaaaa"];
}
代码将使内存在ARC和MRC中爆炸。为什么呢?
stringWithFormat:
还会返回一个autorelease对象。我很困惑......
答案 0 :(得分:3)
使用ARC编译的代码必须与未使用ARC编译的代码进行互操作。此外,您不能假设您使用ARC编译基础,特别是您正在调用的NSString
方法。
必须以这样的方式编译这些NSString
方法,以便可以从ARC和非ARC代码中调用它们。这意味着他们必须自动释放他们返回的对象,无论他们自己是否使用ARC编译。
但是,如果使用ARC编译这些NSString
方法,则可以使用objc_autoreleaseReturnValue()
进行自动释放。如果调用者也使用ARC编译并且它保留了对象(例如,因为它被分配给一个强大的局部变量),那么它可能会使用objc_retainAutoreleasedReturnValue()
。在这种情况下,可以避免使用自动释放池。 objc_autoreleaseReturnValue()
可以通过检查堆栈和调用者的指令来检测调用者将对返回的值使用objc_retainAutoreleasedReturnValue()
,并且它不会自动释放该值,并且它将通过辅助通道将该事实传达给objc_retainAutoreleasedReturnValue()
以便它不会保留该值。
因此,在某些特定情况下,您不能依赖于自己确定,“通常”自动释放然后保留对象的代码不会。它只会转移所有权。
ARC 不会自动耗尽自动释放池或引入内部自动释放池。
由于存在不确定性,您应该始终在任何可能由于自动释放的对象而导致内存增加的代码周围使用@autoreleasepool
。例如:
for (int i = 0; i < 1000000000; ++i) @autoreleasepool {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
}
答案 1 :(得分:1)
是的,ARC内存管理是自动化的。但它并不总是以正确的方式做到这一点。特别是在创建了许多临时对象的循环中。
临时对象是仅在当前迭代中创建和使用的对象。就像附加到较大字符串的小字符串一样。
这些临时对象在循环完成后被释放,这可能为时已晚。这就是为什么你必须将循环体放在@autoreleasepool
中,以确保在循环的每次迭代后立即释放对象。
for (int i = 0; i < 1000000000; ++i) {
@autoreleasepool {
NSString *string = [NSString stringWithFormat:@"aaaaaaaaaaaaaaaaaaa"];
}
}
答案 2 :(得分:0)
使用ARC,编译器变得更加智能。
在ARC之前,像lowercaseString这样的方法会创建一个保留/自动释放的对象,该对象被放入自动释放池中。使用ARC,编译器很聪明:lowercaseString检查调用它的方法会立即保留结果。在这种情况下,你将有一个序列retain / autorelease / retain,在这种情况下,lowercaseString根本不会保留/自动释放,因此只会执行调用者的保留。这更快(它保存了一个保留,自动释放,以及自动释放池关闭时的最终版本),并且自动释放池不会过度增长。
也就是说,仅因编译器执行优化而起作用的代码被破坏。