OC学习篇之---内存管理介绍和使用 分类: IOS 2014-12-14 16:16 1510人阅读 评论(1) 收藏

在以前的一片文章咱们说了OC中谓词操做:http://blog.csdn.net/jiangwei0910410003/article/details/41923507,从今天开始咱们就来看一下OC中最难的一部份内容:内存管理java

为何说他难呢?由于内存若是须要咱们程序员去管理的话,那个难度确定是很大的,若是是Java,垃圾回收器会把这份工做给作了,咱们不须要关心,可是就是由于如此,Android运行速度上会慢一下,缘由很简单,Java的垃圾回收器有不少收集算法的,这个在回收的过程当中是很浪费时间的,效率天然就低了,可是若是这份工做给程序员本身去作的话,效率上确定会增长,可是对于程序员来讲任务就比较繁重了,并且还要特别的当心,千万不能形成内存溢出和泄露。程序员

这里咱们主要从四个方面来介绍一下内存管理算法

一、简单的例子来了解引用计数的使用测试

二、set方法来控制引用计数问题spa

三、销毁方法来控制引用计数问题.net

四、初始化方法来控制引用计数问题指针


下面就来简单看一下OC中的内存管理code

这个例子有点复杂,咱们慢慢分析对象

Dog.hblog

//
//  Dog.h
//  24_MemeryManager
//
//  Created by jiangwei on 14-10-12.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Dog : NSObject{
    NSString *_name;
}

- (void) setName:(NSString *)name;

@end

Dog.m

//
//  Dog.m
//  24_MemeryManager
//
//  Created by jiangwei on 14-10-12.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import "Dog.h"

@implementation Dog

- (void) setName:(NSString *)name{
    _name = name;
}

@end

Dog类中定义了name属性,而且给他提供了一个set方法


Person.h

//
//  Person.h
//  24_MemeryManager
//
//  Created by jiangwei on 14-10-12.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>

@class Dog;
@interface Person : NSObject{
    Dog *_dog;
    NSString * _name;
}

- (id)initWithDog:(Dog*)dog;
- (void)setName:(NSString *)name;
- (void)setDog:(Dog *)dog;
- (void)playDog;
- (Dog *)dog;

- (void)dealloc;
@end


Person.m

//
//  Person.m
//  24_MemeryManager
//
//  Created by jiangwei on 14-10-12.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import "Person.h"

@implementation Person

- (id)initWithDog:(Dog*)dog{
    //使用初始化的时候传入dog
    self = [super init];
    if(self != nil){
        //由于初始化方法只会调用一次,因此这里就没有作判断了
        [_dog release];
        _dog = [dog retain];
    }
    return self;
}

- (void)setName:(NSString *)name{
    //这里name也是对象,因此也是须要进行改写
    _name = name;
    
    /*
     //这里的判断是由于setName方法可能会被屡次调用
     if(_name != name){
        [_name release];
        [name copy];//这里使用了copy,而没有使用retain,这个是字符串独有的,其余对象类型都是使用retain的
     }
     */
}

//第一种方式
/*
- (void)setDog:(Dog *)dog{
    //引用计数须要+1
    _dog = [dog retain];
    
    //有时候可能须要替换Dog对象,因此这里还要注意释放Dog的引用
}
 */

//第二种方式
/*
- (void)setDog:(Dog *)dog{
    //使用nil去调用方法是没有错误的
    //可是当一个对象被销毁的时候,指针就变成野指针了,这时候调用方法会出错的
    [_dog release];
    _dog = [dog retain];
}
 */

//第三种方式
- (void)setDog:(Dog *)dog{
    //这里的判断是由于setName方法可能会被屡次调用
    if(_dog != dog){
        [_dog release];
        _dog = [dog retain];
    }
}

- (void)playDog{
    NSLog(@"playDog");
}

- (Dog *)dog{
    return _dog;
}

- (void)dealloc{
    //对象类型的属性都须要在这里进行释放引用
    //对狗进行释放
    [_dog release];
    NSLog(@"dealloc is Executing");
    [super dealloc];
}

@end
Person类中有一个Dog的属性,而后提供了set方法。代码有点复杂,咱们后面会详细说明

下面来看一下测试代码

main.m

//
//  main.m
//  24_MemeryManager
//
//  Created by jiangwei on 14-10-12.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>

#import "Person.h"
#import "Dog.h"

//内存管理
//alloc用来建立对象,建立完成以后,引用计数为1,只调用一次
//retain使引用计数+1,能够多调用几回
//release使引用计数-1,能够多调用几回
//当引用计数为0的时候会调用dealloc
//最新的Xcode版本默认状况下会开启ARC机制的,当开启这个机制以后,咱们就不能手动的显示调用这些方法,编译器会报错
//因此咱们能够将这个默认状态的ARC关闭,可是这个只是为了测试使用

int main(int argc, const char * argv[]) {

    /*
    Person *person = [[Person alloc] init];//引用计数为1
    NSLog(@"引用计数:%ld",[person retainCount]);
    
    //引用计数加1
    [person retain];
    
    [person release];
    
    NSLog(@"引用计数:%ld",[person retainCount]);
    [person release];
     */
    
    
    Dog *dog = [[Dog alloc] init];
    [dog setName:@"小黑"];
    
    Dog *dog1 = [[Dog alloc] init];//引用计数为1
    [dog setName:@"大黄"];
    
    Person *p1 = [[Person alloc] init];
    [p1 setName:@"张三"];
    [p1 setDog:dog];
    [p1 setDog:dog1];//狗的引用替换成了大黄
    
    Person *p2 = [[Person alloc] init];
    [p2 setName:@"李四"];
    [p2 setDog:dog];
    
    //这里引用计数为1,这个和咱们以前说的引用计数管理有矛盾,因此咱们在使用的时候须要手动的retain
    NSLog(@"引用计数为:%ld",[dog retainCount]);
    
    [dog1 release];//由于alloc的时候引用计数就为1了
    
    //这里就有一个问题了,dog1对象已经被销毁了,可是setDog对象仍是用了dog1对象调用方法了,这就会报错了
    //因此又对set方法进行改进了
    [p1 setDog:dog1];
    
    //当人销毁的时候,还须要对狗的引用-1
    //在人的dealloc方法中实现
    
    Person *p3 = [[Person alloc] initWithDog:dog1];
    [dog1 release];//dog1的引用计数:0
    
    [p3 playDog];
    
    [p3 release];
    
    return 0;
}


下面咱们来详细说明一下:

首先若是想演示这个例子的话,须要修改一下设置:



最新的XCode默认是会自动选上ARC(Automatic Reference Counting),若是咱们不把这个手动关闭的话,代码中会报错的。

alloc用来建立对象,建立完成以后,引用计数为1,只调用一次

retain使引用计数+1,能够多调用几回

release使引用计数-1,能够多调用几回

当引用计数为0的时候会调用dealloc

黄金法则:每次调用alloc一次,都须要调用release一次,他们两是成对出现的

最新的Xcode版本默认状况下会开启ARC机制的,当开启这个机制以后,咱们就不能手动的显示调用这些方法,编译器会报错

因此咱们能够将这个默认状态的ARC关闭,可是这个只是为了测试使用


同时咱们会发现main.m文件中没有自动释放池了@autoreleasepool了。


一、简单的例子

Person *person = [[Person alloc] init];//引用计数为1
NSLog(@"引用计数:%ld",[person retainCount]);

//引用计数加1
[person retain];

[person release];

NSLog(@"引用计数:%ld",[person retainCount]);
[person release];
咱们建立了一个Person类,而后能够打印一下的引用计数值:1

当咱们调用retain方法的时候,引用计数就会+1,当咱们调用release方法的时候引用计数会-1

一旦系统发现引用计数为0的时候,就会销毁这个对象,调用dealloc方法


二、set方法来控制引用计数

这个例子简单吧,没什么问题的,如今咱们把问题复杂化

Dog *dog = [[Dog alloc] init];
[dog setName:@"小黑"];

Dog *dog1 = [[Dog alloc] init];//引用计数为1
[dog setName:@"大黄"];

Person *p1 = [[Person alloc] init];
[p1 setName:@"张三"];
[p1 setDog:dog];
[p1 setDog:dog1];//狗的引用替换成了大黄

Person *p2 = [[Person alloc] init];
[p2 setName:@"李四"];
[p2 setDog:dog];

//这里引用计数为1,这个和咱们以前说的引用计数管理有矛盾,因此咱们在使用的时候须要手动的retain
NSLog(@"引用计数为:%ld",[dog retainCount]);

[dog1 release];//由于alloc的时候引用计数就为1了

//这里就有一个问题了,dog1对象已经被销毁了,可是setDog对象仍是用了dog1对象调用方法了,这就会报错了
//因此又对set方法进行改进了
[p1 setDog:dog1];

//当人销毁的时候,还须要对狗的引用-1
//在人的dealloc方法中实现
咱们定义了两条狗,而后将这两条狗经过setDog方法设置到p1对象上,同时将第一条狗设置到p2对象上,这时候咱们打印一下第一条狗的引用计数,发现他的引用计数是1,缘由很简单,这个1是在alloc时候有的,可是如今是有问题的,由于第一条狗被p1和p2引用者,按照正常状况,第一条狗的引用计数为3的。因此这时候咱们就须要修改一下Person类中setDog方法了

- (void)setDog:(Dog *)dog{
    //引用计数须要+1
    _dog = [dog retain];
    
    //有时候可能须要替换Dog对象,因此这里还要注意释放Dog的引用
}
咱们须要手动的增长dog的引用计数,这样就正常了。


如今又有一个问题了,上面p1对象设置了第二条狗

[p1 setDog:dog1];//狗的引用替换成了大黄
按照上面的setDog方法,咱们又发现一个问题,如今p1引用的狗是第二条狗了,不是第一条,那么这时候按照正常状况第一条狗的引用计数-1,由于他已经再也不被p1引用了。因此setDog方法还得修改:

- (void)setDog:(Dog *)dog{
    //使用nil去调用方法是没有错误的
    //可是当一个对象被销毁的时候,指针就变成野指针了,这时候调用方法会出错的
    [_dog release];
    _dog = [dog retain];
}
这时候咱们就在赋值以前,对_dog调用一次release方法,这样就解决了上面的那个问题。


可是如今还有一个问题:

[dog1 release];//由于alloc的时候引用计数就为1了
NSLog(@"dog1:%ld",[dog1 retainCount]);
//这里就有一个问题了,dog1对象已经被销毁了,可是setDog对象仍是用了dog1对象调用方法了,这就会报错了
//因此又对set方法进行改进了
[p1 setDog:dog1];
在执行release代码以后,dog1的引用计数为1,这是继续调用setDog方法:

- (void)setDog:(Dog *)dog{
    //使用nil去调用方法是没有错误的
    //可是当一个对象被销毁的时候,指针就变成野指针了,这时候调用方法会出错的
    [_dog release];
    _dog = [dog retain];
}
这时候_dog是dog1,继续调用release方法,执行完以后,引用继续为0了,对象被释放了,可是这时候dog参数的值仍是dog1,那么在执行retain方法就会报错了,这个缘由很简单,就是咱们两次调用了setDog方法,将dog1设置了两次。中间调用一次release方法。因此解决这样的问题,咱们在修改一下setDog方法:

//第三种方式
- (void)setDog:(Dog *)dog{
    //这里的判断是由于setName方法可能会被屡次调用
    if(_dog != dog){
        [_dog release];
        _dog = [dog retain];
    }
}
我只须要判断一下,以前的属性值和当前须要设置的值是否相同。这种方式就是没有问题了,因此咱们之后在写set方法的时候,这就是模板了。


三、销毁方法dealloc控制引用计数

如今假如p1被销毁了,那么对于dog1来讲引用计数应该-1的,因此须要在Person类中的dealloc方法中修改一下:

- (void)dealloc{
    //对象类型的属性都须要在这里进行释放引用
    //对狗进行释放
    [_dog release];
    NSLog(@"dealloc is Executing");
    [super dealloc];
}


四、初始化方法控制引用计数

如今还有一种状况,若是咱们使用初始化方法来设置Dog属性值:

- (id)initWithDog:(Dog*)dog{
    //使用初始化的时候传入dog
    self = [super init];
    if(self != nil){
        //由于初始化方法只会调用一次,因此这里就没有作判断了
        [_dog release];
        _dog = [dog retain];
    }
    return self;
}
咱们这里的处理和setDog方法的处理方式同样,可是这里不须要作一次判断了,由于初始化方法只会调用一次。因此不会出现setDog的第三种状况。


总结

这一篇主要介绍了OC中内存管理的概念和用法,后面会继续介绍内存管理的相关知识。

相关文章
相关标签/搜索