Apple - Advanced Memory Management Programming Guide 内存管理

翻译整理自:Advanced Memory Management Programming Guide(Updated: 2012-07-17
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html#//apple_ref/doc/uid/10000011i


文章目录

  • 一、关于内存管理
    • 概览
      • 良好实践可防止与内存相关的问题
      • 使用分析工具调试内存问题
  • 二、内存管理策略
    • 1、基本内存管理规则
      • 一个简单的例子
      • 使用自动发布发送延迟发布
      • 您不拥有通过引用返回的对象
    • 2、实现deloc以放弃对象的所有权
    • 3、Core Foundation使用相似但不同的规则
  • 三、实用内存管理
    • 1、使用访问器方法使内存管理更容易
      • 使用访问器方法设置属性值
      • 不要在初始化方法和释放中使用访问器方法
    • 2、使用弱引用来避免保留周期
    • 3、避免导致您正在使用的对象被取消分配
    • 4、不要使用deloc来管理稀缺资源
    • 5、集合拥有它们包含的对象
    • 6、使用保留计数实施所有权政策
  • 四、使用自动释放池块
    • 1、关于自动释放池块
    • 2、使用本地自动释放池块来减少峰值内存占用
    • 3、自动释放池块和线程


一、关于内存管理

应用程序内存管理是在程序运行时分配内存,使用它,并在完成后释放它的过程。
一个编写良好的程序使用尽可能少的内存。
在Objective-C中,它也可以被视为在许多数据和代码之间分配有限内存资源所有权的一种方式。
当你完成本指南的工作后,你将拥有管理应用程序内存所需的知识,通过显式管理对象的生命周期并在不再需要时释放它们。

尽管内存管理通常是在单个对象的级别上考虑的,但您的目标实际上是管理对象图。
您要确保内存中的对象没有超过实际需要。

在这里插入图片描述


概览

Objective-C提供了两种应用程序内存管理方法。

  1. 在本指南中描述的方法中,称为“手动保留释放”或MRR,您通过跟踪您拥有的对象来显式管理内存。
    这是使用一个称为引用计数的模型来实现的,该模型由Foundation类NSObject与运行时环境一起提供。

  2. 在自动引用计数或ARC中,系统使用与MRR相同的引用计数系统,但它会在编译时为您插入适当的内存管理方法调用。
    强烈建议您在新项目中使用ARC。
    如果您使用ARC,通常不需要了解本文档中描述的底层实现,尽管在某些情况下它可能会有所帮助。
    有关ARC的更多信息,请参阅*过渡到ARC发行说明*。


良好实践可防止与内存相关的问题

不正确的内存管理会导致两种主要问题:

  • 释放或覆盖仍在使用的数据
    这会导致内存损坏,通常会导致应用程序崩溃,或者更糟糕的是,损坏用户数据。
  • 不释放不再使用的数据会导致内存泄漏
    内存泄漏是指分配的内存没有被释放,即使它再也不会被使用。
    泄漏会导致应用程序使用越来越多的内存,进而可能导致系统性能不佳或应用程序被终止。

然而,从引用计数的角度考虑内存管理往往会适得其反,因为您倾向于根据实现细节而不是实际目标来考虑内存管理。
相反,您应该从对象所有权和对象图的角度来考虑内存管理。

Cocoa使用简单的命名约定来指示您何时拥有方法返回的对象。

请参见内存管理策略。

尽管基本策略很简单,但您可以采取一些实际步骤来简化内存管理,并帮助确保您的程序保持可靠和健壮,同时最大限度地减少其资源需求。

请参阅实用内存管理。

自动释放池块提供了一种机制,您可以借此向对象发送“延迟”release消息。
这在您想放弃对象的所有权但又想避免它立即被释放的可能性的情况下很有用(例如,当您从方法返回对象时)。
有时您可能会使用自己的自动释放池块。

请参见使用自动释放池块。


使用分析工具调试内存问题

要在编译时识别代码的问题,您可以使用Xcode中内置的Clang静态分析器。

如果仍然出现内存管理问题,您可以使用其他工具和技术来识别和诊断问题。

  • 许多工具和技术描述在技术说明TN2239,iOS调试魔术,特别是使用NSZombie来帮助找到过度释放的对象。
  • 您可以使用Instruments跟踪引用计数事件并查找内存泄漏。
    请参阅在您的应用程序上收集数据。

二、内存管理策略

在引用计数环境中用于内存管理的基本模型由NSObject 协议中定义的方法和标准方法命名约定的组合提供。
NSObject类还定义了一个方法,即dealloc对象时自动调用的方法。
本文描述了在Cocoa程序中正确管理内存所需了解的所有基本规则,并提供了一些正确使用的示例。


1、基本内存管理规则

内存管理模型基于对象所有权。
任何对象都可能有一个或多个所有者。
只要一个对象至少有一个所有者,它就会继续存在。
如果一个对象没有所有者,运行时系统会自动销毁它。
为了确保明确何时拥有对象,何时不拥有,Cocoa设置以下策略:

  • 您拥有您创建的任何对象
    使用名称以"alloc"、“new”、"cop"或"mutableCopy"开头的方法(例如,allocnewObjectmutableCopy)创建对象。
  • 您可以使用保留获得对象的所有权
    接收到的对象通常保证在它接收到的方法中保持有效,该方法也可以安全地将对象返回给它的调用者。
    您可以在两种情况下使用retain:(1)在访问器方法或init方法的实现中,获取要作为属性值存储的对象的所有权;(2)防止对象因其他操作的副作用而失效(如避免导致您正在使用的对象的解除分配)。
  • 当您不再需要它时,您必须放弃对您拥有的对象的所有权
    通过向对象发送release消息或autorelease消息来放弃对象的所有权。
    因此,在Cocoa术语中,放弃对象的所有权通常称为“释放”对象。
  • 您不得放弃您不拥有的对象的所有权
    这只是以前明确规定的政策规则的必然结果。

一个简单的例子

为了说明该策略,请考虑以下代码片段:

{
    Person *aPerson = [[Person alloc] init];
    // ...
    NSString *name = aPerson.fullName;
    // ...
    [aPerson release];
}

Person对象是使用alloc方法创建的,因此当不再需要它时,它会随后发送release消息。
没有使用任何owning方法检索人员的姓名,因此不会发送release消息。
但是请注意,该示例使用的是release而不是autorelease


使用自动发布发送延迟发布

当您需要发送延迟release消息时,您可以使用autorelease-通常是从方法返回对象时。
例如,您可以像这样实现fullName方法:

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
                                          self.firstName, self.lastName] autorelease];
    return string;
}

您拥有alloc返回的字符串。
为了遵守内存管理规则,您必须在丢失对字符串的引用之前放弃对字符串的所有权。
但是,如果您使用release,字符串将在返回之前被释放(并且该方法将返回一个无效的对象)。
使用autorelease,您表示要放弃所有权,但您允许方法的调用者在释放之前使用返回的字符串。

您还可以像这样实现fullName方法:

- (NSString *)fullName {
    NSString *string = [NSString stringWithFormat:@"%@ %@",
                                 self.firstName, self.lastName];
    return string;
}

遵循基本规则,您不拥有stringWithFormat:返回的字符串,因此您可以安全地从方法返回字符串。

相比之下,以下实现是错误的:

- (NSString *)fullName {
    NSString *string = [[NSString alloc] initWithFormat:@"%@ %@",
                                         self.firstName, self.lastName];
    return string;
}

根据命名约定,没有什么可以表示fullName方法的调用者拥有返回的字符串。
因此,调用者没有理由释放返回的字符串,因此它将被泄露。


您不拥有通过引用返回的对象

Cocoa中的一些方法指定通过引用返回对象(也就是说,它们采用ClassName **id *类型的参数)。
一种常见的模式是使用NSError对象,该对象包含错误发生时的信息,如initWithContentsOfURL:options:error:NSData)和initWithContentsOfFile:encoding:error:NSString)所示。

在这些情况下,适用的规则与前面描述的相同。
当您调用这些方法中的任何一个时,您都没有创建NSError对象,因此您不拥有它。
因此不需要释放它,如下例所示:

NSString *fileName = <#Get a file name#>;
NSError *error;
NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
                        encoding:NSUTF8StringEncoding error:&error];
if (string == nil) {
    // Deal with error...
}
// ...
[string release];


2、实现deloc以放弃对象的所有权

NSObject类中定义了一个方法,即dealloc,当一个对象没有所有者并且它的内存被回收时,它会自动调用——在Cocoa术语中,它是“释放的”或“释放的”。
dealloc方法的作用是释放对象自己的内存,并处置它持有的任何资源,包括任何对象实例变量的所有权。

以下示例说明了如何为Person类实现dealloc方法:

@interface Person : NSObject
@property (retain) NSString *firstName;
@property (retain) NSString *lastName;
@property (assign, readonly) NSString *fullName;
@end
 
@implementation Person
// ...
- (void)dealloc
    [_firstName release];
    [_lastName release];
    [super dealloc];
}
@end

**重要提示:**切勿直接调用另一个对象的dealloc方法。

您必须在实现结束时调用超类的实现。

您不应该将系统资源的管理与对象的生命周期联系起来;请参阅不要使用释放来管理稀缺资源。

当应用程序终止时,对象可能不会被发送dealloc消息。
因为进程的内存在退出时会自动清除,所以简单地允许操作系统清理资源比调用所有内存管理方法更有效。


3、Core Foundation使用相似但不同的规则

Core Foundation对象也有类似的内存管理规则(请参阅*Core Foundation的内存管理编程指南)。
然而,Cocoa和Core Foundation的命名约定是不同的。
特别是,Core Foundation的创建规则(请参阅创建规则)不适用于返回Objective-C对象的方法。
例如,在以下代码片段中,您
不*负责放弃myInstance的所有权:

MyClass *myInstance = [MyClass createInstance];

三、实用内存管理

尽管内存管理策略中描述的基本概念很简单,但您可以采取一些实际步骤来简化内存管理,并帮助确保您的程序保持可靠和健壮,同时最大限度地减少其资源需求。


1、使用访问器方法使内存管理更容易

如果你的类有一个属性是一个对象,你必须确保任何被设置为值的对象在你使用它时都没有被释放。
因此,你必须在设置对象时声明它的所有权。
你还必须确保你放弃了任何当前持有值的所有权。

有时这可能看起来很乏味或迂腐,但是如果你坚持使用访问器方法,内存管理出现问题的可能性会大大降低。
如果你在整个代码中使用实例变量的retainrelease,你几乎肯定做错了。

考虑一个要设置其计数的Counter对象。

@interface Counter : NSObject
@property (nonatomic, retain) NSNumber *count;
@end;

该属性声明了两个访问器方法。
通常,您应该要求编译器合成这些方法;但是,了解它们是如何实现的很有启发性。

在“get”访问器中,您只需返回合成的实例变量,因此无需retainrelease

- (NSNumber *)count {
    return _count;
}

在“set”方法中,如果其他人都遵循相同的规则,您必须假设新的计数可能随时被丢弃,因此您必须获得对象的所有权——通过向其发送retain消息——以确保它不会被丢弃。
您还必须通过向其发送release消息来放弃旧count对象的所有权。
(在Objective-C中允许向nil发送消息,因此如果_count尚未设置。)您必须在之后发送此消息[newCount retain]以防这两个是同一个对象——您不想无意中导致它被释放。

- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [_count release];
    // Make the new assignment.
    _count = newCount;
}


使用访问器方法设置属性值

假设您想实现一个重置计数器的方法。
您有几个选择。
第一个实现使用alloc创建NSNumber实例,因此您可以通过release来平衡它。

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [self setCount:zero];
    [zero release];
}

第二个使用方便的构造函数来创建一个新的NSNumber对象。
因此不需要retainrelease消息

- (void)reset {
    NSNumber *zero = [NSNumber numberWithInteger:0];
    [self setCount:zero];
}

请注意,两者都使用set访问器方法。

下面的方法几乎肯定适用于简单的情况,但是避免访问器方法可能很诱人,这样做几乎肯定会在某个阶段导致错误(例如,当您忘记保留或释放时,或者如果实例变量的内存管理语义学发生变化)。

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [_count release];
    _count = zero;
} 

另请注意,如果您使用键值观察,那么以这种方式更改变量不符合KVO。


不要在初始化方法和释放中使用访问器方法

唯一不应该使用访问器方法来设置实例变量的地方是初始化程序方法和dealloc
要使用表示零的数字对象初始化计数器对象,您可以实现如下的init方法:

- init {
    self = [super init];
    if (self) {
        _count = [[NSNumber alloc] initWithInteger:0];
    }
    return self;
}

要允许使用非零计数初始化计数器,您可以实现initWithCount:方法,如下所示:

- initWithCount:(NSNumber *)startingCount {
    self = [super init];
    if (self) {
        _count = [startingCount copy];
    }
    return self;
}

由于计数器类有一个对象实例变量,您还必须实现一个dealloc方法。
它应该通过向任何实例变量发送release消息来放弃它们的所有权,最终它应该调用超级的实现:

- (void)dealloc {
    [_count release];
    [super dealloc];
}


2、使用弱引用来避免保留周期

保留一个对象会创建对该对象的强引用。
在释放所有强引用之前,不能释放一个对象。
因此,如果两个对象可能具有循环引用,则会出现一个称为保留循环的问题——也就是说,它们彼此具有强引用(直接引用,或者通过其他对象的链,每个对象都具有对下一个对象的强引用,从而返回到第一个对象)。

如[图1] 所示的对象关系说明了一个潜在的保留周期。
Document对象对文档中的每个页面都有一个Page对象。
每个Page对象都有一个属性来跟踪它在哪个文档中。
如果Document对象对Page对象有一个强引用,而Page对象对Document对象有一个强引用,则两个对象都不能被释放。
在Page对象被释放之前,Document的引用计数不能变为零,在Document对象被释放之前,Page对象不会被释放。


图1循环参考的图示

在这里插入图片描述


保留循环问题的解决方案是使用弱引用。
弱引用是一种非拥有关系,其中源对象不保留它具有引用的对象。

然而,为了保持对象图的完整,必须在某个地方有强引用(如果只有弱引用,那么页面和段落可能没有任何所有者,因此将被释放)。
因此,Cocoa建立了一个约定,即“父”对象应该保持对其“子”的强引用,而子对象应该对其父对象有弱引用。

因此,在图1中,文档对象对其页面对象具有强引用(保留),但页面对象对文档对象具有弱引用(不保留)。

Cocoa中弱引用的示例包括但不限于表数据源、大纲视图项、通知观察者以及其他目标和委托。

在向仅持有弱引用的对象发送消息时,您需要小心。
如果您在对象被解除分配后向其发送消息,您的应用程序将崩溃。
您必须对对象何时有效有明确定义的条件。
在大多数情况下,弱引用对象会意识到另一个对象对它的弱引用,循环引用就是这种情况,并负责在它解除分配时通知另一个对象。
例如,当您向通知中心注册一个对象时,通知中心会存储对该对象的弱引用,并在发布适当的通知时向其发送消息。
当对象被解除分配时,您需要在通知中心取消注册它,以防止通知中心向该对象发送任何进一步的消息,该对象已不存在。
同样,当一个委托对象被解除分配时,您需要通过向另一个对象发送带有nil参数的setDelegate:消息来删除委托链接。
这些消息通常从对象的dealloc方法发送。


3、避免导致您正在使用的对象被取消分配

Cocoa 的所有权策略规定,接收对象通常应在调用方法的整个作用域内保持有效。
从当前作用域返回接收到的对象时,也不应担心该对象会被释放。对于您的应用程序来说,对象的 getter 方法是否返回缓存的实例变量或计算值并不重要。重要的是,对象在您需要它时仍然有效。

这条规则偶尔也有例外,主要分为以下两类。

  1. 对象从基本集合类中删除。
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.

从基本集合类中移除对象时,会向其发送一条 release 消息(而不是 autorelease)。如果该集合是被移除对象的唯一所有者,那么被移除的对象(示例中的 heisenObject)就会立即被取消分配。


  1. 当一个 “parent object” 销毁
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.

在某些情况下,您从另一个对象中检索一个对象,然后直接或间接释放父对象。
如果释放父对象会导致它被释放,并且父对象是子对象的唯一所有者,那么子对象(示例中的heisenObject)将同时被释放(假设它被发送release而不是父对象的dealloc方法中的autorelease消息)。


为了防止这些情况,您在收到heisenObject时保留它,并在完成后释放它。
例如:

heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];

// Use heisenObject...
[heisenObject release];

4、不要使用deloc来管理稀缺资源

您通常不应该在dealloc方法中管理稀缺资源,如文件描述符、网络连接和缓冲区或缓存。
特别是,您不应该设计类,以便在您认为将调用dealloc时调用它。
dealloc的调用可能会延迟或回避,要么是因为bug,要么是因为应用程序拆除。

相反,如果你有一个实例管理稀缺资源的类,你应该设计你的应用程序,这样你就知道什么时候不再需要资源,然后可以告诉实例在那个时候“清理”。
然后你通常会释放实例,dealloc会随之而来,但是如果没有,你不会遇到额外的问题。

如果您试图在dealloc之上搭载资源管理,可能会出现问题。
例如:

  1. 顺序依赖于对象图拆解。
    对象图的拆解机制本质上是无序的。
    尽管您通常期望并获得特定的顺序,但您引入了脆弱性。
    例如,如果对象意外地自动释放而不是释放,则拆解顺序可能会改变,这可能会导致意外结果。
  2. 不回收稀缺资源。
    内存泄漏是应该修复的错误,但它们通常不会立即致命。
    但是,如果稀缺资源在您期望释放时没有释放,您可能会遇到更严重的问题。
    例如,如果您的应用程序用完了文件描述符,用户可能无法保存数据。
  3. 清理逻辑在错误的线程上执行。
    如果一个对象在意外时间被自动释放,它将在它碰巧所在的任何线程的自动释放池块上被释放。
    这对于应该只从一个线程触及的资源来说很容易是致命的。

5、集合拥有它们包含的对象

当您将对象添加到集合(例如数组、字典或集合)时,集合将获得它的所有权。
当对象从集合中删除或集合本身被释放时,集合将放弃所有权。
因此,例如,如果您想创建一个数字数组,您可以执行以下操作之一:

NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
    [array addObject:convenienceNumber];
}

在这种情况下,您没有调用alloc,所以没有必要调用release
没有必要保留新数字(convenienceNumber),因为数组会这样做。

NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
    [array addObject:allocedNumber];
    [allocedNumber release];
}

在这种情况下,您确实需要在allocedNumber``for循环的范围内发送release消息来平衡alloc
由于数组在addObject:添加时保留了数字,因此它在数组中时不会被释放。

要理解这一点,请把自己放在实现集合类的人的位置上。
您想确保没有让您照顾的对象从您下面消失,因此您在它们传入时向它们发送一条retain消息。
如果它们被删除,您必须发送一条平衡release消息,任何剩余的对象都应该在您自己的dealloc方法期间发送一条release消息。


6、使用保留计数实施所有权政策

所有权策略是通过引用计数来实现的——通常在保留方法之后称为“retain计数”。
每个对象都有一个保留计数。

  • 当您创建一个对象时,它的保留计数为1。
  • 当您向对象发送retain消息时,其保留计数将增加1。
  • 当您向对象发送release消息时,它的保留计数将减少1。
    当您向对象发送autorelease消息时,它的保留计数在当前自动释放池块的末尾减1。
  • 如果对象的保留计数减少到零,则释放它。

**重要提示:**应该没有理由明确询问对象的保留计数是多少(请参阅retainCount)。
结果通常会产生误导,因为您可能不知道哪些框架对象保留了您感兴趣的对象。
在调试内存管理问题时,您应该只关心确保您的代码符合所有权规则。


四、使用自动释放池块

自动释放池块提供了一种机制,通过这种机制,您可以放弃对象的所有权,但避免它立即被释放的可能性(例如,当您从方法返回对象时)。
通常,您不需要创建自己的自动释放池块,但在某些情况下,您必须这样做或者这样做是有益的。


1、关于自动释放池块

自动释放池块使用@autoreleasepool进行标记,如下例所示:

@autoreleasepool {
    // Code that creates autoreleased objects.
}

在自动释放池块的末尾,在块内接收到autorelease消息的对象被发送release消息——每次在块内发送autorelease消息时,对象都会收到release消息。

与任何其他代码块一样,自动释放池块可以嵌套:

@autoreleasepool {
    // . . .
    @autoreleasepool {
        // . . .
    }
    . . .
}

(您通常不会看到与上述完全相同的代码;通常,一个源文件中自动释放池块中的代码将调用另一个源文件中包含在另一个自动释放池块中的代码。)对于给定的autorelease消息,相应的release消息将在发送autorelease消息的自动释放池块的末尾发送。

Cocoa总是期望代码在自动释放池块中执行,否则自动释放的对象不会被释放,您的应用程序会泄漏内存。
(如果您在自动释放池块之外发送autorelease消息,Cocoa会记录一条合适的错误消息。)AppKit和UIKit框架处理自动释放池块中的每个事件循环迭代(例如鼠标向下事件或点击)。
因此,您通常不必自己创建自动释放池块,甚至不必查看用于创建自动释放池块的代码。
但是,您可以在三种情况下使用自己的自动释放池块:

  • 如果您正在编写不基于UI框架的程序,例如命令行工具。
  • 如果您编写一个创建许多临时对象的循环。
    您可以在循环内使用自动释放池块来在下一次迭代之前处理这些对象。
    在循环中使用自动释放池块有助于减少应用程序的最大内存占用。
  • 如果您生成一个辅助线程。
    您必须在线程开始执行后立即创建自己的自动释放池块;否则,您的应用程序将泄漏对象。
    (有关详细信息,请参阅自动释放池块和线程。)

2、使用本地自动释放池块来减少峰值内存占用

许多程序创建自动释放的临时对象。
这些对象会增加程序的内存占用,直到块结束。
在许多情况下,允许临时对象累积到当前事件循环迭代结束不会导致过多的开销;但是,在某些情况下,您可能会创建大量临时对象,这些对象会大大增加内存占用,并且您希望更快地处理它们。
在后一种情况下,您可以创建自己的自动释放池块。
在块结束时,临时对象被释放,这通常会导致它们的释放,从而减少程序的内存占用。

下面的示例显示了如何在for循环中使用本地自动释放池块for方法。

NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
 
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding error:&error];
        /* Process the string, creating and autoreleasing more objects. */
    }
}

循环一for处理一个文件。
在自动释放池中发送autorelease消息的任何对象(如fileContents)都会在块的末尾释放。

在自动释放池块之后,您应该将块内自动释放的任何对象视为“已释放”。
不要向该对象发送消息或将其返回给方法的调用者。
如果您必须在自动释放池块之外使用临时对象,您可以通过向块内的对象发送retain消息,然后在块后发送autorelease,如本例所示:

(id)findMatchingObject:(id)anObject {
 
    id match;
    while (match == nil) {
        @autoreleasepool {
 
            /* Do a search that creates a lot of temporary objects. */
            match = [self expensiveSearchForObject:anObject];
 
            if (match != nil) {
                [match retain]; /* Keep match around. */
            }
        }
    }
 
    return [match autorelease];   /* Let match go and return it. */
}

发送retain到自动释放池块内的match,并在自动释放池块后发送autorelease给它,延长了match的生命周期,并允许它接收循环之外的消息并返回给findMatchingObject:的调用者。


3、自动释放池块和线程

Cocoa应用程序中的每个线程都维护自己的自动释放池块堆栈。
如果您正在编写仅限Foundation的程序或分离线程,则需要创建自己的自动释放池块。

如果您的应用程序或线程是长期存在的,并且可能会生成大量自动释放的对象,您应该使用自动释放池块(就像主线程上的AppKit和UIKit一样);否则,自动释放的对象会累积,您的内存占用会增加。
如果您的分离线程不进行Cocoa调用,则不需要使用自动释放池块。

**注意:**如果您使用POSIX线程API而不是NSThread创建辅助线程,则不能使用Cocoa,除非Cocoa处于多线程模式。
Cocoa只有在分离其第一个NSThread对象后才能进入多线程模式。
要在辅助POSIX线程上使用Cocoa,您的应用程序必须首先分离至少一个NSThread对象,该对象可以立即退出。
您可以使用NSThread类方法isMultiThreaded测试Cocoa是否处于多线程模式。


2024-06-16(日)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/733405.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法题--华为od机试考试(整数对最小和、素数之积、找城市)

目录 整数对最小和 题目描述 注意 输出描述 示例1 输入 输出 说明 解析 答案 素数之积 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入 输出 说明 解析 找城市 题目描述 输入 输出 示例1 输入 输出 示例2 输入 输出 说明 解析…

嵌入式通信协议-----UART协议详解(基于智芯Z20k11X)

目录 一、简介 1.概念 2.结构 3.特点 4.优缺点 二、协议帧组成 1.起始位 2.数据位 3.奇偶校验位 4.停止位 三、UART通信过程 四、USART与UART区别 五、代码实现 1.硬件框图 2.软件实现 一、简介 1.概念 USART&#xff08;Universal Synchronous Asynchronous R…

相机的标定

文章目录 相机的标定标定步骤标定结果影响因素参数分析精度提升一、拍摄棋盘格二、提升标定精度 标定代码实现 相机的标定 双目相机的标定是确保它们能够准确聚焦和成像的关键步骤。以下是详细的标定步骤和可能的结果&#xff0c;同时考虑了不同光照条件和镜头光圈大小等因素对…

怎样去掉卷子上的答案并打印

当面对试卷答案的问题时&#xff0c;一个高效而简单的方法是利用图片编辑软件中的“消除笔”功能。这种方法要求我们首先将试卷拍摄成照片&#xff0c;然后利用该功能轻松擦除答案。尽管这一方法可能需要些许时间和耐心&#xff0c;但它确实为我们提供了一个可行的解决途径。 然…

Docker网络介绍

网络是虚拟化技术中最复杂的部分&#xff0c;也是Docker应用中的一个重要环节。 Docker中的网络主要解决容器与容器、容器与外部网络、外部网络与容器之间的互相通信的问题。 这些复杂情况的存在要求Docker有一个强大的网络功能去保障其网络的稳健性。因此&#xff0c;Docker…

windows10远程桌面端口,Windows 10远程桌面端口修改的两个方法

在Windows 10系统中&#xff0c;远程桌面功能允许用户通过网络从一台计算机远程访问和控制另一台计算机。默认情况下&#xff0c;远程桌面服务使用的端口是3389。然而&#xff0c;出于安全考虑&#xff0c;许多管理员和用户希望修改这一默认端口。本指南将详细介绍如何在Window…

柯桥商务英语培训|老外和你说Tom和Jack,可不是在说人名!所以是啥意思?

小明和小李&#xff0c;这两个人在中国相信没有谁不认识他们了。而且有关他们的梗更是传遍大街小巷。 例如&#xff1a;小明他爷爷活了103岁&#xff0c;小明做数学题&#xff0c;又或者是小李的老婆比小明小2岁等等。 其实在国外&#xff0c;也有这么两个人像小明、小李一样&a…

WPF/C#:显示分组数据的两种方式

前言 本文介绍自己在遇到WPF对数据进行分组显示的需求时&#xff0c;可以选择的两种方案。一种方案基于ICollectionView&#xff0c;另一种方案基于IGrouping。 基于ICollectionView实现 相关cs代码&#xff1a; [ObservableProperty] private ObservableCollection<Peo…

【mysql】常用操作:维护用户/开启远程/忘记密码/常用命令

一、维护用户 1.1 创建用户 -- 语法 > CREATE USER [username][host] IDENTIFIED BY [password];-- 例子&#xff1a; -- 添加用户user007&#xff0c;密码123456&#xff0c;并且只能在本地可以登录 > CREATE USER user007localhost IDENTIFIED BY 123456; -- 添加用户…

利用第三方服务对目标进行被动信息收集防止被发现(web安全白帽子)

利用第三方服务对目标进行被动信息收集防止被发现&#xff08;web安全白帽子&#xff09; 1 被动信息收集1.1 信息收集内容1.2 信息用途 2 信息收集-DNS2.1 DNS信息收集NSLOOKUP2.1.1 ping2.1.2 nslookup 2.2 DNS信息收集-DIG&#xff08;此命令查到的结果更复杂些&#xff0c;…

java中实现Callable方式创建线程

一、为啥要引入Callable 在前面讲了通过继承Thread和实现Runnable方式创建线程的区别&#xff0c;那为什么有了Runnable还要引入Callable?下面通过实现Runnable方式的弊端给出答案 实现Runnable方式的弊端&#xff1a; package java.lang; FunctionalInterface public inte…

C++基础知识——《缺省参数》和《函数重载》

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;Yan. yan.                        …

pwn1_sctf_2016

首先找到后门 32位IDA 打开 这里fgets,他限定了位数,我们无法利用溢出 但是我们可以看见 最后还有个操作 他把我们里面的I,全部替换为了 you S大小为0x3c ---意思我们要输入0x3c/3 的I 能达到溢出的目的 再加上4 from pwn import * ghust remote("node5.buuoj.cn&q…

如何解决跨区域文件传输存在的安全管控问题?

⼤型企业和集团为扩⼤市场份额、优化资源配置&#xff0c;会在不同地区设⽴多级下属分⽀机构、研发中心、实验室等&#xff0c;存在研发数据横向或纵向流转的需求&#xff0c;研发数据进行跨区域文件传输的场景。跨区域可能是网络区域&#xff0c;也可能是地理区域&#xff0c;…

渗透测试-若依框架的杀猪交易所系统管理后台

前言 这次是带着摸鱼的情况下简单的写一篇文章&#xff0c;由于我喜欢探究黑灰产业&#xff0c;所以偶尔机遇下找到了一个加密H币的交易所S猪盘&#xff0c;我记得印象是上年的时候就打过这一个同样的站&#xff0c;然后我是通过指纹查找其它的一些站&#xff0c;那个站已经关…

【深度学习】实现基于MNIST数据集的TensorFlow/Keras深度学习案例

基于TensorFlow/Keras的深度学习案例 实现基于MNIST数据集的TensorFlow/Keras深度学习案例0. 什么是深度学习&#xff1f;1. TensorFlow简介2. Keras简介3. 安装TensorFlow前的注意事项4. 安装Anaconda3及搭建TensorFlow环境1&#xff09; 下载安装Anaconda Navigator2&#xf…

c++ 内存分析模型、引用

一、内存模型分区 内存四区的意义&#xff1a; 不同区域存放的数据&#xff0c;赋予不同的生命周期&#xff0c;给我们更大的灵活编程 &#xff08;一&#xff09;程序运行前 在程序编译后&#xff0c;生成了exe可执行程序&#xff0c;未执行程序前分为两个区域 代码区&…

C++ 89 之 string查找和替换

#include <iostream> #include <string> using namespace std;int main() { // int find(const string& str, int pos 0) const; //查找str第一次出现位置,从pos开始查找 // int find(const char* s, int pos 0) const; //查找s第一次出现位置,从pos开始查找…

【单片机毕业设计选题24020】-全自动鱼缸的设计与应用

系统功能: &#xff08;1&#xff09;检测并控制鱼缸水温&#xff0c;水温低于22℃后开启加热&#xff0c;高于28℃后关闭加热。 &#xff08;2&#xff09;定时喂食&#xff0c;每天12点和0点喂食一次&#xff0c;步进电机开启后再关闭模拟喂食。 &#xff08;3&#xff09…

路由器基础配置以及静态路由配置

1、搭建网络 搭建网络拓扑、分配IP地址、划分网段、连接端口 2、配置路由器 路由器基础配置 //进入全局配置模式 Router#enable Router#conf t Enter configuration commands, one per line. End with CNTL/Z.//配置高速同步串口serial2/0 Router(config)#int ser2/0 Route…