• 内存管理(二十六)


    内存管理

    在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

  • 相关阅读:
    Vue自定义Table
    Cesium GeometryIntstance 选中改变颜色 和 绘制带箭头的直线
    echart 饼图
    C# 读取json 文件 解析处理并另存
    滚动条到底 监听
    二分总结
    LeetCode 438. 找到字符串中所有字母异位词
    LeetCode 400. 第 N 位数字
    WPF深入浅出代码案例
    设计模式生成器模式
  • 原文地址:https://www.cnblogs.com/DevinSMR/p/5118642.html
Copyright © 2020-2023  润新知