内存管理 - 何时释放?

4

我已经自学了一段时间的Objective C,但仍然不太明白内存管理。什么时候应该释放属性?

例如,我有一个类来处理两个不同的URLRequest连接(register和updateParticulars)。当registerConnection完成时,将执行updateParticularsConnection。

@interface ConnectionViewController : UIViewController {

}
@property (nonatomic, retain) NSURLConnection *registerConnection;
@property (nonatomic, retain) NSURLConnection *updateParticularsConnection;
@property (nonatomic, retain) NSMutableData *responseData;
@property (nonatomic, retain) NSMutableURLRequest *requestURL;

@end

@implementation ConnectionViewController
@synthesize registerConnection, updateParticularsConnection, responseData, requestURL,


(void)performRegistration {
    // other here to prepare the data.
    requestURL = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"myURL"]];
    registerConnection = [[NSURLConnection alloc] initWithRequest:requestURL delegate:self startImmediately:YES];
}

(void)updateParticulars {
    // other here to prepare the data.
     [requestURL setURL:[NSURL URLWithString:@"http:myURL.com"]];
     updateParticularsConnection = [[NSURLConnection alloc] initWithRequest:requestURL delegate:self startImmediately:YES];
}

处理委托回调

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [SVProgressHUD dismissWithError:@"Unable to connect"];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    if (responseData == nil) {
        responseData = [[NSMutableData alloc] init];
    }
    [responseData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    if (connection == registerConnection) {
        NSMutableString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
        NSLog(@"Register connection recieved data reads : %@", responseString);
        if ([responseString isEqualToString:@"-1"]) { // error. stop connection
            [self.requestURL release]; // remember to release requestURL since we would not be continuing on.
        }   
        else if ([responseString isEqualToString:@""]) { // error. stop connection

            [self.requestURL release]; //remember to release requestURL since we would not be continuing on.
        }

        else {
            [self updateParticulars]; // perform next connection, updateParticulars

        }       
        responseData = nil; // clear the current stored data in preparation for the next connection.
        [self.registerConnection release];
        [responseString release];
    } // end of definition for register connection

    else if (connection == updateParticularsConnection) {
            // do stuff with data received back here 
        self.responseData = nil;
        [self.requestURL release];
        [self.updateParticularsConnection release];
    }       
}

我的问题是,我应该尽快释放我的属性,这也是我现在正在做的吗?还是只在dealloc方法中释放?如果我做得不对,请给予建议。


可能是Objective-C内存管理:何时释放?的重复问题。 - jscs
5个回答

2

在处理这个问题时,需要根据具体情况进行考虑。一般来说,如果不是一个微不足道的分配(例如 NSString * firstName),你可以等到 dealloc 或者被替换(例如 setFirstName:)之后再释放内存。这样做只是为了简化实现。

但是,你提出的例子略有不同。

// typically, you will set these to nil right when they 
// have finished and you have grabbed what you need.
// that's pretty common for an async one shot request.
@property (nonatomic, retain) NSURLConnection *registerConnection;
@property (nonatomic, retain) NSURLConnection *updateParticularsConnection;
@property (nonatomic, retain) NSMutableURLRequest *requestURL;

并且

// in the context of your program, this allocation could be large.
// in many cases, you'll simply convert it to the destination (if
// it's an image, just turn it into an image without holding onto
// the data representation), then dispose of it.
@property (nonatomic, retain) NSMutableData *responseData;

重要提示:在操作过程中直接处理实例变量会令您陷入困境,应使用存取器。以下是使用存取器编写程序所做出的更改:

- (void)performRegistration {
    self.requestURL = [[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"myURL"]] autorelease]; // use setter and autorelease
    self.registerConnection = [[[NSURLConnection alloc] initWithRequest:requestURL delegate:self startImmediately:YES] autorelease]; // use setter and autorelease
}

- (void)updateParticulars {
    [self.requestURL setURL:[NSURL URLWithString:@"http:myURL.com"]]; // use getter
    self.updateParticularsConnection = [[[NSURLConnection alloc] initWithRequest:requestURL delegate:self startImmediately:YES] autorelease]; // use setter and autorelease
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    if (self.responseData == nil) { // use getter
        self.responseData = [NSMutableData data]; // use setter and autorelease
    }
    [self.responseData appendData:data]; // use getter
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    if (connection == self.registerConnection) { // use getter
        NSMutableString *responseString = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; // use getter
        NSLog(@"Register connection recieved data reads : %@", self.responseString); // use getter
        if ([responseString isEqualToString:@"-1"]) {
            self.requestURL = nil; // use setter
        }
        else if ([responseString isEqualToString:@""]) {
            self.requestURL = nil; // use setter
        }
        else {
            [self updateParticulars];
        }
        self.responseData = nil; // use setter
        self.registerConnection = nil; // use setter
        [responseString release];
    }
    else if (connection == self.updateParticularsConnection) { // use getter
        self.responseData = nil; // use setter
        self.requestURL = nil; // use setter
        self.updateParticularsConnection = nil; // use setter
    }
}

你的意思是我应该使用 self.requestURL = [[NSMutableURLRequest alloc] init... 而不是仅仅使用 requestURL = alloc init 吗?每当我访问属性时,我都应该经过访问器吗?有没有特殊情况不需要使用访问器? - Gavin
你的意思是我应该使用self.requestURL = [[NSMutableURLRequest alloc] init...而不仅仅是requestURL = alloc init吗?- 是的,只需添加一个release或autorelease。每当我访问属性时,我都应该通过访问器来访问吗?- 当你刚开始学习时,是的。随着时间的推移,你会了解到一些情况下直接访问更理想。有任何特殊情况不应该使用访问器吗?- 不要在部分构造状态(init..., dealloc)中使用它们。 - justin
另外,您不应该使用[self.requestURL release],而是应该使用self.requestURL = nil - justin

2
我只为可以从类外部获取/设置的公共类变量使用@property。对于像您拥有的requestURL这样的私有变量,不需要创建retain属性。
在定义中声明为retain的每个变量中,将self.variable设置会使保留计数增加一。必须记住这一点,因为最常见的泄漏问题是将已保留的值设置为属性,例如self.myString = [[NSString alloc] init]。在这里,myString的保留计数将是2,即使您没有预期也是如此。
所以你的问题是何时释放? 对于@property:在类的dealloc方法中释放,对于私有变量:在使用完毕后立即释放。

1

通常情况下,您应该尽量保持alloc/retain和release在同一范围内。如果值是一个属性,它的作用域对于对象实例来说是全局的,并且应该在dealloc方法中释放。如果值是一个方法中的局部变量(并且不会被分配给更广泛范围的变量),那么显然您必须在该方法中释放它,并且您应该尝试在与执行alloc/retain的{}括号相同的位置进行释放。

例如,如果您发现在代码的某个点之后不再需要一个属性,则可以在该点处将其nil掉,但最好还是在dealloc方法中留下release。

对于具有委托的对象,有时在委托方法中进行对象的释放会起作用,但您应该始终在alloc位置记录此操作,并确保所有委托的“最终”路径都会进行释放。

这些不是硬性规则,但它们可以避免许多问题。在修改程序的过程中,很容易改变控制流或类似的操作,并导致错失“巧妙放置”的释放。遵守作用域规则后,您很少会遇到这种问题。(并记录任何违反作用域规则的异常情况。)


0

关于 release 的速查表:

如果你用来创建对象的方法包含单词 newcopyalloc,那么你赢了,一旦你不再需要引用,就必须释放它。


0

你说得对。

当需要使用时,可以创建对象,而且在不再需要它们时立即释放,这样可以节省内存。在init方法中为所有属性和iVars分配内存可能会减缓实例化过程。在Cocoa中寻找对象的内存是最慢的任务之一,因此必须尽量在CPU使用率和内存消耗之间取得完美平衡——在这个级别上,您无需担心CPU使用率。

在Cocoa中,如果向nil对象发送消息,则什么也不会发生,因此如果您确定为每个copy/alloc/retain调用了release,则应将其设置为nil。

iOS 5.0中可以使用ARC,它完全消除了自己对Cocoa进行内存管理的需要,但仍需要为CoreFoundation和其他基于C的API创建/保留/释放。


在 ARC 下,您需要为所有 C API 管理内存,因此包括 CoreFoundation、CoreGraphics、GCD、ABAddressBook 等等。这使得管理更加容易,但并不意味着它会消失。 - Abizern

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接