OC+1-内存管理

内存管理的基本概念及范围
内存管理:系统会向app发送memory waring消息,收到消息后,需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等,否则程序会崩溃。
管理范围:管理任何继承NSObject的对象,对其他的基本数据类型无效(对象和其他数据类型在内存的存储位置不一样)
内存管理主要是对堆区中对象的内存管理

内存管理的原理及分类
原理
对象的所有权及引用计数 任何对象都可能拥有一个或多个所有者,只要一个对象至少还拥有一个所有者,它就会继续存在
引用计数器的作用: 在每个OC对象的内部,都有专门8个字节的存储空间来存储引用计数器,判断对象要不要回收(存在一种例外,对象值为nil,引用计数为0,但不回收空间)
对引用计数器的操作1、retain消息:是计数器+1,该方法返回对象本身
                                        2、release消息:是计数器-1(并不代表会释放)
                                        3、retainCount消息:获得对象当前的引用计数器值
对象的销毁:当一个对象的引用计数器为0时,name它将被销毁,其占用的内存被系统回收。当对象被销毁时,系统会自动向对象发送dealloc消息,一般会重写dealloc方法。一旦重写dealloc方法,必须调用[super dealloc],并且放在代码块的最后调用(不能直接调用dealloc方法)。
一旦对象被回收了,那么该空间就不可再用,坚持使用会导致程序崩溃(野指针错误)。
当使用alloc、new和copy出的对象,默认引用计数器为1

内存管理分类
OC提供了三种内存管理方式:1、Mannul Reference Counting(MRC,手动管理,在开发iOS4.1之前的版本的项目时,我们要自己负责使用引用计数来管理内存,比如要手动retain、release、autorelease等,而在其后的版本可以使用ARC,让系统自己管理内存)
     2、automatic reference counting (ARC,自动引用计数)
     3、garbage collection(垃圾回收,iOS不支持垃圾回收)
开发中如何使用:需要理解MRC,但实际使用时尽量用ARC

手动内存管理快速入门
// person 实例一个对象
       
Person *p = [ Person new ];
       
// 证明 p 有一个所有者
       
NSUInteger count = [p retainCount ]; // 用一个 8 字节无符号 long 类型来存储
       
NSLog ( @"count = %lu" , count);
       
       
// 是引用计数器 +1
       
// p 的所有者增加 1 ,让 p1 去指向
       
Person *p1 = [p retain ];
       
NSLog ( @"p1.retainCount = %lu" , [p1 retainCount ]);
       
       
// 回收对象,使 retainCount == 0
        [p
release ];
       
NSLog ( @"p.retainCount = %lu" , [p retainCount ]);
        [p release];//p执行后,此时对象被回收销毁,自动调用dealloc方法

重写dealloc方法
//dealloc 方法,是对象的临终遗言的方法
// 对象被销毁的时候,会默认的调用该方法
// 注意: dealloc 方法是系统自动调用的,不要手动调用
-(
void )dealloc{
   
// 先释放子类子集的对象空间
   
NSLog ( @"Person 已经挂了 " );
   
// 再释放父类的
    [
super dealloc ];
}

内存管理的原则
内存管理:
        对象如果不使用了,就应该回收他的空间,防止造成内存泄露
内存管理的范围:
        所有的继承了 NSObject 的对象的内存管理,基本数据类型除外
内存管理的原则:
        1 、原则:只要还有人使用某个对象,该对象就不能被回收
                只要你想使用这个对象,就应该让这个独享的引用计数器 +1 retain
                当你不想使用时,应该让对象的引用计数 -1 release
        2 、谁创建,谁 release
        3 、谁 retain ,谁 release
        4 、总结:有始有终,有加就有减
内存管理研究的内容:
        1 、野指针: 1 )定义的指针变量没有初始化
                 2 )指向的空间已经被释放了
        2、内存泄露:栈区的P已经释放了,而指向的堆区的空间还没释放,堆区的空间就被泄露了

// 创建: new alloc copy
       
Dog *bigYellowDog = [ Dog new ]; //1
       
NSLog ( @"bigYellowDog.retainCount = %lu" , [bigYellowDog retainCount ]);
       
Dog *jd = [bigYellowDog retain ]; // 新的对象,两个对象地址一样
       
NSLog ( @"jd.retainCount = %lu" , [jd retainCount ]);
       
//<Dog: 0x100206760>,<Dog: 0x100206760>
       
NSLog ( @"%@,%@" , bigYellowDog, jd);
       
       
// 谁创建,谁 release
        [bigYellowDog
release ];
        [jd release];
最后,当retainCount == 0,系统自动调用dealloc方法

单个对象内存管理(野指针)
Xcode在默认情况下,为了提高效率,不会开启僵尸对象检测Zombie->Edit scheme->run->
        Dog *byd = [ Dog new ];
        [byd eat];
        NSLog ( @"byd.retainCount = %lu" , byd. retainCount ); // 可以使用 . 调用此方法
       
        [byd release ]; // 逻辑上释放,物理上不可能删除,已经被释放的对象就是僵尸对象
       
        // 这就是野指针使用,已经释放了的空间。但是让僵尸对象去 eat ,不合理
        // 默认是不报错的,设置开启僵尸对象检测
        [byd eat ];
    // 同理,用已经释放的僵尸对象访问 retainCount 方法读取引用计数器的值,也是野指针使用
        NSLog(@"byd.retainCount = %lu", byd.retainCount);
注意:
1、
nil:是一个对象值,赋值为空,便是nil
Nil:是一个类对象值
NULL:是一个通用指针(泛型指针)
[NSNull null]:是一个对象,用在不能使用nil的场合

2、不能使用僵尸对象,使其死而复生
     [d release ];
// 僵尸对象死而复生 , 并未开启 Zombie ,不会报错
        [d
retain ];
       
//nil nil 发送任何消息都没有效果
       
// 避免使用僵尸对象的方式:对象释放完了以后,给对象赋值为 nil, 提高程序健壮性
        d = nil;
        [d retain ];//再使用僵尸对象,就不会报错,不会响应任何消息
3、野指针操作

单个对象的内存泄露问题
1、创建和使用完后,没有release
2、没有遵守内存管理原则,新增的retain要和release对应
3、不当的使用nil,在release之前,使用nil赋值。导致最后并没有release,因为nil不会响应任何消息
4、在方法的内部retain,但是在main中只release一次,也满足了内存管理的原则,此时的retain比较隐蔽,要注意


多个对象的内存管理(野指针)
        Person *fengjie = [ Person new ];
        Car *bigBen = [ Car new ];
        bigBen.
speed = 180 ;
       
       
// 给凤姐一辆车
        [fengjie
setCar :bigBen];
        [fengjie
goLasa ];
       
        [bigBen
release ]; //1->0  Car 销毁
 
       
// 这句话报错的原因   goLasa 方法中使用了 _car( 就是 bigBen) bigBen 已被销毁
       
// 要想下面的语句不报错,必须保证 _car 存在
        //要想在[bigBen release]之后,_car还存在  必须bigBen的引用对象不止一个
        // 即至少 [bigBen retain] 一次 , 但是这种方法明显比较笨
        //还可以在setCar方法里这样赋值_car = [car retain];这样比较健壮
        // 但是这样之后,依旧存在 [bigBen release] 的问题
        // 为了保证,人没亡,就可以调用车
        //我们可以在dealloc方法里进行[bigBen release]-[_car release]
        [fengjie goLasa];

set方法的内存管理
原则:在一个类中,有其他类的对象(关联关系)
基本数据类型作为实例变量:直接赋值_speed = speed;
对象类型作为另一个类的实例变量:先判断是否是同一个对象,然后先release旧值,在retain新值
     -( void ) setCar:( Car *)car {
    // 如果 _car == car 就是同一个对象
   
// 同一个对象, release 之后就不可以在 retain 了,否则 zombie 就复活了
   
if ( _car != car) {
       
// 先释放前一个车,release旧值
        [_car release];
         //retain新值,并且赋值给实例变量
        _car = [car retain ];
    }

@property参数
4.4之前
1)@property+手动实现
2)@property int age; + @synthesize age;//get和set方法的声明和实现都帮我们做了
3) @property int age; + @synthesize age = _age;//指定值赋值
4.4增强
@property int age;
1)生成_age
2)生成_age的get和set方法的声明和实现

格式:@property(参数1,参数2。。)数据类型 方法名
参数类别:
1、原子性:1)atomic:对属性加锁,多线程下线程安全,默认值
         2)nonatomic:对属性不加锁,多线程下不安全,但是速度快
2、读写属性:1 )readwrite:生成setter和getter方法,默认值
          2)readonly:只生成getter方法            
3、set方法处理:1)assign:直接赋值,默认值
               2)retain:先release原来的值,再retain新值
               3)copy:先release原来的值,再copy新值

替换set和get方法名称:@property(setter = isVip:,getter = isVip)
//setVip: == isVip:
//vip == isVip
//同样可以使用点语法

@class的使用
#import作用:把要引用的头文件内容,拷贝到写#import处
//如果引用的头文件内容发生变化,则引用到这个文件的所有类就需要重新编译,效率低
@class的使用
     格式:@class 类名;
     @class XXX;
     含义:告诉编译器,XXX是一个类,至于类有哪些属性和方法,此处不去检测
     好处:如果XXX文件内容发生变化,不需要重新编译
    注意:由于不知道属性和方法,所以不可以直接在其他类中去使用,如何要使用,需要在实现文件中导入该类头文件

所以,在实际开发中,.h中使用@class,.m中使用#import
     1).h  @class  XX;
     2).m  #import “XX.h"
     3)@class解决循环引入问题:交叉引用,循环依赖,使用#import会报错

循环retain的问题
Person *p = [ Person new ];
        Dog *d = [Dog new];
        p.dog = d;     //p,d互掐
        d. owner = p;
       
        [d
release ];
        [p release];
        [p release];//虽然也可解决问题,但有可能由于顺序不同,导致某个对象多释放一次

循环的retain会导致两个对象都会内存泄露
推荐的方法:一端使用assign直接赋值,一端使用retain

NSString类的内存管理
@“abc是字符串常量
栈区高地址,栈区-》堆区-》BSS-》数据区-》代码区
// 定义字符串
        //字符串的常量池
        // 如果你需要的字符串在常量池已经存在了,不会分配内存空间
        // 使用字符串的时候,
        //@"abc"   stringWithString  alloc initWithString  都在常量区
        //str2 str4 如果在常量区,地址应该一样
        // 但是地址不一样,在堆区
        NSString *str1 = @"abc" ; // 常量区
        NSString *str2 = [ NSString stringWithFormat : @"aaa" ];
        NSString *str3 = [ NSString stringWithString : @"abc" ]; // 常量区
        NSString *str4 = [[ NSString alloc ] initWithFormat : @"aaa" ];
        NSString *str5 = [[ NSString alloc ] initWithString : @"abc" ]; // 常量区
       
        NSLog ( @"str1 = %@,%p,%lu" , str1, str1, str1. retainCount );
        NSLog ( @"str2 = %@,%p,%lu" , str2, str2, str2. retainCount );
        NSLog ( @"str3 = %@,%p,%lu" , str3, str3, str3. retainCount );
        NSLog ( @"str4 = %@,%p,%lu" , str4, str4, str4. retainCount );
        NSLog(@"str5 = %@,%p,%lu", str5, str5, str5.retainCount);
危险用法
retainCount对于系统有时候不准,自己的对象要把握retain和release呼应
while([a retainCount] > 0){
     [a release];
}//死循环

autorelease基本使用
1、什么是autorelease?
自动释放池:1)在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的
          2)当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中
iOS5.0之后,创建方式
     @autoreleasepool
     {//开始
     。。。。
     }//结束
autorelease:是一种支持引用计数的内存管理方式
它可以暂时的保存某个对象,然后在内存池自己排干的时候对其中每个对象发release消息(每个对象只发送一次),注意,这里只是发送release消息,如果当时的retaiCount依然不为0,则该对象不会被释放。可以用该方法保存某个对象,也要注意保存之后要释放该对象。
自动释放池的使用
1)创建自动释放池
2)加入自动释放池,对象的引用计数不会改变
     [对象 autorelease];
3)自动释放池销毁,会对池子里的所有对象release一次
2、为什么会有autorelease?
OC内存管理原则:谁申请,谁释放。如果一个方法需要返回一个对象,该对象合适释放?方法内部是不会写release来释放对象的,因为这样立即释放会返回一个空对象;调用者也不会主动释放该对象,因为遵循“谁申请,谁释放”的原则。那么此时,就发生了内存泄露。
针对这种情况,OC设计了autorelease,既能保证对象正确释放,又能返回有效地对象
autorelease好处
1)不需要关心对象释放时间
2)不需要关心什么时候调用release
3、autorelease的原理?
autorelease实际上只是把对release的调用延迟了,将对象一直保留到autoreleasepool释放时,pool中的所有对象都会调用release
4、autorelease什么时候被释放?
1)5.0之前手动释放
2)5.0之后自动释放

autorelease的注意及错误使用
1)并不是所有放到自动释放池中的代码,产生的对象就会自动释放。如果需要释放,必须加入自动释放池
          Person *p = [[ Person new ] autorelease ];
2)如果对象调用了autorelease,但是调用的时候,没有放在任何一个自动释放池中,此对象也不会被加入自动释放池
3)尽量不要把内存较大的对象放到自动释放池中
4)不要在一个释放吃中,多次使用[p autorelease];
5)在alloc后,自动释放池结束之后,又进行 [p autorelease]操作,不允许。zombie操作

autorelease的应用场景
1、快速创建对象的类方法
Person *p = [ Person person ];
       
//person 类方法 : 快速创建对象,并且管理对象的内存(加入自动释放池)
       
//1 )创建一个对象   P
        //2)用完之后,系统把对象释放掉
+( id ) person
{
   
// 能够创建对象
   
// 能够帮我们把对象加入自动释放池
   
return [[[ Person alloc ] init ] autorelease ];
   
}
2、完善快速创建对象的方法
// 创建一个学生对象
       
Student *stu = [ Student person ]; // 返回的是 Person 类型
        [stu run];//动态类型,程序运行时才知道是什么类型,其实是[Person run];
只有将类方法修改一下才可以
+( id ) person
{
   
// 能够创建对象
   
// 能够帮我们把对象加入自动释放池
   
// 谁调用   返回谁
   
//Person 调用   返回 [Person run];
   
//Student 调用   返回 [Student run];
   
//Person--- self
   
return [[[ self alloc ] init ] autorelease ];
   
}
3、最终完善类方法
NSString *str = [ Student person ];
NSLog(@"str.lenth = %ld", str.length);
没完善之前,返回值类型是Student,指针类型是NSString。但是编译不会报错
将id类型改为instancetype类型能够智能的检测指针类型和返回值类型是否一致
+( instancetype ) person
{
   
// 能够创建对象
   
// 能够帮我们把对象加入自动释放池
   
// 谁调用   返回谁
   
//Person 调用   返回 [Person run];
   
//Student 调用   返回 [Student run];
   
//Person--- self
   
return [[[ self alloc ] init ] autorelease ];
   
}
当我们需要指定值的创建对象和初始化,我们可以
-( instancetype ) initWithAge:( int )age
{
   
//1 、先初始化父类的,并且判断是否成功
   
if ( self = [ super init ]) {
   
//2 、初始化子类
       
_age = age;
    }
   
//3 、返回 self
   
return self ;
}
//+(instancetype) student
//{
//    return [[self alloc] initWithAge:18];
//}
// 这样就写死了
+(
instancetype ) studentWithAge:( int )age
{
   
return [[ self alloc ] initWithAge :age];
}
此时的类方法,可以传递参数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值