苹果官方文档:使用Autorelease Pools

本文深入探讨Objective-C中的Autorelease Pools机制,解释其如何提供延迟释放对象的机制,以及在不同场景下如何有效地利用Autorelease Pools来管理内存,包括在非UI框架程序、循环中大量创建临时变量以及线程管理中的应用。
翻译自:

http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html%23//apple_ref/doc/uid/20000047-SW4


Autorelease pools提供了一个延迟向对象发送release消息的机制。当你想放弃一个对象的所有权,但是又不想这个对象马上被释放时(比如当你从一个方法中返回一个对象),这种机制是有用的。一般你不需要创建自己的autorelease pools,但是有的时候你必须自己创建autorelease pools,有的时候自己创建autorelease pools会带来便利。

关于Autorelease pools

一个autorelease pool是一个NSAutoreleasePool的实例,它包含收到autorelease消息的对象。当一个autoreleasepool被销毁的时候,它向所包含的每个对象发送一个release消息。一个对象可以放入autorelease pool多次;一个对象每次被放入pool中时都会收到一个release消息(发送一个autorelease消息)。

Autorelease pools以栈的形式进行组织,这通常被称为嵌套的方式。如果你创建了一个新的autorelease pool,它被添加到栈的顶端。当pools被销毁的时候,它们被从栈中移除。当一个对象发送一个autorelease消息时,它被添加到当前线程的Autorelease pools的最顶端的pool中。

Cocoa总是期望一个autoreleasepool是可用。如果一个pool是不可用的,被自动释放的的对象最终不会被释放,这样你的应用就会泄露内存。当一个pool不可用时,如果向一个对象发送autorelease消息,Cocoa会记录一个适当的错误信息。在每一个事件循环(event-loop)开始的时候(比如鼠标按下事件或者一个手势),AppKit和UIKit框架会自动地创建一个pool,并且在事件处理结束时销毁这个pool。(译者注:这个是object-c内存管理的一个核心)因此,一般你不需要自己创建一个pool,甚至没有必要去看创建pool的代码。然而,这里有3中情况你应该创建自己的autorelease pools:

l 你写的程序不是基于UIFramework,比如一个命令行工具。

l 你写了一个循环,里面创建了大量的临时变量。

在下次事件循环之前,你可以在这个循环中创建一个autorelease pool用于处理这些临时变量。在循环中使用autorelease pool帮助降低应用程序占用的内存峰值。

l 你生成了一个线程。

线程一开始执行,你就必须创建你自己的autorelease pool;不然,你的应用就会泄露对象。(“Autorelease Pools and Threads”查看更多细节)

你使用alloc和init消息创建一个NSAutoreleasePool对象,使用drain消息销毁这个对象。如果你向一个autorelease pool发送autorelease或retain消息则会触发异常。想理解消息release和drain的不同,可以看“Garbage Collection。“一个autorelease pool应该总是在”其被创建的上下文”(比如一个方法或函数调用中,或者一个循环中)中被销毁。


使用Local AutoreleasePools降低内存峰值

很多程序创建自动释放的临时变量。这些变量放置在程序的内存中直到pool被销毁。你可以使用local autorelease pools帮助降低内存峰值。当你的pool销毁的时候,这些临时对象被释放,从而降低程序的内存峰值。

下面的例子展示一个循环中使用localautorelease pool的一个方法

NSArray *urls = <# An array of file URLs #>;

for (NSURL *urlin urls) {

NSAutoreleasePool *loopPool = [[NSAutoreleasePoolalloc]init];

NSError *error =nil;

NSString *fileContents = [[[NSStringalloc]initWithContentsOfURL:url

encoding:NSUTF8StringEncodingerror:&error]autorelease];

/* Process the string, creating and autoreleasing more objects. */

[loopPool drain];

}

For循环一次处理一个文件。一个NSAutoreleasePool对象在循环的开始被创建,在循环的末尾被销毁。因此,这个循环中任何一个发送autorelease消息的对象都会被放到这个loopPool中,当loopPool在循环的最后被销毁时这些对象被释放。

一个autorelease pool被销毁后,你应该认为这个pool被销毁前存放的autoreleased对象都被销毁掉了。不要再向之前在pool中的对象发送消息或把它返回给你的方法的调用者。如果你必须在一个autorelease上下文之外使用一个临时对象,可以像下面的例子描述的那样,在这个上下文中向这个对象发送一个retain消息,然后在这个pool被销毁后向这个对象发送一个autorelease消息。

– (id)findMatchingObject:(id)anObject {

id match = nil;

while (match == nil) {

NSAutoreleasePool *subPool = [[NSAutoreleasePool alloc] init];

/* Do a search that creates a lot of temporary objects. */

match = [self expensiveSearchForObject:anObject];

if (match != nil) {

[match retain]; /* Keep match around. */

}

[subPool drain];

}

return [match autorelease]; /* Let match go and return it. */

}

在subpool起作用的时候,向match发送一个retain消息,在subpool销毁后,向它发送一个autorelease消息,通过这样,match从subpool转移到subpool之前起作用的pool中。这样扩展了match的生命周期并且使它可以在循环外接收消息、返回给findMatchingObject:的调用者。

AutoreleasePools和线程

在Cocoa应用程序中,每个线程管理它自己的NSAutoreleasePool的栈。当一个线程终止时,它自动的释放所有与它关联的autorelease pool。如果你写一个只使用Foudation框架的应用或者你自己创建一个线程时,你需要创建自己的autorelease pool。

如果你的应用或线程生命周期很长并且可能会生成很多自动释放的对象,那么你应该周期的销毁和创建autorelease pool(就像Kit框架在主线程开始所做的那样)。

否则的话,自动释放的对象会堆积,你的应用的内存峰值会增长。如果你自己创建的线程做Cocoa相关的调用,你就不需要创建autorelease pool。

注意:

如果你使用POSIX threadAPIs而不是使用NSThread创建一个线程,你就不能使用Cocoa,包括NSAutoreleasepool,除非Cocoa是在多线程模式。Cocoa只有在创建它的第一个分离的线程对象后才会进入多线程模式。为了在随后创建的POSIX线程中使用Cocoa,你的应用必须首先创建一个分离的NSThread对象,并且这个分离的线程会马上退出。你可以使用NSThread的类方法isMultiThreaded测试Cocoa是否在多线程模式。

AutoreleasePools的作用域和嵌套的Autorelease Pools的实现

通常说autorelease pool是嵌套的,也可以认为嵌套的autoreleasepool是放在一个栈上,最内层的autorelease pool是放在栈顶。程序中的每个线程管理一个autorelease pools的栈。当你创建一个autorelease pool时,它被放到当前线程的autorelease pool的栈的顶端。当一个对象发送一个autorelease消息或者当它做为addObject的参数传递时,它总是被放在autorelease pool栈的顶端的aurelease pool中。

一个autorelease pool是否起作用与它在栈中的位置有关。栈顶端的pool是自动释放的对象被添加到的地方。如果一个新的pool被创建,那么当前最顶端的pool会变为不起作用的直到新创建的pool被销毁。(这样之前的pool又变为最顶端的pool)。当它被销毁的时候,它将永远不会再起作用。

如果你销毁一个非栈最顶端的autoreleasepool时,那么这个栈中所有在这个autorelease pool之上autorelease pool被销毁(会向它们中的所有对象发送release消息)。如果当你使用完一个autorelease pool而没有销毁它(一般不建议),这个pool会被销毁当它嵌套的autorelease pool被销毁的时候。

这个行为忽略掉了有异常的情况。如果一个异常发生了,线程突然退出当前上下文,这个pool关联的上下面文被销毁了。然而,如果那个pool不是位于线程栈最顶端的pool,那么这个pool上面的所有pool都会被销毁(这个过程会释放掉所有的对象)。这个pool下面的一个pool会成为栈顶autorelease pool。因为这样,所以异常处理不需要释放autorelease pool中的对象。对于异常处理没有必要也不值得向它的autorelease pool发送release消息,除非异常会重新触发这个异常。


<think>我们在处理模板类中的嵌套类静态成员声明时,可能会遇到需要添加`typename`的情况。错误信息通常为:`need ‘typename’ before ... because ... is a dependent scope`。这是因为在模板中,编译器无法确定嵌套类的类型,除非我们使用`typename`关键字来显式说明这是一个类型。 根据你提供的错误信息:`need ‘typename’ before A<T>::AutoRelease because A<T> is a dependent scope`,我们可以推断,在类外声明静态成员时,没有正确使用`typename`关键字。 下面是一个示例模板类,其中包含一个嵌套类`AutoRelease`,并且我们想要在类外定义`AutoRelease`的一个静态成员。 错误示例: ```cpp template <typename T> class A { public: class AutoRelease; // 嵌套类的声明 static AutoRelease ar; // 静态成员声明 }; // 在类外定义嵌套类 template <typename T> class A<T>::AutoRelease { // ... 成员定义 }; // 错误的静态成员定义:缺少typename template <typename T> A<T>::AutoRelease A<T>::ar; // 这里会报错 ``` 正确的方式是在定义静态成员时,在类型说明符前添加`typename`关键字,因为`A<T>::AutoRelease`是一个依赖作用域的类型名(即依赖于模板参数T),编译器在没有实例化模板之前无法确定它是否是一个类型。因此,我们需要明确告诉编译器这是一个类型。 修正后的定义: ```cpp template <typename T> typename A<T>::AutoRelease A<T>::ar; ``` 这里,`typename`关键字用于告诉编译器`A<T>::AutoRelease`是一个类型。 因此,针对你的问题,在类外声明嵌套类的静态成员时,应该在静态成员的类型前面加上`typename`关键字,以指示该嵌套类是一个类型。 另外,注意静态成员的定义需要在全局作用域(或命名空间作用域)中进行,并且对于模板类,每个静态成员的定义本身也是一个模板定义。 如果静态成员需要在编译时初始化,并且是整型或枚举类型,可以在类内直接初始化(C++11起允许类内初始化非const静态成员,但通常还是需要在类外定义以分配存储空间)。但对于非整型的静态成员,通常需要在类外定义。 总结:在模板类外定义嵌套类的静态成员时,需要: 1. 使用`template <typename T>`开始定义。 2. 在类型名前使用`typename`关键字指明嵌套类是一个类型。 3. 按照`类型 类名<模板参数>::静态成员名`的格式定义。 这样,就可以解决上述错误。 相关问题: 1. 在C++模板中,何时需要使用`typename`关键字? 2. 模板类中的静态成员初始化有什么特殊要求? 3. 嵌套类在模板类中有什么特殊注意事项? 4. 如何理解“dependent scope”在C++模板中的含义?</think>### 解决C++模板类嵌套类静态成员声明错误 在C++中,当在模板类外部声明嵌套类的静态成员时,遇到`need 'typename' before ... because ... is a dependent scope`错误,是因为编译器无法确定嵌套类是否属于类型。以下是解决方案: #### 错误原因分析 1. **依赖作用域(Dependent Scope)** 模板类`A<T>`中的`AutoRelease`是依赖于模板参数`T`的嵌套类型 2. **编译器无法自动推断类型** 在模板实例化前,编译器无法确定`A<T>::AutoRelease`是否表示类型(可能被特化为非类型成员) 3. **缺少`typename`关键字** 必须显式告知编译器这是类型而非静态成员或变量 #### 正确解决方案 在静态成员声明前添加`typename`关键字: ```cpp template<typename T> class A { public: class AutoRelease; // 嵌套类声明 static AutoRelease ar; // 静态成员声明 }; // 嵌套类定义 template<typename T> class A<T>::AutoRelease { // 类实现... }; // ✅ 正确静态成员定义(关键修改) template<typename T> typename A<T>::AutoRelease A<T>::ar; // 添加typename ``` #### 关键说明 1. `typename`的作用: 明确告知编译器`A<T>::AutoRelease`是一个类型而非成员变量 2. 语法要求: - 只能用于模板定义中 - 必须放在完整限定名称前 - 不能用于基础数据类型(如`int`/`double`) 3. 特例情况: 如果嵌套类在基类中定义,可能需要额外添加`template`关键字: ```cpp template<typename T> typename A<T>::template AutoRelease A<T>::ar; ``` #### 错误示例对比 ```cpp // ❌ 错误写法(缺少typename) template<typename T> A<T>::AutoRelease A<T>::ar; // 触发编译错误 ``` > **根本原因**:C++标准规定,在模板的依赖作用域中,编译器默认将嵌套名称视为非类型(除非使用`typename`显式声明)[^1][^2][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值