内存管理(二十六)

内存管理

在iOS中的app,会出现crash(闪退),基本上都是内存出了问题。

出现内存问题的原因,基本上有两种,内存溢出和野指针。

iOS会给每一个app都分配一定的内存。基本上都是固定平分的内存。因此,我们在开发一个app的时候,需要对内存进行管理,否则容易出现内存问题,导致闪退。

内存溢出,在固定的内存里,若是不释放内存,而是往内存里放入大量的数据时,就会导致内存溢出。导致闪退。

野指针:野指针就是,对象所占的内存空间已经被系统回收(指针已经释放),但是指针没有置空,这个指针就会继续指向这个对象数据所在的内存,还能读出里边的数据。此时就更应该注意野指针所带来的危害。

管理范围:任何继承于NSObject的对象,对其他的基本数据类型无效。

内存的管理方式

gc(垃圾回收机制):

只在oc 2.0后才支持,iOS不支持垃圾回收机制

MRC(手动引用计数):

即程序员手动管理内存,也就是开辟内存后和使用内存过后的释放。通过程序员自己写的代码来控制。

ARC(自动引用计数):

ARC不是垃圾回收,而是释放空间。这里的arc是允许程序员开辟空间而不用手动去release自己开辟的空间。arc的本质也是mrc。

引用计数

每个对象都有自己的引用计数器,表示对象所指向的那个实际内存空间一共有几个指针指向它。

alloc

alloc一般用在创建对象,主要是为了分配内存,然后把引用计数+1。

        //alloc:

        //功能1:分配内存

        //功能2:将引用计数置为1

        Person *p1 = [[Person alloc]init];

        NSLog(@"%ld",[p1 retainCount]);2015-04-23 11:10:14.508 OClesson9_内存管理[1029:40725] 1

retainCount,用来计算引用计数,但是苹果公司强烈要求不要用retainCount作为编码依据,因为在很多时候,系统也会不知道在什么情况下指向我们的对象或者其他对象。所以这个retainCount得出来的结果不准确。

retain

retain的作用,就是将引用计数 +1。

当引用计数 +1时,表示又友一个指针指向它,如果没有特殊要求,不要单独retain,不然会造成内存泄漏。

        //retain

        //功能:将引用计数+1

        //注意:当引用计数 +1 时,表示又有一个指针要指向它。如果没有特殊要求,不要单独retain,不然会内存泄露。

        Person *p2 = [p1 retain];

        NSLog(@"%ld",[p1 retainCount]);//2015-04-23 11:11:48.156 OClesson9_内存管理[1041:41178] 2

copy

标记为copy的实例变量或属性,所在的类必须要遵循NSCopying协议,并实现copyWithZone:方法。

copy标记后,开辟另一块空间,并把原来空间的东西拷贝到新的空间,原来的引用计数不变,新开辟的空间引用计数为1。

copy分为浅拷贝和深拷贝。

浅拷贝:

浅拷贝:又叫拷贝指针。就是把自己的地址再赋给自己。并让引用计数+1。

深拷贝:

深拷贝就是具体的实现copyWithZone:方法时,在里边创建对象,开辟空间,并把内容拷贝到新空间。

一个例子:

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject<NSCopying>

@property (nonatomic,copy)NSString *name;

@end

解释:

1、@property (nonatomic,copy)NSString *name;声明一个name属性,attitude为非原子保护nonatomic和copy的,此时就必须让Person类遵循<NSCopying>协议。

2、Person.m

#import "Person.h"

@implementation Person

 

- (void)dealloc{   

    [_name release];

    [super dealloc];

}

//浅拷贝

//又叫拷贝指针

//- (id)copyWithZone:(NSZone *)zone{

//    return [self retain];

//}

 

//深拷贝

- (id)copyWithZone:(NSZone *)zone{

    //创建新对象,而且创建新空间

    Person *p = [[Person allocWithZone:zone]init];

    //给新创建的对象的实例变量赋原来的值

    p.name = self.name;

    return p;

}

@end

解释:

1、return [self retain];是浅拷贝的形式,就是让自己指向自己的地址赋给自己,然后retain使引用计数 +1。

2、p.name = self.name;把原来name(name是个指向name所指向那块空间的一个地址)的地址赋给p对象的name。这样,就能让p对象的name指向了原来的地址所指向的内存。

main.m

#import <Foundation/Foundation.h>

#import "Person.h"

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

    @autoreleasepool {

        Person *p1 = [[Person alloc]init];

        p1.name = @"贝爷业";

       

        Person *p2 = [p1 copy];

       

        //地址一样,说明是浅拷贝

        NSLog(@"%p",p1);//2015-04-23 17:06:07.893 OCLesson9_NSCopying[2439:129348] 0x100206900

        NSLog(@"%p",p2);//2015-04-23 17:06:07.894 OCLesson9_NSCopying[2439:129348] 0x100206900

       

        //地址不一样,说明是深拷贝

        NSLog(@"%p",p1);//2015-04-23 17:13:08.601 OCLesson9_NSCopying[2468:131407] 0x100206900

        NSLog(@"%p",p2);//2015-04-23 17:13:08.602 OCLesson9_NSCopying[2468:131407] 0x100206b20

       

        NSLog(@"%@",p2.name);//2015-04-23 17:14:32.384 OCLesson9_NSCopying[2481:131971] 贝爷业

       

        [p1 release];

        p1 = nil;

       

       

    }

    return 0;

}

解释:

1、[p1 release];是将p1释放。

2、p1 = nil;是将p1释放后,把指针置空。若是没有这句,则会使得p1仍然能读取原来的数据,成为野指针。

assign

assign一般用来标记标量(不带 * 号的,或者基本数据类型)和代理delegate。

当然,也可以用assign来标记变量,但是需要重写getter、setter方法,否则就会使用assign自己的getter、setter方法。由于assign自己的getter、setter方法并没有对野指针和内存泄漏进行相应处理,会导致assign标记的变量可能出现野指针或者内存泄漏。

后面会降到具体的实现过程。

release

release是释放一个对象,并使得引用计数 – 1,表示减少一个指针指向这个内存。当引用计数为0 时,系统就会回收这块内存。

注意:我们在mrc下创建对象时,应该在后面写上配套的释放方法,也就是把对象release,并置空。因为在编辑代码过程中,随时会忘记对对象release。release释放对象是按照语句执行的顺序释放的。

autorelease

autorelease,与@autoreleasepool(自动释放池)配套使用。

在创建一个对象后,用autorelease来释放的话,需要注意与release释放的不同。

@autoreleasepool实际上是一个栈,在pool内,每遇到一个对象用autorelease释放,就会将这个对象压入@autoreleasepool的栈中。一直到语句执行到@autoreleasepool的作用域的花括号时,就会将这个对象出栈,一个一个的匹配地址,然后释放掉。所以就会出现先autorelease的对象,最后才被释放掉。

由于@autoreleasepool是一个栈结构,因此自己也有一定的容量,当创建的对象过多时,如果不及时释放的话,就会导致栈溢出。出现程序崩掉的情况。所以,有时候,考虑好数据量后,再考虑把@autoreleasepool写在哪个地方(防止溢出)。

一个例子:

Student.h

#import <Foundation/Foundation.h>

 

@interface Student : NSObject

 

@property(nonatomic , copy)NSString *name;

@property(nonatomic , assign)NSInteger age;

 

@end

Student.m

#import "Student.h"

 

@implementation Student

- (void)dealloc{

    NSLog(@"开始销毁实例变量%@...",_name);

    [_name release];

    //标量不需要release。

    NSLog(@"实例变量%@已销毁...",_name);

    [super dealloc];

}

@end

main.m

#import <Foundation/Foundation.h>

#import "Person.h"

#import "Student.h"

 

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

    //自动释放池

    @autoreleasepool {

 //练习

        Student *s1 = [[Student alloc]init];

        s1.name = @"黄飞鸿";

        s1.age = 19;

        [s1 release];

        s1 = nil;

        //retainCount,不能作为编程依据,因为不知道系统会调用多少次。

        Student *s3 = [[Student alloc]init];

        s3.name = @"s3";

        //release一执行,引用计数-1。

//        [s3 release];

       

        //autorelease,除了自动释放池(@autoreleasepool),引用计数-1。

        [s3 autorelease];

       

        Student *s4 = [[Student alloc]init];

        s4.name = @"s4";

        //release是按顺序释放

//        [s4 release];

        //autorelease

        //autoreleasepool是一个栈结构,先进后出。也就是s5先释放,s4后释放

        [s4 autorelease];//

        Student *s5 = [[Student alloc]init];

        s5.name = @"s5";

//        [s5 release];

        [s5 autorelease];

     }

    return 0;

}

autoreleasepool的几个例子:

Student.h

#import <Foundation/Foundation.h>

@interface Student : NSObject

@property(nonatomic , copy)NSString *name;

@property(nonatomic , assign)NSInteger age;

@end

Student.m

#import "Student.h"

@implementation Student

- (void)dealloc{

    NSLog(@"开始销毁实例变量%@...",_name);

    [_name release];

    //标量不需要release。

    NSLog(@"实例变量%@已销毁...",_name);

    [super dealloc];

}

@end

main.m

#import <Foundation/Foundation.h>

#import "Student.h"

//安全释放的宏定义(装逼小技巧)

//所有的宏定义都应该加do{}while(0)

#define SAFERELEASE(pointer)do{[pointer release];pointer = nil;}while(0)

//do{}while()就是为了限制里边变量的作用域

#define TEST do{int a = 10; int b = 20;}while(0)

 

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

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];

    Student *s1 = [[Student alloc]init];

    s1.name = @"aaaa";

    [s1 autorelease];

   

    [pool release];

    NSLog(@"%@",s1.name);//除了释放池,s1没有被限定,就变成了——野指针

   

   

//    @autoreleasepool {

//        for (int i = 0; i < 10000000; i ++) {

//            //autoreleasepool创建位置要合适

//            @autoreleasepool {

//            Student *s = [[Student alloc]init];

//            //使用

//            [s autorelease];//入栈,此时应该把池放到for里

//            }

//        }

    @autoreleasepool {

        Student *s1 = [[Student alloc]init];

        Student *s2 = [s1 retain];

        //谁污染谁治理(谁开辟的空间,谁就负责释放)

        [s1 release];

        [s2 release];

    }

   

    return 0;

}

@autoreleasepool写出来的时候,应该加上作用域,也就是加上花括号,因为若是不加上作用域,会导致后面释放的指针,在释放后还可以继续使用,一直到程序结束为止。成为野指针。

dealloc

dealloc方法是从父类NSObject继承过来的。

它的作用就是检测某个对象的引用计数是否为0 ,如果为0,则系统会自动调用dealloc方法,销毁对象自己的实例变量。

dealloc方法一般写在.m实现文件中,并且重写dealloc方法应该注意:

1、销毁自己的意思,就是销毁引用计数为0的那个对象的实例变量。

2、在写dealloc方法时,最先把[ super dealloc ]写在dealloc方法里的最后一行。然后在[ super dealloc ]前面几行再编辑其他代码。

3、在一个类中,声明一个变量(属性)的时候,应该在下面写上对应的dealloc。

Person.m

#import "Person.h"

 

@implementation Person

 

//当引用计数为0 时,自动调用这个方法。

 

- (void)dealloc{

    //当发现引用计数为0的时候,直接销毁对象

   

    //销毁自己

    //就是销毁自己的实例变量。

    NSLog(@"我完蛋了...");

    //全部实力变量在dealloc调用前,要全部release。

    //除了标量外。

    [_name release];

    //必须写在最后面。

    [super dealloc];

}

@end

原文地址:https://www.cnblogs.com/DevinSMR/p/5118642.html