1

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

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

干杯。

4

3 回答 3

5

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

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

所以,打破标准的习惯用法[[[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

但是,如果您想返回其他对象,则需要返回release self 任何 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;
}
于 2010-12-31T06:07:21.727 回答
4

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

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

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

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

通常来说,这个过程是这样的:

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

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

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

编辑:一个正确的-init方法应该明确地测试以确保它在对一个正确初始化的对象做任何其他事情之前,它正在使用该对象。我的答案中缺少此测试,但出现在bbum 的答案中。

这是您的 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的实例替换分配的内存时,您返回的内存块太小而无法容纳 a ,但您的代码将继续将此代码块视为尺寸. 哦哦。UIButtoninit...CustomButtonCustomButton

例如,下面的代码现在非常非常糟糕:

- (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 的内存管理指南。从您的 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 部分中确定的内存访问错误

于 2010-12-31T05:36:42.470 回答
1

First:

Do not call retainCount

The absolute retain count of an object is next to useless. There are always better ways to reason about memory management in your application.


Next:

Your initWithTitle:frame: method is allocating and returning an instance of UIButton, not an instance of the subclass. If that is what you want, there is no need for a subclass at all.

If you really want an instance of a subclass of UIButton, that is going to be more difficult. A quick google search and a read of the documentation indicates that UIButton really isn't intended to be subclassed.

I just tried:

@interface FooButton:UIButton
@end
@implementation FooButton
@end

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

And it printed:

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

I.e. the sole method to be used to create UIButton instances quite explicitly does not allocate via [self class]. You could go down the path of trying to initialize the instance by hand ala UIView or UIControl, but that is likely a lot of trouble.

What are you trying to do?

于 2010-12-31T05:48:23.730 回答