OC基础知识 -- 内存管理

本文介绍了内存管理的三种方式:垃圾回收、人工引用计数及自动引用计数,并探讨了内存泄漏、溢出和野指针等问题。同时,还详细解析了影响引用计数的因素。

内存管理

内存管理的三种方式

1.垃圾回收(gc)轮询延迟

程序员只需要开辟内存空间,不需要⽤代码显⽰地释放,系统来判断哪些空间不再被使⽤,并回收这些内存空间,以便再次分配。整个回收的过程不需要写任何代码,由系统⾃动完成垃圾回收。Java开发中⼀直使⽤的就是垃圾回收技术

2.MRC(Manual Reference Counting)人工引用计数

内存的开辟和释放都由程序代码进⾏控制。相对垃圾回收来说,对内存的控制更加灵活,可以在需要释放的时候及时释放,对程序员的要求较⾼,程序员要熟悉内存管理的机制。

3.ARC(Auto Reference Counting)自动引用计数

iOS 5.0的编译器特性,它允许⽤户只开辟空间,不⽤去释放空间。它不是垃圾回收!它的本质还是MRC,只是编译器帮程序员默认加了释放的代码。

OS X使用的是垃圾回收的方式

iOS使用的是ARC方式

内存管理的3种常见问题

1.内存泄漏

在堆区开辟了空间后, 未进行释放操作, 结果导致一直占据该内存单元, 直到程序结束。

2.内存溢出

系统的内存是有限的,若程序占用内存超出了内存上限 程序会引起闪退(crash)

3.野指针

野指针: 指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为NULL而避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。

因此, OC中引入了引用计数器, 在每个对象的内存单元中都存放着一个计数器, 占据4个字节, 它用来计算对象的内存单元被指针引用的次数.

影响计数器的5个因素

1.alloc

alloc 先将指定内存空间清零 再开辟空间 引用计数 从 0 –> 1

    Person *person1 = [[Person alloc] initWithName:@"long" age:18];
    NSLog(@"%ld",[person1 retainCount]);

2.retain

只有retain 才能 +1 Person *per = person1 不增加计数

    Person *person2 = [person1 retain];
    NSLog(@"%ld",[person1 retainCount]);

    Person *person3 = person2;
    NSLog(@"%ld",[person1 retainCount]);

3.copy
如果要调用copy方法, 本类必须加入NSCopying协议

@interface Person : NSObject <NSCopying>

拷贝分为三种: 伪拷贝 浅拷贝 深拷贝

- (id)copyWithZone:(NSZone *)zone
{
    // 伪拷贝
    // 伪拷贝 拷贝完还是一个对象(直接返回)
    retain self;
}
- (id)copyWithZone:(NSZone *)zone
{
    // 浅拷贝
    // 拷贝后 有2个对象 但是它们的值是同一个值
    // 引用计数变化 被拷贝的对象不变 拷贝出来的新对象 从 0 - 1
    Person *p = [[Person alloc] initWithName:_name age:_age];
    return p;
}
- (id)copyWithZone:(NSZone *)zone
{ 
    // 深拷贝
    // 拷贝出新对象 并且 对象的值也重新拷贝一份 再赋值
    // 引用计数变化 被拷贝的对象不变 拷贝出来的新对象 从 0 - 1

    // 对字符串进行拷贝 拷贝的结果 要看字符串这个类如何实现的拷贝方法 对不可变字符串的拷贝 其实相当于直接retain一次
    // 可变字符串拷贝的时候 就是真的拷贝了一个新的

    // 字符串这个类 类簇
    NSString *name = [_name copy];
    Person *p = [[Person alloc] initWithName:name age:15];
    return p;
}

4.release

release 是引用计数 -1

当对象的引用计数为0的时候 该对象就会被系统释放

    Person *p1 = [[Person alloc] initWithName:@"long" age:19];
    NSLog(@"%ld",[p1 retainCount]);

    [p1 retain];
    NSLog(@"%ld",[p1 retainCount]);

    [p1 retain];
    NSLog(@"%ld",[p1 retainCount]);

    [p1 release];
    NSLog(@"%ld",[p1 retainCount]);

    [p1 release];
    NSLog(@"%ld",[p1 retainCount]);

    [p1 release];
// 计数器已经为0 p1 被释放 不要再去访问这个空间 一访问就是野指针 比如:
    NSLog(@"%ld",[p1 retainCount]);
// 当计数器为0时 系统会自动调用dealloc方法 
// 书写规范: 一定要写在类的最上面 避免忘记书写
// 系统自动调用 释放对象
- (void)dealloc
{
    NSLog(@"哈哈 被释放了");
    // 如果重写dealloc方法 必须要调用父类的方法
    // 保证对象可以被释放
    [super dealloc];
    // dealloc里面写东西 都写在[super dealloc]; 的上面 防止出现野指针
}

5.autorelease

// 在未来的某一个时刻 引用计数会自动 -1
// autorelease 需要自动释放池去 -1
// autorelease 依托自动释放池去释放的
// 出了自动释放池 相当于 系统会给释放池中间调用了autorelease的对象 发送一个 release消息

    Person *p1 = [[Person alloc] initWithName:@"hah" age:18];
    [p1 retain];
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    [p1 autorelease];
    [pool release];
    NSLog(@"%ld",p1.retainCount);

// 自动释放池
    @autoreleasepool {
        // 先进后出
      [p1 autorelease];
    }

// 对于遍历创建对象而言 @autoreleasepool 如下放置比较好 以免内存溢出
    for (int i = 0; i < 10; i++) {
        Person *p = [[Person alloc] initWithName:@"long" age:18];
        @autoreleasepool {
        [p autorelease];
        }
    }

在实现便利构造器时 返回值需要调用autorelease方法 以免造成内存泄露

+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age
{
    Person *person = [[Person alloc]initWithName:name age:age];
    return [person autorelease];
}

用便利构造器创建对象时 需要将对象创建在自动释放池之内

@autoreleasepool {
        Person *per = [Person personWithName:@"sdf" age:19];
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值