【问题标题】:Possible scenarios of using alloc/retain/release?使用 alloc/retain/release 的可能场景?
【发布时间】:2011-03-19 21:44:16
【问题描述】:

我已经使用 ojective c 快一周了,我主要是一个 c++ 编码器。在我阅读了苹果的内存管理指南之后,我尝试将我在 c++ 中的内存使用风格带入客观的 c... 我试图总结这些场景,如果我按照这些说明进行操作,我想我不会犯内存错误。如果我错了,请告诉我:)

我会尽量不使用autorelease,个人来说,使用autorelease,在某些自动释放池被耗尽之前,可能总会有一些冗余内存。我只会使用 release,它可以确保我的应用程序在任何时候都使用最少的内存。

Apple 说的另一件事,我用我自己的话来描述,是:每次我添加一个 retain/alloc/copy,我都应该在某处添加一个 release

以下是我总结的所有场景:

  1. 在同一个函数中:alloc一个对象,使用它,然后释放

  2. 在类的init函数中,alloc是一个对象,在类的dealloc函数中,释放对象

  3. 当需要拥有一个指针时,应该在一个类的方法中保留一个输入指针(比如说方法A),然后释放类的dealloc函数中的指针。

    我发现objective c中使用retain的时机和c/c++中使用memcpy的时机是一样的,所以我取retain 作为“内存高效副本”

    如果输入的保留指针要设置为成员指针变量,则应先释放成员指针。 所以在case[3]中,类的init中的allocmethod A中的release配对, 方法A中的retaindealloc

    中的release配对
  4. 返回一个指针作为返回值。老实说,当我使用 c++ 时,我从不做这样的事情。如果返回一个成员指针也没关系,因为有人会照顾它:

    -(UIButton*) getTheButton() {
        return theButton;
    }
    

    但是返回一个指向本地分配对象的指针真的很糟糕:

    -(UIButton*) getTheButton() {
        UIButton* myButton = [[UIButton alloc] init];
        return myButton; //TERRIBLE!
    } 
    

    有人可能会说在这种情况下我应该使用自动释放,但我只是想通过使用这个来绕过该解决方案:我只会返回成员指针,或者我不会返回指针,我只会对给定的输入指针进行操作。

    -void operateOnTheButton(UIButton* button) {
        [button release];
        button = [[UIButton alloc] init];
    } 
    

所以,如果我按照上面的内存使用说明操作有任何问题,请告诉我。

谢谢:D

【问题讨论】:

  • 次要注意:您的最后一个 sn-p 似乎假设 button 是一个输入输出参数。事实并非如此,输入输出将是(UIButton**)button
  • 我尝试将我在 c++ 中的内存使用风格带入目标 c。那你就会失败。不要对你不熟悉的事物抱有偏见。

标签: objective-c memory-management retain


【解决方案1】:

在一个重要的场景中,您必须使用autorelease。如果您分配/保留/复制一个对象,然后将其返回给您拥有该对象的其他代码,但您不能 release 它,因为其他代码需要使用它(或者您不会还没退)。

在这种情况下,您必须通过向对象发送autorelease 消息来对对象的释放负责,从而确保它最终会被释放。这正是像 NSString 的 stringWithString: 这样的基础方法所做的。您不必 release 从此方法获得的字符串对象,因为您没有分配/保留/复制它,但如果该方法没有为您自动释放它,它将在 [pool drain] 中存活并成为内存泄漏。

确保您了解引用计数的工作原理。 release 减少对象的引用计数。当计数达到 0 时,对象被销毁。 alloccopy 创建一个引用计数为 1 的对象。retain 将接收对象的引用计数增加 1。对于每个分配/保留/复制,必须有一个 release。 1 上,1 下。

因此,当您返回您创建的对象并将控制权传递给被调用者时,您会失去 1 对 1 等式的平衡,因为您无法 release 它。这就是为什么有autorelease。它不会递减接收者的 ref 计数器,因此不会有被销毁的危险,但是当池耗尽时,池中的每个对象都会收到一个 release 消息,它接收到的每个 autorelease 都会恢复平衡(除非有意保留了一个对象,以便它能够在流失后幸存下来,在这种情况下,被调用者需要稍后release它)。

至于第 4 点,如果您 autorelease,您的第二个示例很好,基础课程中这种技术的示例比比皆是。每个数据类,如 NSString、NSArray、NSDictionary 等,都有返回本地分配的对象指针的方法。它们通常具有thisWiththat: 的形式,例如stringWithstring:dictionaryWithContentsOfFile:。后者将分配一个字典对象,用文件的内容填充它,自动释放它,并返回一个指针给你。它被称为工厂函数。 obj-c 中很常见的模式,java 也是。

您的第三个代码示例稍后会导致运行时错误。您正在释放一个您不拥有的对象。所有者不会期望您release 它,这可能意味着它会收到太多release 消息,从而导致运行时错误。如果你没有分配/保留/复制它,你就不拥有它,所以你不应该释放它。

【讨论】:

  • 关于第 4 点,ObjC 代码通常会返回指针成员值 - 想想例如NSString* 属性。调用者有责任妥善管理所有权 - 如果他违反了约定,那是他的错。
  • 第 4 点的推理,示例 3 也不成立:它不会在传入指针上创建内存泄漏。它释放一个它不拥有的传入对象(导致稍后过度释放,从而导致一些 EXC_BAD_ACCESS),然后泄漏一个新实例。
  • 如果你的类可能是一个模型类,那是有道理的,但是你很难像着色器那样概括,这样做总是可以的。不过,您对内存泄漏是正确的。我会编辑我的答案。无论哪种方式,您都应该释放一个不属于您的对象。
  • 如果每个类类型的 getter 实现都这样做,您也不能概括返回指针成员的值违反了类完整性。参见例如Apple 文档中的 getter examples
  • 我认为每个 @synthesizer 在它生成的 get 函数中返回成员指针...
【解决方案2】:

自动释放部分不正确。如果你做一个工厂方法,它不是以 alloc/retain/copy 开头的。你必须自动释放它。调用者不创建对象,所以调用者不能也不应该释放它。因此,如果调用者想要做某事并确保对象存在,调用者必须自己保留它们。

好的解决方案是:

-(UIButton*) getTheButton() {
    UIButton* myButton = [[UIButton alloc] init];
    return [myButton autorelease];  // if you don't do autorelease, memory will leak
} 

然后在调用者中:

- (void)caller {
  UIButton *button = [[self getTheButton] retain];
}

你不能这样做:

-void operateOnTheButton(UIButton* button) {
    [button release];
    button = [[UIButton alloc] init];
} 

原因:operateOnTheButton 不拥有该按钮,它不应该释放该按钮。如果我给你一个按钮,然后在呼叫之后,我有另一个按钮,会发生什么。操作按钮不分配/保留/复制参数。它无权释放它。 它可能会给您带来一些问题:

1/ EXEC_BAD_ACCESS:当你在一个你不拥有该对象的方法中释放时,有人可能仍然使用它

2/ 内存仍然泄漏:释放并不意味着您立即删除内存。您只需将 retainCount 减 1。这意味着如果传递按钮对象时的 retainCount 为 2,则释放它只会使您的 retainCount 为 1,还不会删除对象的内存

3/ 你的按钮对象也泄漏了:没有人可以确保你的按钮对象被释放。你拥有它,所以你必须自己释放它,否则,按钮对象泄漏

【讨论】:

  • @shader 对于 C++ 可能是正确的,但在 Objective-C 中,一个变量有多个所有者是正常的。返回本地分配的对象也是正常的,因为 Objective-C 有autorelease,而 C++ 没有。
  • @shader:这不仅仅是编码风格。这也是关于内存管理的规则,你不创建它,谁允许你释放它。这是 Apple 强烈建议的,我认为您应该遵循。正如 Tom Dalling 所说,我们也有 autorelease
  • 正如 vodkhang 所说,这不是风格问题,而是源于 C++ 和 ObjC 具有根本不同的内存管理模型这一事实。但是,要回答您的问题,是的,在返回本地分配的对象时您所要做的就是自动释放它。如果调用者想要保留返回值,它可以使用保留,否则返回值将在不久的将来被释放。
  • 你知道工厂方法吗?一个非常好的和常见的用例是:与单例一起使用并重用一些内存,例如如果您的系统中只需要 1 个用户默认管理器,您可以执行工厂方法:[NSUserDefault userDefault];它不允许你做 alloc init
  • @Shader,基础类中的例子比比皆是。 NSStringNSArrayNSDictionary 等每个数据类都有返回本地分配的对象指针的方法。它们通常具有thisWiththat: 的形式,例如stringWithstring:dictionaryWithContentsOfFile:。后者将分配一个字典对象,用文件的内容填充它,autorelease 它,并返回一个指针给你。它被称为工厂函数。 obj-c 中很常见的模式,java 也是。
【解决方案3】:
  1. 这种情况是正常的,但有些人(包括我自己)更喜欢在 alloc 的同一行自动释放,以避免意外忘记释放。

  2. init通常会调用alloc。在对象上创建时,首先调用alloc,然后在新分配的实例上调用init

  3. 这种情况非常正确。但是,保留不会复制。

  4. 在 Objective-C 中,对象总是由它们的指针引用,这与 C++ 不同。如果您正在编写一个返回字符串的方法,那么您将返回一个 NSString*,而不是 NSString 结构本身。

下面的sn-p代码实际上会导致内存泄漏:

-(void) operateOnTheButton:(UIButton*)button {
    [button release];
    button = [[UIButton alloc] init];
} 

button 是一个局部变量,当它超出范围时会因为没有释放而泄漏。我不太确定这个operateOnTheButton: 方法应该做什么。

【讨论】:

  • sn-p 也会导致传入的button 过度释放。
【解决方案4】:

为什么要避免 AutoRelease?这是应该的方式......

还有一些建议:

很多人在 iPhone 上犯的一个错误是在 UIViewController 的 viewDidLoad 方法中分配内存,然后在 dealloc 方法而不是 viewDidUnload 方法中释放它。

始终考虑可以多次加载视图。

【讨论】:

    【解决方案5】:

    “在类的init函数中,分配一个对象,在类的dealloc函数中,释放对象”

    首先:作为类的一部分,没有函数。是的,您可以编写 C 函数。您可以编写对实例进行操作的 C 函数。但是,如果您编写其中的一些 -(foo)doSomethings(甚至是 +(foo)doSomethings),您会得到一个消息处理程序。您可以通过发送消息来使用它们。

    这就是为什么您不能从 -(id)init 消息中分配实例的原因:init 想要与内存和其中的超类实例“对话”。由于您没有分配此空间和功能,因此您将获得一些疯狂的内存位置或(更有可能)为零。

    另一方面,你释放对象的方式根本不会释放任何东西。因为 dealloc 是 发送 到对象的时候才会被销毁。但是由于您不释放它(因为您想在 dealloc 中执行此操作),因此该对象将永远不会被放置在“天堂之门”的列表中:它保持保留计数为 1(或更大),直到您的应用程序死亡并将分配内存的对象踢回操作系统的空闲池。 dealloc-message 不是由您发送。 dealloc 仅用于内存管理例程。 (我想,如果最超级的 NSObject,您的实例父母和祖父母的根,将收到一个释放并且它看到保留计数应该低于 1,则发送此消息。)

    如果您真的想在 Objective-C 中进行您的内存管理,您将不得不编写自己的框架;你自己的“符合 C++ 的 Cocoa”。恕我直言。

    问候

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-30
      • 1970-01-01
      相关资源
      最近更新 更多