天天看点

【黑马程序员】-oc中的内存管理

------- <ahref="http://www.itheima.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="blank">android培训</a>、<ahref="http://www.itheima.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="blank">java培训</a>、期待与您交流! ----------

1. 什么是内存管理?

       由于我们程序运行的任何设备的内存都是有限的,所以合理的安排内存,处理好内存的问题,是一个非常重要的技术。当一个对象已经不再使用,例如植物大战僵尸中的子弹,当它飞出屏幕时,我们就要及时的让它释放占用的内存。这样就为我们提高了内存的利用效率。如果一个APP不能及时的释放废弃的空间时,当它占用较大的内存是,系统就会发出警告,这是就要及时的处理掉“垃圾”。

       数据或者对象一般会存放到堆或者栈中。栈中存放内容一般都会自动释放,而栈中的内容就需要我们去手动释放。

       在学习这部分的内容时,编程中要关闭Xcode中的ARC机制。在Xcode6.2版本中,创建项目是并不会让你去选择是否带ARC,而要在创建好项目之后进行取消。具体方法是:首先创建好项目,然后点项目名,在Building Setting中可以找到Apple LLVM 6.0 Language-Object c,在此框架下你可以看到Object c Automatic Reference Counting,选择NO 就可以了。

2. 引用计数器

     每个oc对象都有一个引用计数器。引用计数器用来判断此对象在什么时候释放内存。当某个oc对象的引用计数器为0时,那么oc对象占用的内存将会被释放。每个引用计数器都占用4个字节的存储空间。

3. 内存管理常用的关键字

     (1)alloc:用来创建一个对象空间。一般它都会配合init的使用,比如:[[NSObject alloc] init],创建一个类型为NSObject的对象,并对其进行初始化。

     (2)release:给对象发送一个release消息,对象的引用计数器减1。一般配合retain的使用,一个retain就有一个release。

     (3)retain:给对象发送一个retain消息,对象的引用计数器加1。

     (4)retainCount:获取对象当前的引用计数器数值,返回值类型为长整型。

     (5)autorelease:会将调用的对象放到一个自动释放池中。当释放池结束时,对象会自动调用一次release,引用计数器减1。在调用此命令是,对象引用计数器并不会立即减1。有返回值,返回值类型为对象本身。而且autorelease必须放到自动释放池(释放池是以栈的形式存放,先进后出)中使用。例如:

//自动释放池
    @autoreleasepool {
        User *suer =[[[User alloc] init] autorelease];
    } //自动释放池结束。
           

它的缺点是不能精确的释放,因此使用时占用内存较大的对象不要使用autorelease。

     (6)dealloc:相当于对象的“遗言”。在对象被销毁时,对象会自动调用dealloc。而且在调用dealloc时,一定会有调用[super dealloc],而且一定要放到最后调用。

4. 几个要注意的概念

    (1)空指针:没有指向任何对象的指针,也就是说指针指向的内容为nil、null或者0。

    (2)僵尸对象:所占用内存已经被回收的对象,而且僵尸对象不能再使用。

    (3)野指针:指向僵尸对象的指针。

      利用下边的程序来看一下这些概念:

#import <Foundation/Foundation.h>

@interface Person : NSObject
@end

@implementation Person

- (void)dealloc
{
    NSLog(@"Person对象销毁");
    
    [super dealloc];
}
@end

int main()
{
    //定义了一个空指针。
    int *p = nil;
    
    //定义一个Person对象,引用计数器为1。
    Person *p1 = [[Person alloc] init];
    
    //进行一次release,对象引用计数器变为0。此时,指针p1已经是野指针,因为对象已经销毁。
    [p1 release];
    
    //将野指针指向空,变成空指针。
    p1 = nil;
    //对空指针进行release是不会报错的。
    [p1 release];
    
    [p1 release];
    return 0;
}
           

     在出现野指针错误时,一般会报错为:EXC-BAD-ACCESS。因此,需要牢记,下次遇见这样的报错就知道什么原因了。

5. 内存管理的一些总结

(1)想要使用一个对象,就必须让对象的引用计数器加1(也就是引用retain操作)。

(2)有加就有减。如果用alloc、new或者copy创建一个对象,必须调用release或者autorelease对对象进行一次引用计数器减1。

(3)不想使用对象,就应该让对象的计数器-1,也就是让对象进行一次release。

(4)谁retain,谁release;谁alloc,谁release。

6. set方法内存管理

        其实,在调用set方法时,如果有传递参数,而且传递参数为对象,就不得不考虑对象的引用计数器的加减。对于,基本数据类型参数,set设置方法为:

//基本数据类型,set方法直接赋值
- (void)setAge
{
    _age = age;
}
           

但是对于对象,就要进行进一步的考虑:

//传递的参数为对象类型
- (void)setDog:(Dog *)dog
{
    //判断时候为已经拥有的对象。防止出现重复指向时,使对象变为僵尸对象。
    if (self.dog != dog)
    {
        //要放弃的对象进行-1
        [_dog release];
        //新对象+1,然后赋值给成员变量
        _dog = [dog retain];
    }
}
           

7. @property参数写法

@property参数的类型主要分为4类,在进行@property参数书写时,不同类型的参数可以同时存在,但相同类型的参数只能存在一个。

(1)内存管理相关参数:

retain:表示进行对象的set方法,即set方法中的第二中方法;

assign:一般对于基本数据类型,进行直接赋值操作。当然,在类的相互引用时,对已对象类型的参数,也要用assign。默认情况为此类型。

copy:对旧对象进行一次release,对新对象进行copy。

(2)是否生成set方法:

readonly:只生成set方法,不生成声明;

readwrite:可读可写,默认情况下为此方法。

(3)多线程管理

nonatomic:可以提高性能,以后写程序时,必须加上这个。否则就是atomic。

atomic:性能低,而且默认情况下为此形式。

(4)set方法和get方法的名称

getter = 方法名, setter = **:,不可漏了”:“。

如下是一个简单的例子:

@property (nonatomic, retain) NSString *text;

@property (nonatomic, retain) NSString *peitu;

@property (nonatomic, assign) time_t time;

@property (nonatomic, retain) User *user;
           

8. 循环引用

         在循环引用时,不可能利用#import进行头文件的复制。因此,为了解决此问题,引入了@class。

         @class   ***;在.h文件中仅仅是告诉编译器,***是一个类。并没有对其进行复制,因此在.m文件中还要对其进行#import操作。利用@class可以提高程序性能,同时解决了循环引用的问题。

另外就是循环return的问题,在类与类之间的循环return会使对象不能够释放。解决方法是一端用retain,而另一端用assign。

9.ARC机制

         ARC是编译器特性,不是垃圾回收。在编译时,自动检测哪里需要插入release。

         ARC判断标准:只要没有强指针指向对象,那么对象就会释放。

          指针分类:

       (1)强指针:默认情况下所有的指针都是强指针,声明是利用__strong来声明。

       (2)弱指针:不能决定对象是否释放。定义时用__weak声明。

继续阅读