如何在iPhone应用中避免内存泄露


本篇文章将介绍几个在iPhone APP中避免内存泄露的小技巧

关于所有权

所有权是iPhone内存管理的核心思想,对象的所有者负责在使用完对象后进行释放。一个对象可以有多个所有者,当它没有所有者时将被设置为取消分配(deallocation)。

创建对象时,所有权通过alloc、new、或者copy的方式建立,之后通过调用retain或者通过Cocoa函数来分配和复制对象的所有权。内存释放有两种方式,一种方法是明确地请求释放对象的所有权,另一种方法则是使用自动释放池(auto-release pool)。

所有权的背后是一个和引用有关的运算系统,iPhone SDK的大多数对象使用这个系统,彼此之间建立着很强的引用和参照。

当你创建一个对象时,引用值为1,调用一次retain则对象的引用值加1,调用一次release则对象的引用值减1,当引用值为0时,对象的所有权分配将被取消。使用自动释放池意味着对象的所有权将在一段延后的时间内被自动取消。

对象之间也可以建立弱的引用参照,此时意味着,引用值不会被保留,对象的分配需要手动取消。

什么时候使用retain?

什么时候你想阻止对象在使用前就被释放?

每当使用copy、alloc、retain、或者Cocoa函数来创建和复制所有权,你都需要相应的release或者auto-release。

开发者应该从所有权的角度来考虑对象,而不必担心引用值。只要你有相应的retain和release方法,就能够对引用值进行+1和-1操作。

注意:你或许想使用[object retainCount],但它可能因为SDK的底层代码而发生返回值出错的情况。在内存管理时不推荐这种方式。

自动释放

将对象设置为自动释放意味着不需要明确地请求释放,因为当自动释放池清空时它们将被自动释放。iPhone在主线程上运行自动释放池,能够在事件循环结束后释放对象。当你创建你自己的线程时,你需要创建自己的自动释放池。

iPhone上有便利的构造函数,用这种方法创建的对象会设置为自动释放。

例子;

NSString* str0 = @"hello";
NSString* str1 = [NSString stringWithString:@"world"];
NSString* str2 = str1;

一个已分配的对象可以用如下的方法设置为自动释放:

NSString* str = [[NSString alloc] initWithString:@"the flash?"];
[str autorelease];

或者用下面的方法:

NSString* str = [[[NSString alloc] initWithString:@"batman!"] autorelease];

当指针出界,或者当自动释放池清空时,自动释放对象上的所有权将被取消。

在一个事件循环结束时,自动释放池内的构件通常会被清空。但是当你的循环每次迭代都分配大量内存时,你或许希望这不要发生。这种情况下,你可以在循环内创建自动释放池。自动释放池可以嵌套,所以内部池清空时,其中分配的对象将被释放。在下面的例子中,每次迭代后将释放对象。

for (int i = 0; i < 10; ++i)
{
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        NSString* str = [NSString stringWithString:@"hello world"];
        [self ProcessMessage: str];
        [pool drain];
}

注意:在编写的时候iPhone不支持垃圾回收,所以drain和release的功能相同。当你想为程序设置OSX的端口时通常会使用drain,除非后来在iPhone中添加了垃圾回收机制。Drain能够击发垃圾回收器释放内存。

返回一个对象的指针

开发者在遵循所有权规则时需要清楚哪些函数拥有对象的所有权。下面是返回一个对象的指针并释放的例子。

错误的方法:

- (NSMutableString*) GetOutput
{
        NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];
        return output;
}
- (void) Test
{
        NSMutableString* obj = [self GetOutput];
        NSLog(@"count: %d", [obj retainCount]);
        [obj release];
}

在这个例子中,output 的所有者是 GetOutput,让 Test 释放 obj 违反了Coccoa内存管理指南中的规则,尽管它不会泄露内存但是这样做不好,因为Test 不应该释放并非它所拥有的对象。

正确的方法:

- (NSMutableString*) GetOutput
{
        NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];
        return [output autorelease];
}
- (void) Test
{
        NSMutableString* obj = [self GetOutput];
        NSLog(@"count: %d", [obj retainCount]);
}

在第二个例子中,output 被设置为当 GetOutput 返回时自动释放。output的引用值减少,GetObject 释放 output 的所有权。Test 函数现在可以自由的 retain 和 release 对象,请确保它不会泄露内存。

例子中 obj 被设置为自动释放,所以 Test 函数没有它的所有权,但是如果它需要在其他地方存储对象会怎样?

此时对象需要有一个新的所有者来保留。

Setters

setter函数必须保留它所存储的对象,也就是声明所有权。如果我们想要创建一个 setter 函数,我们需要在分配一个新的指向成员变量的指针之前做两件事情。

在函数里:

- (void) setName:(NSString*)newName

首先我们要减少成员变量的引用值:

[name release];

这将允许当引用值为0时 name 对象被释放,但是它也允许对象的其他所有者继续使用对象。

然后我们增加新的 NSString 对象的引用值:

[newName retain];

所以当 setName 结束时, newName 不会被取消分配。 newName 现在指向的对象和 name 指向的对象不同,两者有不同的引用值。

现在我们设置 name 指向 newName 对象:

name = newName;

但是如果 name 和 newName 是同一个对象时怎么办?我们不能在它被释放后保留它,并再次释放。

在释放存储的对象前保留新的对象:

[newName retain];
[name release];
name = newName;

现在两个对象是相同的,先增加它的引用值,然后再减少,从而使得赋值前引用值不变。

另一种做法是使用 objective-c:

声明如下:

@property(nonatomic, retain) NSString *name;

1. nonatomic 表示没有对同一时间获取数据的多个线程进行组块儿。Atomic 为一个单一的线程锁定数据,但因为 atomic 的方式比较缓慢,所以不是必须的情况一般不使用。

2. retain 表示我们想要保留 newName 对象。

我们可以使用 copy 代替 retain:

@property(nonatomic, copy) NSString *name;

这和下面的函数一样:

- (void) setName:(NSString*)newName
{
        NSString* copiedName = [newName copy];
        [name release];
        name = copiedName;
        [name retain];
        [copiedName release];
}

newName 在这里被复制到 copiedName,现在 copiedName 拥有串的一个副本。name 被释放,而 copiedName 被赋给 name。之后 name 保留这个串,从而使得 copiedName 和 name 同时拥有它。最后 copiedName 释放这个对象,name 成为这个串的唯一所有者。

如果我们有如下的函数,像这样的 setters 将被输入用来保留成员对象:

- (void) Test
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   // do something...
   name = [self GetOutput];
   // do something else...
   NSLog(@"Client Name before drain: %@", name);
   [pool drain];
   NSLog(@"Client Name after drain: %@", name);
}

name 在调用至 drain 后是未定义的,因为当池被释放时,name 也将被释放。

如果我们用如下的部分替代赋值:

[self setName:[self GetOutput]];

然后 name 将被这个类所有,在使用时保留直到调用 release

那么我们何时释放对象?

由于 name 是成员变量,释放它的最安全的办法是对它所属的类使用 dealloc 函数。

- (void)dealloc
{
   [name release];
   [super dealloc];
}

注意:虽然并不总是调用 dealloc,依靠 dealloc 来释放对象可能是危险,可能会触发一些想不到的事情。在出口处,iPhone OS 可能在调用 dealloc 前清空全部应用程序的内存。

当用 setter 给对象赋值时,请小心下面的语句:

[self setName:[[NSString alloc] init]];

name 的设置是正确的但 alloc 没有相应的释放,下面的方式要好一些:

NSString* s = [[NSString alloc] init];
[self setName:s];
[s release];

或者使用自动释放:

[self setName:[[[NSString alloc] init] autorelease]];

自动释放池

自动释放池释放位于分配和 drain 函数之间的对象。

我们在下面的函数中设置一个循环,在循环中将 NSNumber 的一个副本赋给 magicNumber,另外将 magicNumber 设置为自动释放。在这个例子中,我们希望在每次迭代时清空自动释放池(这样可以在赋值的数量很大时节省循环的内存)

- (void) Test
{
   NSString* clientName = nil;
   NSNumber* magicNumber = nil;
   for (int i = 0; i < 10; ++i)
   {
           NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
           magicNumber = [[self GetMagicNumber] copy];
           [magicNumber autorelease];
           if (i == [magicNumber intValue])
           {
                   clientName = [self GetOutput];
           }
           [pool drain];
   }
   if (clientName != nil)
   {
           NSLog(@"Client Name: %@", clientName);
   }
}

这里存在的问题是 clientName 在本地的自动释放池中被赋值和释放,所以当外部的池清空时,clientName 已经被释放了,任何对 clientName 的进一步使用都是没有定义的。

在这个例子中,我们在赋值后保留 clientName,直到结束时再释放它:

- (void) Test
{
   NSString* clientName = nil;
   NSNumber* magicNumber = nil;
   for (int i = 0; i < 10; ++i)
   {
           NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
           magicNumber = [[self GetMagicNumber] copy];
           [magicNumber autorelease];
           if (i == [magicNumber intValue])
           {
                   clientName = [self GetOutput];
                   [clientName retain];
           }
           [pool drain];
   }
   if (clientName != nil)
   {
           NSLog(@"Client Name: %@", clientName);
           [clientName release];
   }
}

我们在调用 retain 函数和 release 函数的期间获得 clientName 的所有权。通过添加一对 retain 和 release 的调用,我们就确保 clientName 在明确调用释放前不会被自动释放。

集合

当一个对象被添加进集合时,它就被集合所拥有。

在这个例子中我们分配一个串,它现在有了所有者;

NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];

然后我们将它添加进数组,现在它有两个所有者:

[array addObject: str];

我们可以安全的释放这个串,使其仅被数组所有:

[str release];

当一个集合被释放时,其中的所有对象都将被释放。

NSMutableArray* array = [[NSMutableArray alloc] init];
NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];
[array addObject: str];
[array release];

在上面的例子中,我们分配了一个数组和一个串,然后将串添加到数组中并释放数组。这使得串仅拥有一个所有者,并且在我们调用 [str release] 前它不会被释放。

用线程传递指针

在这个函数中,我们从串的 input 传递到函数 DoSomething,然后释放 input

- (void) Test
{
   NSMutableString* input = [[NSMutableString alloc] initWithString:@"batman!"];
   [NSThread detachNewThreadSelector:@selector(DoSomething:) toTarget:self withObject:input];
   [input release];
}

detatchNewThreadSelector 增加 input 对象的引用值并在线程结束时释放它。这就是为什么我们能够在线程刚开始的时候就释放 input,而无论函数 DoSomething 何时开始或结束。

- (void) DoSomething:(NSString*)str
{
   [self performSelectorOnMainThread:@selector(FinishSomething:) withObject:str waitUntilDone:false];
}

performSeclectorOnMainThread 也会保留传递的对象,直到 selector 结束。

自动释放池是特殊的线程,所以如果我们在一个新的线程上创建自动释放的对象,我们需要创建一个自动释放池来释放它们。

[NSThread detachNewThreadSelector:@selector(Process) toTarget:self withObject:nil];

这里在另一个线程上调用函数 Process

- (void) Process
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   NSMutableString* output = [[[NSMutableString alloc] initWithString:@"batman!"] autorelease];
   NSLog(@"output: %@", output);
   [self performSelectorOnMainThread:@selector(FinishProcess) withObject:nil waitUntilDone:false];
   [pool drain];
}

对象 output 被分配并且在自动释放池中设置了自动释放,它将在函数结束前被释放。

- (void) FinishProcess
{
   NSMutableString* output = [[[NSMutableString alloc] initWithString:@"superman?"] autorelease];
   NSLog(@"output: %@", output);
}

系统会为主线程自动创建一个自动释放池,所以在 FinishProcess 中,我们不需要为主线程上运行的函数创建自动释放池。

总结

为了在你的iPhone中避免内存泄露,你必须要清楚每个被分配对象的所有者是谁,要明白什么时候释放所有权,并且还要始终按对设置 retain 和 release,这三点非常重要。如果你遵循所有权的规则,你的应用将更加稳定并且因为 bug 的减少而节省大量时间。

许可

这篇文章及其中的源代码和文件得到  The Code Project Open License(CPOL)的许可

关于作者

r_adem

原文链接:http://www.codeproject.com/KB/iPhone/avoidiphoneleaks.aspx

原作者:r_adem

原文:

How to avoid memory leaks in iPhone applications

Some tips to avoid leaking memory on your iPhone apps.

Ownership

Ownership is the overall idea behind how memory management should work on the iPhone. When an object has an owner, they are responsible for releasing the object when they have finished using it. An object can have more than one owner and when it has no owners it is set for de-allocation.

Ownership is made when creating an object with alloc, new or copy, when calling retain on an object or when calling Cocoa functions that have Create or Copy in their name. There are two ways memory is released either explicitly calling release on an object or using the auto-release pool.

Behind ownership is a system called reference counting. Most objects in the iPhone SDK are strongly referenced, this means they use reference counting.

When you create an object it will have a reference count of 1 and calling retain on an object will increment the reference count by 1. Calling release will decrement the reference count by 1, when the reference count reaches zero the object is de-allocated. Calling auto-release instead of release means the object will be de-allocated at a later time automatically.

Objects can also be weakly referenced meaning that a reference count isn’t kept and the object will need to be de-allocated manually.

When should you use retain?

When you want to prevent an object from being de-allocated before you use it.

Every time you use copy, alloc, retain, or a Cocoa function that has Create or Copy in its name you need to have a matching release or autorelease.

The developer should think about objects in terms of ownership and not worry about reference counts. If you have matching retain and release calls it is obvious that you will have matching +1 and -1 additions to the reference count.

Note: It may be tempting to use [object retainCount] but the values this returns can be misleading due to the behind the scenes code in the SDK. It is not recommended to manage memory this way.

Auto-release

Objects set to auto-release mean that they do not need to be explicitly released because they will be released when an auto-release pool is popped. The iPhone has an auto-release pool that runs on the main thread which usually releases objects at the end of an event loop. When you create your own threads you must create your own auto-release pool.

On the iPhone there are convenience constructors, objects created with convenience constructors are set to auto-release.

examples:

 Collapse
NSString* str0 = @"hello";
NSString* str1 = [NSString stringWithString:@"world"];
NSString* str2 = str1;

An allocated object can be set to auto-release like this:

 Collapse
NSString* str = [[NSString alloc] initWithString:@"the flash?"];
[str autorelease];

Or like this:

 Collapse
NSString* str = [[[NSString alloc] initWithString:@"batman!"] autorelease];

Ownership for auto-released objects is relinquished when the pointer goes out of scope or when an auto-release pool is popped.

The built in auto-release pool is usually popped at the end of an event loop but this may not be desired when you have a loop that is allocating a lot of memory each iteration. In that case you can create an auto-release pool in the loop. Auto-release pools can be nested so the objects allocated in the inner pool will be released when that pool is popped. In the example below objects will be released at the end of each iteration.

 Collapse
for (int i = 0; i < 10; ++i)
{
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        NSString* str = [NSString stringWithString:@"hello world"];
        [self ProcessMessage: str];
        [pool drain];
}

Note: At the time of writing the iPhone doesn’t support garbage collecting so drain will work the same as release. Drain is often used in case you wanted to port the program to OSX or if garbage collecting is added the iPhone later. Drain provides a hit to the garbage collector that memory is being released.

Returning a pointer to an object

When following the rules of ownership the developer needs to be aware of what functions have ownership of an object. This is an example of returning a pointer to an object and releasing it.

Wrong way:
 Collapse
- (NSMutableString*) GetOutput
{
        NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];
        return output;
}
- (void) Test
{
        NSMutableString* obj = [self GetOutput];
        NSLog(@"count: %d", [obj retainCount]);
        [obj release];
}

In this example output is owned by GetOutput. HavingTest release obj violates the rules in the Coccoa Memory Management Guide, this will not leak memory but it is not good practice because Test shouldn’t release an object it doesn’t own.

If GetOutput is called the caller shouldn’t need to know if the object returned from GetOutput is retained or not. It is therefore free to retain and release the returned object without disrupting any other code in the application.

Correct way:
 Collapse
- (NSMutableString*) GetOutput
{
        NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];
        return [output autorelease];
}
- (void) Test
{
        NSMutableString* obj = [self GetOutput];
        NSLog(@"count: %d", [obj retainCount]);
}

In the second example output is set to auto-release whenGetOutput returns. The reference count for output is decremented and GetObject is relinquishing is ownership of output. The Test function is now free to retain and release the object and be sure it wont leak when its done.

In the example obj is set to auto-release, so the Testfunction will not have ownership of it when the function ends but what if it wanted to store the object somewhere else?

The object will then need to be retained by a new owner.

Setters

A setter function must retain the object it is storing which means claiming ownership. If we want to create a setter function we need to do two things before assigning a new pointer to our member variable.

In the function:

 Collapse
- (void) setName:(NSString*)newName

First we would decrement the reference count of our member variable:

 Collapse
[name release];

This will allow the name object to be de-allocated if the reference count is zero but it will allow any other owners of the object to continue to use the object.

Then we will increment the reference count of the new NSString object:

 Collapse
[newName retain];

So the newName will not be de-allocated when thesetName selector finishes. The object newName now points to is different to the one name points to with a different reference count.

Now we can set name to point to the newName object:

 Collapse
name = newName;

But what if name and newName are the same object? We can’t release it with the possibility of it being de-allocated and then retain it!

Simply retain the incoming object before releasing the stored object:

 Collapse
[newName retain];
[name release];
name = newName;

Now of the objects are the same it will up the reference count then subtract from it, causing it to stay the same before assigning it.

Another way to do this is use the objective-c properties.

A property for a object is declared like so:

 Collapse
@property(nonatomic, retain) NSString *name;
  1. nonatomic means there is no blocking for multiple threads accessing the data at the same time. Atomic will lock the data for a single thread but is slower so it is not used unless necessary.
  2. retain means that we want to retain the newName object.

Instead of retain we could use copy:

 Collapse
@property(nonatomic, copy) NSString *name;

This would be the same as a function like this:

 Collapse
- (void) setName:(NSString*)newName
{
        NSString* copiedName = [newName copy];
        [name release];
        name = copiedName;
        [name retain];
        [copiedName release];
}

Here newName is copied to copiedName, nowcopiedName has a copy of the string and owns it. nameis released and copiedName is assigned to namenamethen retains the string, so both copiedName and nameown it. Finally copiedName releases the object and nameis the only owner of the copied string.

Setters like this are import to retain the member object, if we had a function like this:

 Collapse
- (void) Test
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   // do something...
   name = [self GetOutput];
   // do something else...
   NSLog(@"Client Name before drain: %@", name);
   [pool drain];
   NSLog(@"Client Name after drain: %@", name);
}

name would be undefined after the call to drain because name will be released when the pool is drained.

If we replaced the assignment with:

 Collapse
[self setName:[self GetOutput]];

Then name will owned by the class and be retained for use until release is called.

But when do we release the object?

Since name is a member variable the safest place to release it is in the dealloc function of the class it belongs to.

 Collapse
- (void)dealloc
{
   [name release];
   [super dealloc];
}

Note: Although dealloc is not always called so relying on objects to be released in dealloc to trigger something could be dangerous. On exit the iPhone OS may clear all the application’s memory before dealloc is called.

When assigning an object with a setter be careful of lines like this:

 Collapse
[self setName:[[NSString alloc] init]];

name will be set fine but the alloc does not have a matching release. A better way would be something like this:

 Collapse
NSString* s = [[NSString alloc] init];
[self setName:s];
[s release];

Or with auto-release:

 Collapse
[self setName:[[[NSString alloc] init] autorelease]];

Auto-Release pools

Auto-release pools will release objects that are assigned in between their allocation and drain functions.

In the function below we have a function that has a loop. In this loop we are assigning a copy of NSNumber tomagicNumber we are also setting magicNumber to be auto-released. In this example we want to drain the pool on each iteration (This can save memory for loops with a large amount of assignments).

 Collapse
- (void) Test
{
   NSString* clientName = nil;
   NSNumber* magicNumber = nil;
   for (int i = 0; i < 10; ++i)
   {
           NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
           magicNumber = [[self GetMagicNumber] copy];
           [magicNumber autorelease];
           if (i == [magicNumber intValue])
           {
                   clientName = [self GetOutput];
           }
           [pool drain];
   }
   if (clientName != nil)
   {
           NSLog(@"Client Name: %@", clientName);
   }
}

The problem is that clientName is assigned and released in the local auto-release pool. So when the loop finishes theclientName has already been released and any further uses of clientName will be undefined.

In this case we can retain clientName after we assign it and release it when we are done:

 Collapse
- (void) Test
{
   NSString* clientName = nil;
   NSNumber* magicNumber = nil;
   for (int i = 0; i < 10; ++i)
   {
           NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
           magicNumber = [[self GetMagicNumber] copy];
           [magicNumber autorelease];
           if (i == [magicNumber intValue])
           {
                   clientName = [self GetOutput];
                   [clientName retain];
           }
           [pool drain];
   }
   if (clientName != nil)
   {
           NSLog(@"Client Name: %@", clientName);
           [clientName release];
   }
}

We have taken ownership of clientName from the period between the retain and release calls. By adding a pair of retain and release calls we are saying that clientNamewill not be released until release is explicitly called.

Collections

When an object is added to a collection it will be owned by the collection.

In this example we allocate a string, it now has one owner:

 Collapse
NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];

We then add it to the array, now it has two owners:

 Collapse
[array addObject: str];

We can safely release the string, leaving it to be owned only by the array:

 Collapse
[str release];

When a collection is released it will release all its objects as well.

 Collapse
NSMutableArray* array = [[NSMutableArray alloc] init];
NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];
[array addObject: str];
[array release];

In the above example we allocate an array, allocate a string, add the string to the array and release the array. This leaves the string with one owner and it will not be de-allocated until we call [str release].

Passing in pointers with threads

In this function we are passing in the string input to the function DoSomething, then releasing input.

 Collapse
- (void) Test
{
   NSMutableString* input = [[NSMutableString alloc] initWithString:@"batman!"];
   [NSThread detachNewThreadSelector:@selector(DoSomething:) toTarget:self withObject:input];
   [input release];
}

detatchNewThreadSelector ups the reference count for the input object and releases it when the thread finishes. This is why we can release input right after starting the thread no matter when the functionDoSomething starts or finishes.

 Collapse
- (void) DoSomething:(NSString*)str
{
   [self performSelectorOnMainThread:@selector(FinishSomething:) withObject:str waitUntilDone:false];
}

performSeclectorOnMainThread will also retain the object passed in until the selector has finished.

Auto-release pools are thread specific, so if we are creating auto-released objects on a new thread we need to create an auto-release pool to release them.

 Collapse
[NSThread detachNewThreadSelector:@selector(Process) toTarget:self withObject:nil];

This calls the function Process on a different thread.

 Collapse
- (void) Process
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   NSMutableString* output = [[[NSMutableString alloc] initWithString:@"batman!"] autorelease];
   NSLog(@"output: %@", output);
   [self performSelectorOnMainThread:@selector(FinishProcess) withObject:nil waitUntilDone:false];
   [pool drain];
}

The object output is allocated and set to auto-release inside the auto-release pool and will be released before the end of the function.

 Collapse
- (void) FinishProcess
{
   NSMutableString* output = [[[NSMutableString alloc] initWithString:@"superman?"] autorelease];
   NSLog(@"output: %@", output);
}

There is an auto-release pool created for the main thread automatically so in FinishProcesswe do not need to create an auto-release pool as this function runs on the main thread.

Summary

To avoid memory leaks in your iPhone apps it is important to keep in mind who owns each object that is allocated, when to relinquish that ownership and keeping retain and release calls in pairs. If you follow the rules of ownership your apps will be more stable and you will cut down a lot of bug fixing time.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

r_adem

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值