黑马程序员--OC自学笔记---07内存管理、@property参数、@class、autorelease

本文详细介绍了Objective-C中的内存管理机制,包括所有权、引用计数、内存分类(MRC、ARC)、手动内存管理原则、内存管理研究内容、单个对象内存管理问题以及多个对象的内存管理策略。

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


1.   内存管理的范围

1)  管理任何继承NSObject的对象,对其他基本数据类型无效

2)  对象类型是程序运行过程中动态分配的,存储在堆区,内存管理主要是对堆区中对象的内存管理。

2.   内存管理的原理(理论)

1)        对象的所有权及引用计数

①  对象所有权概念:任何对象都可能拥有一个或多个所有者。只要一个对象至少还拥有一个所有者,它就会继续存在。

②  Cocoa所有权策略:任何自己创建的对象都归自己所有,可以使用名字以“alloc”或“new”开头或名字中包含“copy”的方法创建对象,可以使用retain来获得一个对象的所有权。

③  对象的引用计数器:每个OC对象都由自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象。对象刚被创建是,默认计数器值为1,当计数器的值变为0是,则对象销毁。

2)        在每个OC对象内部,都专门有8个字节的存储空间来存储引用计数器。

3)        引用计数器的作用

引用计数器是判断对象要不要回收的依据(存在一种例外:对象值为nil时,引用计数为0,但不回收空间)就是计数器是否为0,若不为0则存在。

4)        对引用计数器的操作

给对象发送消息,进行相应的计数器操作:

①  retain消息:使计数器+1,该方法返回对象本身

②  release消息:使计数器-1(并不代表释放对象)

③  retainCount消息:获得对象当前的引用计数器值   %ld  %tu

5)        对象的销毁

①  当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统回收。

②  当对象被销毁时,系统会自动向对象发送一条dealloc消息,一般会重写dealloc方法,在这里释放相关的资源,dealloc就像是对象的“临终遗言”。

③  一旦重写了dealloc方法就必须调用[super dealloc],并且放在代码块的最后调用(不能直接调用dealloc方法)。

④  一旦对象被回收了,那么它所占据的存储空间就不再可用,坚持使用会导致程序崩溃(野指针错误)。

注意:

⑤  如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出)。

⑥  任何一个对象,刚创建的时候,引用计数器都为1.(对象一旦创建好,默认引用计数器就是1)当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1.

3.   OC内存管理分类

1)        MRC,手动管理

2)        ARC,自动引用计数(iOS4.1之后)

3)        垃圾回收。

IOS中只有前两种,不支持垃圾回收。

4.   手动内存管理

1)        编译器默认使用ARC,所以首先要关闭ARC

2)        手动内存管理要重写dealloc方法

①          一定要在dealloc方法的最后调用父类的dealloc方法,[super dealloc],先释放子类占用的空间,再释放父类占用的空间

②          对self(当前)所拥有的其他对象做一次release操作

③          永远不要直接用对象来调用dealloc方法,是系统自动调用的。

④          一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误),为了防止调用出错,通常将“野指针”指向nil0

5.   内存管理的原则

1)        原则:

①  只要还有人在使用某个对象,那么这个对象就不会被回收

②  只要你想使用这个对象,那么就应该让这个对象的引用计数器+1

③  当你不想使用这个对象时,应该让对象的引用计数器-1.

2)        谁创建,谁release

①  如果你通过alloc,new,copy来创建了一个对象,那么你就必须调用release或者autorelease方法

②  不是你创建的就不用你去负责

3)       retain,谁release

只要你调用了retain,无论这个对象是如何生成的,你都要调用release

4)        总结

有始有终,有加就应该有减。曾经让某个对象计数器加1,就应该让其在最后-1.

6.   内存管理研究内容

1)        野指针

①          义的指针变量没有初始化

②          指向的空间已经被释放了

2)        内存泄露

如果栈区的指针变量已经释放了,而堆区的空间还没有释放,堆区的空间就泄露了。

7.   单个对象内存管理(野指针)

1)        对象创建完成以后,默认的所有者有一个,是自己,所以引用计数为1

2)        野指针错误:访问了一块坏的内存(已经被回收的,不可用的内存)

3)        僵尸对象:所占内存已经被回收的对象,僵尸对象不能再被使用(默认情况下xcode不会检查僵尸对象,需要自己打开检测

4)        nil :是一个对象值

5)        Nil:是一个对象指针

6)        NULL:是一个通用指针

7)        [NSNull null]是一个对象,它用在不能使用nil的场合。

8)        不能使用retain让僵尸对象起死回生。

#import <Foundation/Foundation.h>
@interface Dog : NSObject
-(void)eat;
@end
#import "Dog.h"
@implementation Dog
-(void)eat{
    NSLog(@"狗在吃东西!");
}
-(void)dealloc{
    NSLog(@"狗死了~~");
    [super dealloc];
}
@end
 
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        //使用MRC首先要关闭ARC
        //对象创建完成后,默认引用计数为1,所有者为自己
        Dog *d = [Dog new];
        [d eat];
       NSLog(@"d.retainCount = %lu",[d retainCount]);//1
        //一个对象被释放了,就变成僵尸对象
        [d release];
        //对象已经被释放,其引用计数已经没有意义
       //NSLog(@"d.retainCount = %lu",[d retainCount]);
       
        //僵尸对象不能再使用
        //这句话默认不会报错,是因为Xcode默认是不检查僵尸对象的,需要手动开启才能检测。
        //d也就是野指针
        //[d eat];
       
        //不能用retain让僵尸对象复生
        //[d retain];
    }
    return 0;
}

8.   单个对象的内存管理(2)

1)        避免使用僵尸对象:给僵尸对象赋值为nil

2)        内存泄露问题

①  创建完成后没有release

②  没有遵守内存管理原则

③  不当的使用了nil

④  在方法中错误的进行了retain

 

#import <Foundation/Foundation.h>
@interface Dog : NSObject
-(void)test:(Dog *)dog;
-(void)eat;
@end
#import "Dog.h"
@implementation Dog
-(void)test:(Dog *)dog{
    [dog retain];
}
-(void)eat{
    NSLog(@"狗在吃东西!");
}
-(void)dealloc{
    NSLog(@"狗死了~~");
    [super dealloc];
}
@end
 
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
     
        //内存泄露1
        //对象创建完成,使用后没有release
        Dog *d = [[Dogalloc]init];
        NSLog(@"d.retainCount= %lu",[d retainCount]);//1
        //没有release
       
        //内存泄露2
        //没有遵守内存管理的原则
        //谁创建,谁release。谁retain,谁release
        Dog *d2 = [[Dogalloc]init];
        [d2 retain];
        [d2 release];
       NSLog(@"d2.retainCount = %lu",[d2 retainCount]);//1
        //缺少一次release
       
        //内存泄露3
        //不当的使用了nil
        Dog *d3 = [[Dogalloc]init];
        d3 = nil;
        [d3 eat];       //等同于[nil eat],已经失效
        [d3 release];   //等同于[nil release],已经失效
        //内存未被释放,造成泄露
       
        //内存泄露4
        //调用的方法中使用了retain
        Dog *d4 = [[Dogalloc]init];
        [d4 test:d4];//调用的test方法中对传入的参数d4进行了retain
       
        [d4 release];
       NSLog(@"d4.retainCount = %lu",[d4 retainCount]);//1
       
    }
    return 0;
}

9.   多个对象的内存管理

1)        野指针问题:

当一个对象调用的方法中包含另一个对象时,如果方法结束后被调用的对象被释放,则主调对象不可再调用这个方法,否则产生僵尸对象问题。

2)        内存泄露问题

只有当主调对象先释放后,再释放被调对象才能保证不发生内存泄露

10. Set方法的内存管理问题

1)        对基本数据类型,直接赋值

int float  double long  struct  enum
-(void)setAge:(int)age{
  _age= age;
}

2)        OC对象类型原则:如果在一个类中,有其他类的对象(关联关系),set方法在书写的时候,要先判断是否是同一个对象,然后release旧值,retain新值

重要重要重要

-(void)setCar:(Car *)car{
  //1.先判断是不是新传进来的对象
  if(car!=_car){
        //2.对旧对象做一次release
        [_car  release];//若没有旧对象,则没有影响。
        //3.对新对象做一次retain
        _car= [car  retain];
  }
}

11. @property参数

1)        retain:对对象release旧值,retain新值(适用于OC对象类型)

在一个类中有关联其他对象的时候,使用retain参数,注意要重写dealloc方法,把对应对象释放一次。

@property(nonatomic,retain)Car * car;
//其set方法定义为:
-(void)setCar:(Car *)car{
  If(_car!= car){
        [_car release];
        _car= [car  retain];
  }
}

2)        assign:直接赋值(默认为assign,适用于非OC对象类型,即基本数据类型)

@property(nonatomic,assign)int age;等同于@property(nonatomic)int age;

3)        copy:release旧值,copy新值

4)        设置set和get方法的名称

自定义set方法的名称(注意方法名的冒号):

@property(nonatomic,setter=isTrue:)int age;

对应的set方法为-(void)isTrue:(int)age{_age = age;}

同样可以自定义get方法的名称

@ property(nonatomic,setter=isTrue);

对应的set方法为-(int)isTrue{return _age;}

12. @class的使用

1)        简单的引入一个类,格式为:@class 类名

@class XXX:告诉编译器XXX是一个类,至于类有哪些属性和方法,此处不会检测

好处:如果XXX文件的内容发生了改变,不需要重新编译

2)        使用注意:

在.h文件中使用@class引入某个类,在.m文件中使用#import引入该类。

使用@class引入某个类,只是告诉编译器引入的是一个类,而无法得知这个类有哪些属性和方法,所以在.m文件中实现时,如果调用了该类的属性或方法会报错,所以使用#import 引入该类,让程序在运行时去检测。

3)        @class可以解决循环引入问题

循环引入就是A引入了B,B同时引入了A,使用#import的话就会报错,可以使用@class来代替。

4)        #import的作用

把要引用的头文件(Person.h)的内容,拷贝到#import处,如果要引入的文件(Person.h)内容发生了变化,此时所有引入了该头文件(Person.h)的类,都需要重新编译一次

5)        @class和#import的区别

①  #import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中B *b只是类的声明,具体这个类有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类的信息。

②  使用@class方式由于只需要知道被引用类德尔名称就可以了,而在实现类由于要用到被引用类中的实体变量和方法,所以在实现文件中需要使用#import来包含被引用类的头文件。

③  使用#import,一旦头文件稍有改动,后面引用到这个文件的所有类都需要重新便以一遍,这样的效率太低了;而使用@class则不需要重新编译,效率要高很多。

综上:在实际开发中尽量在.h文件中使用@class来引入要包含的类

13. 循环retain问题

会当值两个对象都造成内存泄露,防止方法如下↓↓↓

1)        让某个对象多释放一次(注意顺序)

2)        推荐方法:一端使用assign,一端使用retain

14. NSString类的内存管理

1)  使用alloc initWithString:@”aaa”    stringWithString:@”aaaa”   @”aa”这三种方法来初始化字符串,都是位于常量区的

2)使用stringWithFormat:@”aa”  alloc  initWithFormat:@”aa”来初始化的字符串是位于堆区的

3)字符串的常量池:如果需要使用的字符串已经存在于常量池中,则不会重新去分配空间。

4)常量区的对象引用计数为无符号的最大值。

5)不要使用retainCount方法来作为循环条件,因为这个方法得到的值有可能是不准确的。

15. autorelease的基本使用

1)        自动释放池:

①  在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在的

②  当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中

③  Autorelease是一种支持引用计数的内存管理方式。它可以暂时的保存某个对象,然后再内存池自己排干的时候对其中每个对象发送release消息。

注意:这里只是发送release消息,如果当时的引用计数依然不为0,则该对象依然不会被释放。可以用该方法来保存某个对象,也要注意保存之后要释放该对象。

2)        为什么会有autorelease?

1)        OC的内存管理机制中比较重要的一条规律是:谁申请,谁释放。

2)        使用autorelease,既能确保对象能正确释放,又能返回有效的对象

3)        使用autorelease:不需要再关心对象释放的时间;不需要在关心什么时候调用release。

3)        Autorelease基本用法

1)        会将对象放到一个自动释放池中,创建自动释放池@autoreleasepool{  }

2)        当自动释放池被销毁时,会对池子里的所有对象做一次release。

3)        会返回对象本身

4)        对象加入自动释放池,[对象  autorelease]。调用完autorelease方法后,对象的计数器不受影响(销毁时影响-1)

4)        autorelease原理:

autorelease实际上只是把对release的调用延迟了,对于每一个autorelease,系统只是把该对象放入了当前的autoreleasepool中,当该pool被释放时,该pool中的所有对象会被调用release。

5)        autorelease何时释放

对于autorelease pool本身,会在以下两个条件发生时被释放:

①  手动释放autorelease pool

②  Runloop结束后自动释放

③  对于autorelease pool内部的对象,在引用计数的retain == 0 的时候释放。Release和autorelease pool的drain都会触发retain事件。

16. Autorelease使用注意

1)        并不是所有的放到自动释放池的代码,产生的对象就会自动释放,如果需要释放,必须使用autorelease方法加入到自动释放池。

2)        如果对象调用了autorelease,但是调用的时候没有在任何一个自动释放池中,此时该对象也不会被加入到自动释放池中

3)        不管创建对象的代码是否在自动释放池中,只要调用autorelease方法的代码在自动释放池中,该对象就被加入到了自动释放池中。

4)        自动释放池的嵌套

自动释放池的栈结构(数据结构),和内存的栈区是不一样的,对象时存在位于栈顶位置的自动释放池中。

5)        不建议把占内存比较大的对象加入到自动释放池中

错误用法:

1)        连续多次调用autorelease,释放池销毁时执行两次release。

2)        Alloc之后调用了autorelease,之后又调用了release。

17. Autorelease的应用场景

1)        经常用来在类方法中快速创建一个对象,并且管理对象的内存。创建一个类方法,在方法中进行创建对象及添加到自动释放池中

+(instancetype)person{

  Return[[[self  alloc]init]autorelease];

}

2)        动态类型,程序运行的时候才知道属于什么类型

3)        Instancetype可以智能的判断返回的类型和接收的类型是否一致,而id类型不会进行判断

使用autorelease快速创建方法应用:

#import <Foundation/Foundation.h>
 
@interface Student : NSObject
@property (nonatomic,assign)int age;      //使用@property生成一个实例变量及其set和get方法
-(instancetype)initWithAge:(int)age;    //重写构造方法
+(instancetype)studentWithAge:(int)age;//使用autorelease快速创建一个类
@end
#import "Student.h"
 
@implementation Student
//重写dealloc方法,便于查看对象是否释放
-(void)dealloc{
    NSLog(@"studentdealloc");
    [super dealloc];
}
-(instancetype)initWithAge:(int)age{
    if (self = [super init]) {
        _age = age;
    }
    return self;
}
+(instancetype)studentWithAge:(int)age{
    return [[[selfalloc]initWithAge:age] autorelease];
}
@end
 
 
#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        Student *stu =[Student studentWithAge:19];
        NSLog(@"stu.age =%d",stu.age);
    }
    return 0;
}

结果截图


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陌影~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值