【IOS 开发学习总结-OC-24】★★★objective-c——内存管理

本文详细探讨Objective-C的手动内存管理,包括引用计数、自动释放池和手动内存释放的基本思路。讲解了如何处理临时对象、事件循环中的自动释放池,并对内存管理的规则进行了总结。此外,还提及了自动引用计数(ARC)和@autoreleasepool块的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

手动内存管理

内存泄露:我们在用面向对象的语言编程时,会不断地创造对象,随着时间的推移,有些老的对象不再会被调用,也不再有指针指向它们,但如果程序没有回收它们占用的内存,就会出现形参内存泄露。

有效的内存管理包括2个方面

  1. 内存分配(相对容易)——合理设计,尽量减少对象的创建,减少创建过程中对内存的开销
  2. 内存回收——程序不再需要该对象时,必须及时回收它们占用的内存。
    内存回收策略有2种:
    1. 自动回收
    2. 混合回收——自动+手动

objective-c 可用的内存回收机制有3种:
1. 手动引用计数和自动释放池
2. 自动引用计数(ARC)——全称:automatic reference counting
3. 自动垃圾回收——ios 系统不支持

对象的引用计数

OC用引用计数( 对象关联的整数,该整数称为引用计数器)来跟踪对象的状态,正常情况下,当一段代码访问某个对象时,该对象的引用计数+1,当这段代码不再访问该对象时,该对象的引用计数-1,表示这段代码不再访问该对象。当引用计数的数值为0时,表示程序已经不再需要该对象,系统会自动回收该对象占用的内存。
系统在销毁该对象 之前,会调用 dealloc方法来执行回收操作,所有的对象都有该方法——继承自 NSObject。

注意:当对象被销毁后(引用计数为0,系统调用 dealloc 方法),此时该对象已经不再存在,若有指针指向这个销毁的对象,这个指针就称为悬空指针(dangling pointer)。
程序员不要主动调用该对象的 dealloc 方法!!!

手动引用计数中改变对象的引用计数的方式和一些方法:
1. 当程序调用以 alloc,new,copy,mutableCopy开头的方法(以驼峰写法开头的方法)创建对象时,该对象的引用计数+1;
2. 程序调用对象的 retain 方法时,该对象的引用计数+1——retain 方法返回调用该方法的对象本身,所以在调用该方法后,可直接在此调用其他方法
3. 程序调用对象的 release 方法时,该对象的引用计数-1;
4. autorelease方法: 不改变该对象的 引用计数器的值,只是将对象添加到自动释放池中。
5. retainCount 方法:返回该对象的引用计数的值。

示例程序:
FKItem.h

#import <Foundation/Foundation.h>

// 定义类的接口部分
@interface FKItem : NSObject
@end

FKItem.m

#import "FKItem.h"

// 为FKItem提供实现部分
@implementation FKItem
// 重写该方法作为测试
- (id) init
{
    if(self == [super init])
    {
        NSLog(@"init方法中,引用计数为:%ld" , self.retainCount);
    }
    return self;
}
// 重写该方法作为测试
- (void) dealloc
{
    NSLog(@"系统开始销毁我了,再见!");
    [super dealloc];
}
@end
#import "FKItem.h"

// 为FKItem提供实现部分
@implementation FKItem
// 重写该方法作为测试
- (id) init
{
    if(self == [super init])
    {
        NSLog(@"init方法中,引用计数为:%ld" , self.retainCount);
    }
    return self;
}
// 重写该方法作为测试
- (void) dealloc
{
    NSLog(@"系统开始销毁我了,再见!");
    [super dealloc];
}
@end

这里写图片描述

FKItemTest.m

#import <Foundation/Foundation.h>
#import "FKItem.h"

int main(int argc , char * argv[])
{
    // 调用new方法创建对象,该对象的引用计数为0
    FKItem* item = [FKItem new];
    NSLog(@"%ld" , item.retainCount);
    [item retain]; // 引用计数加1,retainCount为2
    NSLog(@"%ld" , item.retainCount);
    [item retain]; // 引用计数加1,retainCount为3
    NSLog(@"%ld" , item.retainCount);
    [item release]; // 引用计数减1,retainCount为2
    NSLog(@"%ld" , item.retainCount);   
    [item retain]; // 引用计数加1,retainCount为3
    NSLog(@"%ld" , item.retainCount);
    [item release]; // 引用计数减1,retainCount为2
    NSLog(@"%ld" , item.retainCount);
    [item release]; // 引用计数减1,retainCount为1
    NSLog(@"%ld" , item.retainCount);
    [item release]; // 引用计数减1,retainCount为0
    // 系统会自动调用该对象的dealloc方法来销毁它,
    // 后面代码不要再调用item指针的方法,调用item方法看到的都是假象,甚至可能导致程序崩溃
}
编译运行结果为:
2015-09-30 07:46:42.993 923[1173:23728] init方法中,引用计数为:1
2015-09-30 07:46:42.995 923[1173:23728] 1
2015-09-30 07:46:42.995 923[1173:23728] 2
2015-09-30 07:46:42.995 923[1173:23728] 3
2015-09-30 07:46:42.996 923[1173:23728] 2
2015-09-30 07:46:42.996 923[1173:23728] 3
2015-09-30 07:46:42.997 923[1173:23728] 2
2015-09-30 07:46:42.997 923[1173:23728] 1
2015-09-30 07:46:42.997 923[1173:23728] 系统开始销毁我了,再见!

注意:上面的程序是手动的内存回收。需要关闭 ARC。如何关闭 ARC 呢?很简单,请看下图。
这里写图片描述

手动内存释放的基本思路

基本应该遵循:谁(包括对象,函数等)把对象的引用计数加了1,谁就要负责在”临死”前把该对象的引用计数减一。——任何实体(包括对象,函数等)在结束前应该把其他对象的引用计数恢复到开始前的状态。

自动释放池

为了保证函数,方法返回的对象,不会在被返回之前就被销毁,我们需要保证被函数,方法返回的对象能被延迟销毁。为实现延时销毁,方法有2种: 1. 程序每次获取并使用完其他方法,函数返回的对象后,立即调用该对象的 release 方法将函数,方法返回对象的引用计数-1.——这种方法,易理解,但编程复杂 2. 使用自动释放池进行延迟销毁。

>自动释放池:是指它是一个存放对象的容器(比如集合),而自动释放池会保证延迟释放该池中所有的对象。

哪些对象应该添加到自动释放池中呢?怎样把对象加到自动释放池中呢?

所有对象都应该添加到自动释放池中——出于自动释放的考虑,这样可以让自动释放池在销毁之前,先销毁池中的所有对象。 要把一个对象添加到自动释放池中,可调用该对象的 autorelease 方法——该方法返回调用该方法的对象本身。 autorelease方法,会把对象的引用计数—1,时间是自动释放池释放时,自动释放池会让池中所有的对象执行 release 方法。 示例代码: 实现延时销毁第一种情况示例:
#import <Foundation/Foundation.h>
#import "FKItem.h"

FKItem* productItem()
{
    FKItem* item = [FKItem new]; // 引用计数为1
    NSLog(@"函数返回之前的引用计数:%ld" , item.retainCount);
    // 返回的对象的引用计数为1
    return item;
}

int main(int argc , char * argv[])
{
    // it的引用计数为1
    FKItem* it = productItem();
    // 接下来可以调用it的方法
    NSLog(@"%ld" , it.retainCount);
    // ...
    // 程序执行完成后,将it(引用productItem()函数返回值)的引用计数减1
    // 程序使用productItem()函数返回值完成后,延迟销毁it所指向的对象
    [it release];
}
实现延时销毁第二种情况:
#import <Foundation/Foundation.h>
#import "FKItem.h"
#import "FKUser.h"

FKItem* productItem()
{
    FKItem* item = [FKItem new]; // 引用计数为1
    NSLog(@"函数返回之前的引用计数:%ld" , item.retainCount);
    // autorelease不会改变对象的引用计数
    // 但程序执行autorelease方法时,会将该对象添加到自动释放池中
    return [item autorelease];
}

int main(int argc , char * argv[])
{
    //创建自动释放池
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    // it的引用计数为1
    FKItem* it = productItem();
    // 接下来可以调用it的方法
    NSLog(@"%ld" , it.retainCount);
    // ...
    // 创建一个FKUser对象,并将它添加到自动释放池
    FKUser* user = [[[FKUser alloc] init] autorelease];
    // 接下来可以调用user的方法
    NSLog(@"%ld" , user.retainCount);
    // ...
    // 系统将因为池的引用计数变为0而回收自动释放池,
    // 回收自动释放池时会调用池中所有对象的release方法
    [pool release];
}

这里写图片描述

☆☆☆临时对象与事件循环中的自动释放池

**对于 foundation 中的类而言,当调用方法创建对象时,只要这些方法不是以 alloc,new,copy,mutableCopy开头的,系统就会创建自动释放对象。** 例如如下代码: ` NSMutableArray* arr=[NSMutableArray arrayWithCapacity:20];`

什么是临时对象?

arr 就是个自动释放对象,自动释放池释放时,该对象就会被自动释放,这种对象就被称为——临时对象。

什么是事件循环?

Cocoa 与 ios 的事件循环,是指当程序中某组事件被激发(如按钮被单击)后,系统将会回调某个方法来 处理该事件。 事件循环的步骤如下: 1. 创建自动释放池 2. 调用事件处理方法 3. 销毁自动释放池 4. 处理完成,方法返回 由上面的步骤可看出,事件循环中自动创建的是自动释放上下文,,所以,事件处理方法中创建的临时对象都会被销毁。如果程序希望在事件处理方法中 对实例变量赋值,那么就需要格外小心———如果赋值的对象是临时对象,那么对象赋值完成和事件处理结束时,该对象已被回收。 如果需要在事件处理结束后依然可以保留临时对象,可采用如下2种方式之一: 1. 将临时对象赋值之前,先调用临时对象的 retain 方法将它的引用计数+1。这里说明一下,retain 指示符只有在 ARC 机制未启用的 情况下有效。 示例代码:
#import <UIKit/UIKit.h>
@interface MyView : UIView
@property(nonatomic, retain ) NSMutableArray* data;
@end
  1. 把临时对象赋值给 retain,strong 或 copy 指示符修饰的属性。——注意:assign,weak,unsafe_unretain指示符除外。
    data属性用 copy 指示符也是可以的。copy 指示符对应的 setter 方法:
if (property != newValue)
{
     [property release];
     property = [newValue copy];
}

上面的代码返回了newValue的副本,会让副本的引用计数+1。上面的做法虽然没有把newValue的引用计数+1,当被赋值的newValue对象为临时对象,自动释放池释放时,newValue会被释放。但留下了副本。
由于MyView类使用 data 保留了 NSMutableArray 对象(该对象引用计数+1),因此,程序需要在 MyView 的 dealloc 方法中将该 NSMutableArray 对象的引用计数-1——重写MyView类的如下方法:

-(void)dealloc
{
    [data release];
    [super dealloc];
}

手动内存管理的规则总结

  1. 调用对象的 release 方法并不是销毁对象——只是将该对象的引用计数-1;
  2. 自动释放池被回收时,自动释放池会依次调用池中每个对象的 release 方法。——若调用该方法后,引用计数为0,对象被销毁,否则,对象可以从自动释放池中”存活”;
  3. 当程序以 alloc,new,copy,mutableCopy开头的方法创建对象后,该对象的引用计数为1,当不在使用该对象时,调用该 对象的 release或 autorelease 方法。
  4. 使用 retain 方法为对象增加引用计数,用完后对象需要调用 release 方法来减少该对象的引用计数,并保证 retain 次数与 release 次数相等。
  5. 如果创建的对象是个临时对象(其他方法创建的),如果在自动释放上下文中国使用该对象,那么使用完成后无需理会该对象的回收——系统自动回收。如果要保留这个临时对象,需手动调用 retain 来增加该临时 对象的引用计数,或将该临时对象赋值给 retain,strong 或 copy指示符修饰的属性。
  6. 在 Cocoa 或 ios 的事件循环中,每个事件处理方法执行之前会创建自动释放池,如果希望自动释放池被回收后,池中某些对象能”存活”下来,程序必须增加该对象的引用计数,保证该对象的引用计数大于它调用 autorelease 的次数。

自动引用计数

一旦启用 ARC 机制,程序员不被允许在程序中调用对象的 retain,release 方法来改变对象的引用计数。
前面已经介绍了如何启用ARC机制。
在某些情况下,如果项目需要单独对某个文件启用手动引用计数,——单独关闭某个文件的 ARC 机制。那么如何做呢?
如下图:
这里写图片描述
找到相应的文件后,双击 添加指令:-fno-objc-arc。

@autoreleasepool 块

@autoreleasepool 块定义自动释放池上下文,任何在块中创建的对象,都由 ARC 来自动释放 ,并在自动释放块结束时销毁这些对象。

如果在程序执行过程中,创建了大量的临时对象(如在循环中创建大量临时对象——非常 差的做法),程序可能需要多次释放这些临时对象,此时程序可以考虑将@autoreleasepool 块放在循环体内,如下代码:

for (int i=0; i<100; i++) 
{
    @autoreleasepool {
            //创建临时对象
            //调用临时对象方法
            //...
        }
}

上面代码在循环体每次执行完成时,都会销毁这些临时对象,避免整个循环结束时才去销毁这些临时对象——不便于更好地管理内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值