您的位置:首页 > 移动开发 > Objective-C

【Objective-C】内存管理

2015-08-13 00:21 555 查看
前言:内存管理是ios开发中很重要的一个环节,用于管理程序占用的内存空间。每个程序都会使用内存,所以内存管理显得比较重要。java、.net开发中是比较幸运的,由于其运行时候内置了垃圾回收器,当对象无法从程序访问时,垃圾收集器会将这些垃圾对象占用的内存回收。但是Cocoa Touch并没有提供垃圾收集机制(与C语言兼容),因此自己需要来管理内存(不过在 IOS 5 SDK 中添加了自动引用计数ARC(Automatic
Reference Counting 自动释放机制)的特性它能够自动管理对象的释放,开发人员就无需显示释放对象的内存)。

一、内存管理

1. 范围:任何继承了NSObject的对象,对基本数据类型无效。

2. 了解内存管理:

2.1 每个Cocoa对象就是一块内存空间。当程序实例化类的对象时,alloc静态方法会根据对象类中定义的成员变量的类型和个数,计算出对象需要占用多大的内存空间。内存申请成功后,我们可以通过指针来得到对象占用内存空间的地址。内存申请成功后,我们可以通过指针来得到对象占用内存空间的首地址,并通过该指针来对该对象进行各种操作。

2.2 对于对象来说,倘若程序中没有任何指针指向某个对象,那么这个对象将永远无法被程序访问。由于对象还占用一块内存,所以这块内存也就是永远无法归还操作系统,很容易造成“指针泄露”。

注意:在ios开发中要是使用内部类的静态方法创建的一些对象是不需要我们进行手动释放的,是系统内部自动释放的。



2.3 规则管理:(例子:玩气球前先将手中的线栓在气球上,玩累了可以剪段绳子。)(发送消息其实就是调用方法

1、每个对象内部都保存着一个与之相关联的整数,成为引用计数器;(相当于绳子)

2、当使用alloc、new或者copy创建对象时候,对象的引用计数器被设置成1;(对象在实例化后)

3、给对象发送一条retain消息,可以使引用计数器值+1;(retain方法用于保留对象,也就是将对象的保留计数器加1,有新的代码需要使用(持有)对象时,新代码需要调用retain方法。该方法返回值为id,也就是对象的地址,这样就可以嵌套执行对象上的其它方法)

4、给对象发送一条release消息,可以使引用计数器值-1;(与retain方法是相对的)

5、当一个对象引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收;oc也会自动向对象发送一条dealloc消息。一般会重写dealloc方法,在这里释放相关资源。一定不要直接调用dealloc方法;(dealloc是一个回调方法,不需要我们手动调用)

6、可以给对象发送retainCount消息获得当前的引用计数器值;

如:

#import <Foundation/Foundation.h>
#import "Student.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Student *stu = [[Student alloc] init]; //计数器1
        
        //z代表无符号,int其实就是包含了有符号与无符号
        NSLog(@"count:%zi", [stu retainCount]);
        
        [stu retain];  //2
        NSLog(@"count:%zi", [stu retainCount]);
        
        [stu release];  //1
        NSLog(@"count:%zi", [stu retainCount]);
        
        [stu release]; //0  立马发送dealloc消息
        //这个时候已经被回收了,所以下面的打印没有什么意义,就算能打印也未必是0;因为这个时候访问的已经不是自己的内存了
        //NSLog(@"count:%zi", [stu retainCount]);
        
    }
    
    return 0;
}
#import <Foundation/Foundation.h>
#import "Student.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Student *stu = [[Student alloc] init];
        [stu release];
        
        NSLog(@"Hello, World!");
    }
    
    return 0;
}



3. 保留计数

保留计数是一个无符整数,用以表示对象当前被保留(持有)的次数。保留计数能够有效识别出垃圾对象。这是用于管理内存而使用的内部技术,作为我们不应该过分关注保留计数,我们更应该关注“严格遵从内存管理的原则”。

4. 内存管理的原则:

1. 释放由alloc方法创建的对象,copy和mutablecopy方法复制的对象,以及手动调用retain的对象。 alloc方法会创建一个新的对象,并将对象的保留计数置为1。copy和mutablecopy方法会创建一个对象的副本,并将其保留计数置为1。手动调用retain会导致对象的计数加1,目的是保证该对象在使用过程中不会被回收。这三种方式都需要我们手动调用release进行释放。(这种情况下才是需要我们自己进行释放)

2. 其实就是谁创建谁释放,自动释放的对象不需要release,因为已经将释放的责任交给自动释放池了。

3. 管好自己就行。如果获得一个其他方法提供的对象,则不需要考虑该对象的内存管理问题。但是,如果你要在一段时间内使用该对象,则需要保留该对象,并在使用后将其释放(防止对象在使用时被释放)

5. 属性的附加特性与内存管理问题 (参考前面类的属性问题中部分知识:属性附加特性

1. assign: (默认值)set方法直接将新值赋给成员变量。

2. retain:set方法先判断新值是否与成员变量当前值指向同一对象,如果不是,则将当前值释放,然后将新值保留后赋给成员变量;如果是,则什么也不做。注意:该附加特性不适用于基本数据类型,因为他们不是COCOA类的对象,不能进行保留,释放操作。

未使用附加特性:

#import <Foundation/Foundation.h>
// #import "Book.h"
@class Book;
@class Card;

@interface Student : NSObject {
    Book *_book;
}

@property Book *book;

@property Card *card;

@end
#import "Student.h"
#import "Card.h"

@implementation Student

- (void)setBook:(Book *)book {
    if (_book != book) {
        [_book release];
        _book = [book retain];
        
    }
}

// card
- (void)setCard:(Card *)card {
    if (_card != card) {
        [_card release];
        _card = [card retain];
    }
}
使用之后只需要在头文件中进行:
#import <Foundation/Foundation.h>

@class Book;
@class Card;

@interface Student : NSObject {
    Book *_book;
}

@property (retain) Book *book;

@property (retain) Card *card;

@end
#import "Student.h"
#import "Card.h"

@implementation Student

- (void)dealloc {
    // self.book = nil;
    // [self setBook:nil];  释放完之后还把成员变量变成nil
    
    NSLog(@"Student被销毁了");
    [_book release];
    
    [_card release];
    
    [super dealloc];
    
    // 可以通过main.h文件中进行返回计数器来进行验证
}

@end
#import <Foundation/Foundation.h>
#import "Book.h"
#import "Student.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        Student *stu = [[Student alloc] init];
        
        
        Book *book = [[Book alloc] init];
        stu.book = book;            // 调用自动生成的set方法
        NSLog(@"count = %zi", [book retainCount]); //2
        
        [book release];
        [stu release];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
2015-08-12 01:12:12.258 @class关键字[2527:905773] count = 2

2015-08-12 01:12:12.259 @class关键字[2527:905773] Student被销毁了

2015-08-12 01:12:12.259 @class关键字[2527:905773] book被释放了

2015-08-12 01:12:12.259 @class关键字[2527:905773] Hello, World!

3. ocpy: set方法先判断新值是否与成员变量当前值指向同一对象,如果不是,则将当前值释放,然后将新值保留后赋给成员变量;如果是,则什么也不做。新值需要知道如何复制自己,即实现NSCopying协议的类的对象。注意:该附加特性不适用于基本数据类型。

#import <Foundation/Foundation.h>

@class Book;
@class Card;

@interface Student : NSObject {
    Book *_book;
}

@property (retain) Book *book;

@property (retain) Card *card;

//@property (retain) int age;      // 这是错误的

@end
6. 自动释放池 autorelease pool
#import <Foundation/Foundation.h>
#import "Book.h"
#import "Student.h"

int main(int argc, const char * argv[]) {   
    // @autoreleasepool代表创建一个自动释放池
    @autoreleasepool {
        // 下面的东西都是池子里面的,已经将池子放到栈里面了
        Student *stu = [[Student alloc] init];
        [stu autorelease];
        
    }  //  这个括号结束就表示池子里面的对象调用了release方法(不能说被销毁了,由于计数器不一定为0)
    
    NSLog(@"下面是池子2");
    
    @autoreleasepool {
        Book *book = [[Book alloc] init];
        [book autorelease];
    }
    
    return 0;
}


输出结果是:


1. oc对象只要发送一条autorelease消息,就会把这个对象添加到最近的自动释放池中(栈的释放池 --- 先进后出);

2. autorelease并不会改变对象的计数器,只是将对象扔进去池子中;

3. 在ARC下,不能使用[[NSAutoreleasePool alloc] init];而应当使用@autoreleasepool

4. 不要把大量循环操作放到同一个NSAutoreleasePool里面,这样会造成内存峰值的上升

5. 尽量避免对大量的内存使用该方法,对于这种延迟释放机制,还是尽量少用

6. 程序中的每个线程至少需要一个自动释放池对象,因此在创建线程之后需要立即创建自动释放池对象,否则会造成内存泄露

7. 使用自动释放池会造成对象的释放延迟。为了减少延迟时间,我们可以创建新的自动释放对象,这会将新建的吃对象放入线程对应的自动释放池中。autorelease方法总是将对象放入栈顶的自动释放池对象中。

- (ClassA *)createObj {
    ClassA *obj = [[ClassA alloc] init];
    // 到底这里要不要进行释放obj呢
    return obj;
    
}

// 使用自动释放进行解决,由于延迟释放了
- (ClassA *)createObj {
    return [[[ClassA alloc] init] autorelease];
    
}


**来源于ios程序开发方法与实践
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: