Apple - Advanced Memory Management Programming Guide 内存管理

news2024/11/28 22:34:19

翻译整理自: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(日)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mac OS 如何在命令行下启动Docker

现象 当用 Mac air作为服务器时&#xff0c;远程登录上去后想使用 docker&#xff0c;却报如下错&#xff1a; Cannot connect to the Docker daemon at unix:///Users/aborn/.docker/run/docker.sock. Is the docker daemon running? 原因分析 因为 docker 有一个守护进程…

WebSocket走私实践(附赠LiveGBS监控系统未授权管理员密码重置)

WebSocket走私实践&#xff08;附赠LiveGBS监控系统未授权管理员密码重置&#xff09; 对此&#xff0c;我特别感谢TryHackMe和HackTheBox academy&#xff0c;永远相信和追随英国TryHackMe所教导的网络安全知识,并保持学习 WebSocket走私相关的知识在这里 前段时间学习过htt…

关于小程序内嵌H5页面交互的问题?

有木有遇到&#xff1f;有木有遇到。 小程序内嵌了H5&#xff0c;然后H5某个按钮&#xff0c;需要打开小程序某个页面进行信息完善或登记&#xff0c;登记后要返回H5页面&#xff0c;而H5页面要动态显示刚才在小程序页面登记的信息。 操作流程是这样&#xff1a; 方案1&#…

一般人不要学Python?一般人怎么学Python!!

关于“建议一般人真的不要学Python”这一观点&#xff0c;我认为这是一个过于绝对的说法。实际上&#xff0c;Python作为一种流行的编程语言&#xff0c;具有许多优点&#xff0c;适合不同背景和需求的人学习。以下是一些反驳这一观点的理由&#xff1a; 易于学习和理解&#x…

学习笔记——路由网络基础——动态路由

五、动态路由 1、动态路由概述 动态路由&#xff1a;通过在设备上运行某种协议&#xff0c;通过该协议自动交互路由信息的过程。 动态路由协议有自己的路由算法&#xff0c;能够自动适应网络拓扑的变化&#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开始查找…

【吊打面试官系列-Mysql面试题】什么是存储过程?用什么来调用?

大家好&#xff0c;我是锋哥。今天分享关于 【什么是存储过程&#xff1f;用什么来调用&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 什么是存储过程&#xff1f;用什么来调用&#xff1f; 答&#xff1a;存储过程是一个预编译的 SQL 语句&#xff0c;优点是…

Flutter-实现头像叠加动画效果

实现头像叠加动画效果 在这篇文章中&#xff0c;我们将介绍如何使用 Flutter 实现一个带有透明度渐变效果和过渡动画的头像叠加列表。通过这种效果&#xff0c;可以在图片切换时实现平滑的动画&#xff0c;使 UI 更加生动和吸引人。 需求 我们的目标是实现一个头像叠加列表&…

2024信友队春季 Day3 二分分治

二分 & 分治 二分 二分查找 也叫折半搜索&#xff0c;用于在一个有序数组中查找某一元素的算法。 给定一个长度为 n n n 的从小到大排列的数列 a a a 和 q q q 次询问&#xff0c;每次询问给定 x x x&#xff0c;查找 x x x​ 在数列中的位置。 n , q ≤ 1 0 6 n,q\…

【机器学习】大模型驱动下的医疗诊断应用

摘要&#xff1a; 随着科技的不断发展&#xff0c;机器学习在医疗领域的应用日益广泛。特别是在大模型的驱动下&#xff0c;机器学习为医疗诊断带来了革命性的变化。本文详细探讨了机器学习在医疗诊断中的应用&#xff0c;包括疾病预测、图像识别、基因分析等方面&#xff0c;并…

cherry 笔记三(启动)

cherry启动很简单 app创建完 直接startup()就好了 func main() {app : cherry.Configure("./examples/config/profile-chat.json","chat-master",false,cherry.Cluster,)app.SetSerializer(cserializer.NewJSON())app.Startup() } Configure()--->N…

深入理解RunLoop

RunLoop 是 iOS 和 OSX 开发中非常基础的一个概念&#xff0c;这篇文章将从 CFRunLoop 的源码入手&#xff0c;介绍 RunLoop 的概念以及底层实现原理。之后会介绍一下在 iOS 中&#xff0c;苹果是如何利用 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的。 一…

01_01_Mybatis的介绍与快速入门

一、数据持久层框架的发展历程 1、JDBC JDBC&#xff08;Java Data Base Connection&#xff09;&#xff0c;是一种用于执行SQL语句的Java API&#xff0c;为多种关系型数据库提供了统一访问的方式&#xff0c;它由一组用Java语言编写的类和接口组成。JDBC提供了一种规范&…

meterpeter基础及永恒之蓝漏洞利用

meterpeter基础知识 meterpeter为渗透测试框架&#xff0c;主要用于后渗透模块 后渗透&#xff1a;拿到shell后进行的操作&#xff08;远程&#xff0c;权限维持&#xff0c;入侵痕迹清除&#xff0c;上机关闭杀毒软件&#xff09; msf能干什么 相当于一个漏洞库&#xff08…

时序预测 | Matlab基于Transformer多变量时间序列多步预测

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于Transformer多变量时间序列多步预测&#xff1b; 2.多变量时间序列数据集&#xff08;负荷数据集&#xff09;&#xff0c;采用前96个时刻预测的特征和负荷数据预测未来96个时刻的负荷数据&#xff1b; 3…

【大数据离线项目三:将HIve数仓中的dim进行拉宽聚合处理,PostgreSQL的使用,以及怎么将数据导出到PostgreSQL数据库中】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;今天主要和大家分享一下将HIve数仓中的dim进行拉宽聚合处理&#xff0c;PostgreSQL的使用&#xff0c;以及怎么将数据导出到PostgreSQL数据库中,希望对大家有所帮助。 &#x1f49e;&…

“论软件的可靠性评价”必过范文,软考高级,系统架构设计师论文

论文真题 软件可靠性评价是软件可靠性活动的重要组成部分,既适用于软件开发过程,也可针对最终软件系统。在软件开发过程中使用软件可靠性评价,可以使用软件可靠性模型,估计软件当前的可靠性,以确认是否可以终止测试并发布软件,同时还可以预计软件要达到相应的可靠性水平…

html做一个雷达图的软件

要实现一个在线输入数据并生成雷达图的功能&#xff0c;可以使用HTML表单和JavaScript来处理用户输入的数据。以下是一个示例代码&#xff0c;演示了如何实现这个功能&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"…

Redis进阶 - 朝生暮死之Redis过期策略

概述 Redis 是一种常用的内存数据库&#xff0c;其所有的数据结构都可以设置过期时间&#xff0c;时间一到&#xff0c;就会自动删除。你可以想象 Redis 内部有一个死神&#xff0c;时刻盯着所有设置了过期时间的 key&#xff0c;寿命一到就会立即收割。 你还可以进一步站在死神…

C/C++ vector模拟实现

模拟实现&#xff1a; 框架 namespace yx {template<class T>class vector{public:typedef T* iterator;private:iterator _start;iterator _finish;iterator _end_of_storage;}; } 这里我们声明定义不分离 reverse() 新开一个空间&#xff0c;拷贝数据&#xff0c;然…