【问题标题】:Custom UIButton Memory Management in deallocdealloc 中的自定义 UIButton 内存管理
【发布时间】:2011-06-01 21:04:49
【问题描述】:

我希望澄清这里正在进行的过程。

我创建了一个 UIButton 的子类,它的 init 方法如下所示:

- (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame {
    self = [UIButton buttonWithType:UIButtonTypeCustom];
    [self setTitle:title forState:UIControlStateNormal];
    self.frame = btnFrame;
    return self;
}

在我的视图控制器中,我正在创建这些按钮之一并将其添加为子视图:

myButton = [[CustomButton alloc] initWithTitle:@"Title" frame:someFrame];
[self.view addSubview:myButton];

在视图控制器的 dealloc 方法中,我记录了按钮的保留计数:

- (void)dealloc {
    NSLog(@"RC: %d", [myButton retainCount]); //RC = 2
    [super dealloc];
    NSLog(@"RC: %d", [myButton retainCount]); //RC = 1
}

按照我的理解,myButton 实际上并没有保留,即使我使用alloc 调用它,因为在我的子类中我创建了一个自动释放按钮(使用buttonWithType:)。

在dealloc 中,这是否意味着,当调用 dealloc 时,superview 释放按钮并且其保留计数下降到 1?该按钮尚未自动释放?

或者我是否需要在调用 [super dealloc] 后将保留计数减为零?

干杯。

【问题讨论】:

    标签: iphone ios memory-management uibutton dealloc


    【解决方案1】:

    这值得两个答案....一个针对特定问题,另一个针对在-init(这个)中替换实例时如何管理内存。

    初始化器在 Objective-C 内存管理领域是一只奇怪的鸟。实际上,您正在管理self。进入时,self 被保留。退出时,您应该返回 either 一个保留对象 -- 不必与 self 相同的对象 -- 或 nil。 p>

    所以,打破[[[Foo alloc] init] autorelease] 的标准习语:

    id x = [Foo alloc]; // allocates instance with RC +1
    x = [x init]; // RC is preserved, but x may differ
    [x autorelease]; // RC -1 (sometime in future)
    

    请注意,所有保留计数 [RC] 都表示为 deltas。

    因此,在init 方法中,您通常根本不会更改self 的保留计数!

    然而,如果你想返回一些其他的对象,你需要releaseself和retain无论你要返回什么(无论分配然后或之前分配到其他地方,比如从缓存中检索对象时)。

    具体来说,因为这个答案过于迂腐,所以一切都被吹成了单独的表达方式:

    - init {
        [self release];
        self = nil;
        id newObject = [SomeClass alloc];
        newObject = [newObject init];
        if (newObject) {
            self = newObject;
            ... initialize self here, if that is your fancy ...
        }
        return self;
    }
    

    【讨论】:

      【解决方案2】:

      这有点棘手。我将我的答案总结为 5 个部分:

      1. 创建返回不同对象的自定义 init 方法
      2. 警告:小心非法内存访问!
      3. 如何正确地将按钮的所有权转移到其父视图
      4. 具体问题的具体答案
      5. 改进建议

      第 1 部分:创建返回不同对象的自定义 init 方法:

      这是一个非常特殊情况的示例,即从-initWithTitle:frame: 返回的对象不是最初发送消息的“对象”。

      一般来说,流程是这样的:

      instance = [Class alloc];
      [instance init];
      ...
      [instance release];
      

      当然,alloc 和 init 调用通常组合到一行代码中。这里要注意的关键是已经分配了接收到 init 调用的“对象”(此时只不过是分配的内存块)。如果您返回一个 不同 对象(如您的示例中所示),则您有责任释放该原始内存块。

      下一步是返回一个具有正确保留计数的新对象。由于您使用的是工厂方法 (+buttonWithType:),因此生成的对象已被自动释放,您必须保留它以设置正确的保留计数。

      编辑:正确的-init 方法应该明确测试以确保它在之前使用正确初始化的对象与该对象的其他任何东西。我的答案中缺少此测试,但出现在 bbum's answer 中。

      下面是您的 init 方法的外观:

      - (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame {
          [self release]; // discard the original "self"
          self = [UIButton buttonWithType:UIButtonTypeCustom];
          if (self == nil) { return nil; }
          [self retain]; // set the proper retain count
          [self setTitle:title forState:UIControlStateNormal];
          self.frame = btnFrame;
          return self;
      }
      

      第 2 部分: 警告:小心非法内存访问!

      如果您分配CustomButton 的实例,然后将其替换为UIButton 的实例,则很容易导致一些非常细微的内存错误。假设CustomButton 有一些 ivars:

      @class CustomButton : UIButton
      {
          int someVar;
          int someOtherVar;
      }
      ...
      @end;
      

      现在,当您在自定义 init... 方法中将分配的 CustomButton 替换为 UIButton 的实例时,您返回的内存块太小而无法容纳 CustomButton,但您的代码将继续将此代码块视为是一个完整大小的CustomButton。哦哦。

      例如,下面的代码现在非常,非常不好:

      - (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame {
          [self release]; // discard the original "self"
          self = [UIButton buttonWithType:UIButtonTypeCustom];
          [self retain]; // set the proper retain count
      
          someOtherVar = 10; // danger, Will Robinson!
      
          return self;
      }
      

      第 3 部分:如何正确地将按钮的所有权转移到其父视图:

      至于视图控制器的dealloc 方法,如果您已经初始化按钮,则必须调用[myButton release],如图所示。这是为了遵循您必须释放您分配、保留或复制的任何内容的规则。处理此问题的更好方法是让控制器的视图拥有该按钮的所有权(当您将按钮添加为子视图时它会自动完成):

      myButton = [[CustomButton alloc] initWithTitle:@"Title"
                                               frame:someFrame]; // RC = 1
      [self.view addSubview:myButton];                           // RC = 2
      [myButton release];                                        // RC = 1
      

      现在,您再也不用担心释放该按钮了。视图拥有它,并在视图本身被释放时释放它。


      第 4 部分:具体问题的具体答案:

      问:按照我的理解,myButton 实际上并没有保留,即使我使用 alloc 调用它,因为在我的子类中我创建了一个自动释放按钮(使用 buttonWithType:)。

      正确。

      问:在dealloc中,这是否意味着,当调用dealloc时,superview释放按钮并且它的retain count下降到1?按钮尚未自动释放?

      也正确。

      问:或者我需要在调用 [super dealloc] 后将保留计数减为零?

      类似于:) 保留计数可能会或可能不会在您记录时降至零。自动释放对象的保留计数可能仍然为 1,因为它们实际上属于当前运行循环的自动释放池。就此而言,视图本身可能仍属于尚未释放的窗口。您真正需要担心的唯一一件事是平衡您自己的内存管理。有关详细信息,请参阅Apple's memory management guide。从您的 viewController 的角度来看,您已经分配了一次按钮,因此您必须准确地释放它一次。当涉及到您的自定义 init... 方法时,事情会变得有点棘手,但原理是一样的。一块内存已分配,因此必须释放它(第 1 部分),并且(第 2 部分)init 应该返回一个保留计数为 1 的对象(稍后将正确释放)。


      第 5 部分:改进建议:

      您可以通过简单地按照UIButton 提供的精神创建自己的工厂方法来避免大多数自定义初始化程序混乱:

      + (id)buttonWithTitle:(NSString *)title frame:(CGRect)btnFrame {
          UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
          [button setTitle:title forState:UIControlStateNormal];
          button.frame = btnFrame;
          return button;
      }
      

      请注意,此方法仍可能导致第 2 部分中确定的内存访问错误

      【讨论】:

      • @bbum: 哪个解释不正确?这已经成为一个很长的答案,我不确定你指的是哪个部分:)
      • @bbum:关于绝对保留计数无用的观点。我仅使用它们来说明 addSubview: 中的隐式保留。
      • 抱歉——初始化器中的保留计数是正确的(除了绝对保留计数讨论)。你的其余答案——虽然很长:)——很好。
      • 谢谢。 :)我特别担心非法内存访问的可能性。我突然想到,这可能会很快变得混乱非常。
      【解决方案3】:

      第一:

      不要调用retainCount

      对象的绝对保留计数几乎是无用的。 总是有更好的方法来推理应用程序中的内存管理。


      下一步:

      您的initWithTitle:frame: 方法正在分配和返回UIButton 的实例,而不是子类的实例。如果这是您想要的,则根本不需要子类。

      如果你真的想要一个 UIButton 子类的实例,那就更难了。快速谷歌搜索和阅读文档表明UIButton 真的不打算被子类化。

      我刚试过:

      @interface FooButton:UIButton
      @end
      @implementation FooButton
      @end
      
      FooButton *f = [FooButton buttonWithType: UIButtonTypeDetailDisclosure];
      NSLog(@"%@", f);
      

      它打印出来了:

      <UIButton: 0x9d03fa0; frame = (0 0; 29 31); opaque = NO; layer = <CALayer: 0x9d040a0>>
      

      即用于创建UIButton 实例的唯一方法非常明确地不通过[self class] 分配。您可以尝试手动初始化实例 UIView 或 UIControl,但这可能会很麻烦。

      你想做什么?

      【讨论】:

        猜你喜欢
        • 2011-07-26
        • 1970-01-01
        • 2023-04-07
        • 2011-09-20
        • 2011-06-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多