Objective-C内存管理(上)

一.概述:

    1.内存的作用:存储数据(例子:声明一个变量,会将变量的数据存储到内存之中)。

                             

   2.当数据不再被使用的时候,占有的内存空间如何被释放:

  内存中的五大区域:

1.栈:局部变量,当局部变量的作用域被执行完毕之后,这个局部变量就被系统回收。

2.BSS段:未初始化的全局变量、静态变量。一旦初始化就回收,并转存到数据段之中。

3.数据段:已经初始化的全局变量、静态变量。直到程序结束的时候才会被回收。

4.代码段:代码,程序结束的时候系统会自动回收存储在代码段中的数据.

5.堆:实例化的对象。底层使用C函数申请的空间。需要我们手动进行管理。

  除了 '堆' ,其他由系统自动回收的,不需要我们手动管理。


  3.堆:对象存储在堆中,当“代码块(局部作用域)”结束时候,这个代码块(局部作用域)中涉及的所有局部变量都会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但是!!!对象依然存在内存中,造成内存泄露!!!

MRC情况下: 

void test(){

Person *p = [Person alloc] init];
}


int main(){
//内存泄漏
test();
return 0;
}

  无论是 iOS 还是 android 中,系统对每个程序运行时内存的占用都有一个限制,默认都是几十 M 左右大小,当 程序占用的内存的大小超过限制时,程序可能就会被强制退出。

二.如何进行管理呢?

1.了解一个概念:“引用计数器”

1). 每1个对象都有1个属性,叫做retainCount引用计数器,类型是unsigned long占据8个字节。(64位操作系统)
2). 当多1个人使用这个对象的时候,应该先让这个对象的引用计数器的值+1代表这个对象多1个人使用。
3). 当这个对象少1个人使用的时候,应该先让这个对象的引用计数器的值-1代表这个对象少1个人使用。

4). 当这个对象的引用计数器变为0的时候,代表这个对象无人使用,这个时候系统就会自动回收这个对象。

2.如何操作引用计数器:

1). 为对象发送1条retain消息,对象的引用计数器就会加1,当多1个人使用对象的时候才发。
2). 为对象发送1条release消息,对象的引用计数器就会减1,当少1个人使用对象的时候才发。
3). 为对象发送1条retainCount消息,就可以取到对象的引用计数器的值。
4). 对象的引用计数器变为0的时候,对象就会被系统立即回收,在对象被回收的时候,会自动调用对象的dealloc方法。

引用计数器的作用:

用来记录当前这个对象有多少个人在使用它,默认情况下,创建1个对象出来,这个对象的引用计数器的默认值是1。
 

Snip20160130_11 

如上图,这个对象正在被 A、B、C 三者使用(拥有),那么它的引用计数就是 3。


三.MAC:

僵尸对象:所占用的内存已经被回收的对象,僵尸对象不能再使用 .

野指针:指向僵尸对象的指针,给野指针发送消息会报错

空指针:没有指向任何东西的指针(存储的东西是 nil、null、0),给空指针发送消息不会报错。

内存泄露:指的是1个对象没有被及时的回收,在该回收的时候而没有被回收,一直驻留在内存中,直到程序结束的时候才回收。
 

  1. Xocode打开僵尸对象检测:Product -> Scheme -> EditScheme -> Diagnostict -> Zombie Objects(耗性能)

     2.野指针错误形式在Xcode中通常表现为:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)错误。

 

 补充:

nil:用于“对象”的字面空值。例如:NSString *someSting = nil;  id someObject = nil;

Nil: 用于Class类型的对象空值。 例如:class someclass = Nil;

NULL: 用于C指针空值。  例如: int *p = NULL;

NSNull:

  • 一般用于表示集合中值为空的对象。

  • 只有一个单例方法:+[NSNull null]

例子:

  • // 因为 nil 被用来用为集合结束的标志,所以 nil 不能存储在 Foundation 集合里。
    NSArray *array = [NSArray arrayWithObjects:@"one", @"two", nil];
    
    // 错误的使用
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:nil forKey:@"someKey"];
    
    // 正确的使用
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:[NSNull null] forKey:@"someKey"];
    

MRC: Manual Reference Counting 手动引用计数-手动内存管理-当多1个人使用对象的时候,要求程序员手动的发送retain消息,少1个人使用的时候程序员手动的发送relase消息。

在Xcode中关闭ARC(项目默认):项目属性—Build Settings--搜索“garbage”找到Objective-C Automatic Reference Counting设置为No即可。
 

  • MRC
    1). 新创建1个对象,这个对象的引用计数器的值默认是1。
    2). 当对象的引用计数器变为0的时候,对象就会被系统立即回收并自动调用dealloc方法。
    3). 为对象发送retain消息,对象的引用计数器就会+1。
    4). 为对象发送release消息,并不是回收对象,而是让对象的引用计数器-1


  • 当对象的引用计数器的值变为0的时候,对象才会被系统立即回收。
    MRC中重写dealloc方法的规范:
    必须要调用父类的dealloc方法,并且要放在最后一句代码。

  • 内存管理的重点及原则

    (1)谁创建,谁 release:
    如果你通过 alloc、new 或[mutable]copy 来创建一个对象,那么必须调用 release 或 autorelease

  • (2)谁 retain,谁 release:
    只要你调用了 retain,无论这个对象如何生成的,你都要调用 release。 综上:有始有终,有加就有减。曾经让对象计数器+1,就必须在最后让对象计数器-1。

    (3). 在ARC机制下,retain release dealloc这些方法方法无法调用.

#import <Foundation/Foundation.h>

@interface Person : NSObject

#pragma mark - 属性
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;

@end


#import "Person.h"

@implementation Person

#pragma mark - 覆盖方法
#pragma mark 重写dealloc方法,在这个方法中通常进行对象释放操作
-(void)dealloc{
    NSLog(@"Invoke Person's dealloc method.");
    [super dealloc];//注意最后一定要调用父类的dealloc方法(
两个目的:一是父类可能有其他引用对象需要释放;
二是:当前对象真正的释放操作是在super的dealloc中完成的)
}

@end
#import <Foundation/Foundation.h>
#import "Person.h"

void Test1(){
    Person *p=[[Person alloc]init]; //调用alloc,引用计数器+1
    p.name=@"xb";
    p.age=20;
    
    NSLog(@"retainCount=%lu",[p retainCount]);
    //结果:retainCount=1
    
    [p release];
    //结果:Invoke Person's dealloc method.
    
    
    
    //上面调用过release方法,p指向的对象就会被销毁,但是此时变量p中还存放着Person对象的地址,
    //如果不设置p=nil,则p就是一个野指针,它指向的内存已经不属于这个程序,因此是很危险的
    p=nil;
    //如果不设置p=nil,此时如果再调用对象release会报错,但是如果此时p已经是空指针了,
    //则在ObjC中给空指针发送消息是不会报错的
    [p release];
}

void Test2(){
    Person *p=[[Person alloc]init];
    p.name=@"xb";
    p.age=20;
    
    NSLog(@"retainCount=%lu",[p retainCount]);
    //结果:retainCount=1
    
    [p retain];//引用计数器+1
    NSLog(@"retainCount=%lu",[p retainCount]);
    //结果:retainCount=2
    
    [p release];//调用1次release引用计数器-1
    NSLog(@"retainCount=%lu",[p retainCount]);
    //结果:retainCount=1
    [p release];
    //结果:Invoke Person's dealloc method.
    p=nil;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test1();
    }
    return 0;
}

2.当一个对象的成员变量(属性)是一个对象的时候。

set方法的写法:

//人对象拥有车对象
-(void)setCar:(Car *)car{
if(_car !=car){
//对旧对象做一次release
[_car release];//诺没有旧对象,则没有影响。初始化的时候nil
_car = [car retain];
}
}

//在人对象重写dealloc
-(void)dealloc{
[_car release];
[super dealloc];
}

猜你喜欢

转载自blog.csdn.net/null959_/article/details/81138815