dealloc中的自定义UIButton内存管理

时间:2010-12-31 05:10:45

标签: iphone ios memory-management uibutton dealloc

我希望澄清这里发生的过程。

我创建了一个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]后我是否需要将保留计数降为零?

干杯。

3 个答案:

答案 0 :(得分:5)

这应该得到两个答案......一个用于特定问题,一个用于在-init(这个)中替换实例时如何管理内存。

初始化程序在Objective-C内存管理领域是一个奇怪的鸟。实际上,您正在管理self。在输入时,保留self。退出时,您需要返回 保留对象 - 不必与self - {{1}相同的对象}。

所以,打破nil的标准习语:

[[[Foo alloc] init] autorelease]

请注意,所有保留计数[RC]均表示为增量

因此,在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) 方法中,您通常根本不会更改init的保留计数!

但是,如果您想要返回其他一些对象,则无论您是什么,都需要self release self将返回(无论是当时分配还是以前在其他地方分配,例如从缓存中检索对象时)。

具体来说,由于这个答案过于迂腐,所以一切都被吹成了个别表达:

retain

答案 1 :(得分:4)

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

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

  6. 第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;
    

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

    例如,以下代码非常非常错误:

    CustomButton

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

    对于您的视图控制器的- (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; } 方法,如果您已初始化按钮,则必须调用dealloc,如图所示。这是遵循以下规则:您必须释放您分配,保留或复制的任何内容。处理这个问题的一个更好的方法是让控制器的视图取得该按钮的所有权(当你将按钮添加为子视图时它会自动执行):

    [myButton release]

    现在,您永远不必担心再次释放该按钮。视图拥有它,并在视图本身被释放时释放它。


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

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

    正确。

      

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

    也正确。

      

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

    排序myButton = [[CustomButton alloc] initWithTitle:@"Title" frame:someFrame]; // RC = 1 [self.view addSubview:myButton]; // RC = 2 [myButton release]; // RC = 1 保留计数在您记录时可能会或可能不会下降到零。自动释放的对象可能仍然具有一个保留计数,因为它们实际上属于当前运行循环的自动释放池。就此而言,视图本身可能仍然属于尚未发布的窗口。您唯一需要担心的是平衡自己的内存管理。有关详细信息,请参阅Apple's memory management guide。从viewController的角度来看,您已经分配了一次按钮,因此您必须只释放一次。当谈到你的自定义:)方法时,事情变得有点棘手,但原理是一样的。已经分配了一块内存,因此必须将其释放(第1部分),并且(第2部分)init应该返回一个保留计数为1的对象(稍后将被正确释放)。


    第5部分:改进建议:

    您可以通过简单地创建自己的工厂方法来避免大多数自定义初始化程序混乱,其精神与init...提供的方法相同:

    UIButton

    请注意,此方法仍可能导致内存访问错误,如第2部分所述

答案 2 :(得分:1)

首先:

不要调用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]进行分配。您可以尝试手动初始化实例ala UIViewUIControl,但这可能会带来很多麻烦。

你想做什么?