【iOS】关于自动引用计数的认识

文章目录

前言

在使用xcode编写程序的过程中,我们经常接触到自动释放池,自动释放池采用了自动引用计数(Automatic Reference Counting,简称 ARC),ARC作为iOS 5引入的一项特性,大大简化了内存管理,并减少了内存泄漏的可能性。本篇博客就让我们一起来简单了解一下这项技术。

什么是自动引用计数

自动引用计数是一种内存管理机制,用于跟踪和自动释放不再使用的内存。在 ARC 出现之前,开发者需要手动管理内存,通过 retain、release 和 autorelease 来控制对象的生命周期。ARC 作为 iOS 5 引入的一项特性,大大简化了内存管理,并减少了内存泄漏的可能性。

苹果的官方说明:

内存管理/引用计数

我们把"持有对象"看作是需要照明,把"释放对象"看作是无需照明(如下图)。

假如办公室照明设备只有一个,每次有人进来需要照明就打开照明设备;离开时不需要照明了就关闭照明设备。如果上班的人就一个,这样是没问题的,可是一旦上班的人不止一个,有人进来就打开照明设备,有人离开就关闭照明设备,这样就会出现问题。为了解决这样的问题,我们只需在第一个人进来的时候打开照明设备,最后一个人离开时关闭照明设备即可(如下图)。

在OC中,"对象"就相当于办公室的照明设备,一台计算机可以同时处理多个对象。我们可以将上班进入办公室的人对照明设备的操作与程序员编程时对oc对象所做的动作联动起来,即开灯可视为生成oc对象,需要照明就类比为持有oc对象,不需要照明就是释放oc对象,关灯则为废弃oc对象。

总而言之,使用引用计数能更好的管理oc对象。

内存管理的思考方式

引用计数式内存管理的正确思考方法:

  • 自己生成的对象,自己所持有。
  • 非自己生成的对象,自己也能持有。
  • 不再需要自己持有的对象时释放。
  • 非自己持有的对象无法释放。

对于oc对象的生成、持有、释放、废弃,其对应的操作方法如下:

这些oc管理方法不包含在该语言中,而是在Cocoa框架中。Cocoa框架中Foundation框架类库的NSObject类担负内存管理的职责。oc内存管理中的alloc\retain\release\dealloc方法分别指代NSObject类的alloc类方法、retain实例方和dealloc实例方法。

自己生成的对象,自己所持有

使用alloc、new、copy、mutableCopy名称开头的方法名意味着自己生成的对象只有自己持有。

自己生成并持有对象代码示例:

objectivec 复制代码
//使用NSObject类的alloc类方法
//自己生成并持有对象
id obj = [[NSObject alloc] init];//指向生成并持有的指针被赋给变量obj
//自己持有对象

//使用NSObject类的new类方法
//自己生成并持有对象
id obj = [NSObject new];//与上面的方法是完全一致的
//自己持有对象

copy方法利用基于NSCopying方法约定,由各类实现的copyWitnZone:方法生成并持有对象的副本;mutableCopy方法与copy方法类似,其利用基于NSMutableCopy方法约定,由各类实现的mutableCopyWithZone:方法生成并持有对象的副本。两者唯一的区别是前者生成的是不可变对象,后者生成的是可变对象。这两个方法虽然生成的是对象的副本,但跟alloc和new一样,均是自己生成并持有对象。

此外,对于allocMyObject、newThat、copyThisObject、mutableCopyYourObject等方法名,也是自己生成并持有对象。

对于 allocate、newer、copying、mutableCopyed等方法名,虽然使用alloc\new\copy\mutableCopy名称开头,但不属于同一类别的方法。

非自己生成的对象,自己也能持有

用alloc\new\copy\mutableCopy以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。这里我们以NSMutableArray类的array类方法为例:

objectivec 复制代码
//取得非自己生成并持有的对象
id obj = [NSMutableArray array];//NSMutableArray类对象被赋给obj变量
//取得的对象存在,但自己不持有

//使用retain方法可以持有对象
[obj retain];
//自己持有对象

通过retain方法可以使非自己生成的对象,变成自己所持有的(就跟用alloc\new\copy\mutableCopy方法生成并持有的对象一样)。

不再需要自己持有的对象时释放

自己所持有的对象,不再需要时,务必要进行释放,可通过release方法释放。

代码示例:

objectivec 复制代码
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象

//释放对象
[obj release];
//指向对象的指针仍保存在变量obj中,貌似能访问,但对象一经释放绝对不可访问。

因此,用alloc一类方法自己生成并持有的对象即可通过release方法来释放;而用这类方法之外的方法非自己生成并持有的对象可以先通过retain方法变为自己持有后,再通过release方法释放。

objectivec 复制代码
//取得非自己生成并持有的对象
id obj = [NSMutableArray array];

//自己持有对象
[obj retain];

//释放对象
[obj release];

用某个方法生成对象,并将其返还给该方法的调用方:

objectivec 复制代码
-(id)allocObject   //方法命名遵循前文所提及的规定,后续调用该方法就意味着生成并持有对象
{
    //自己生成并持有对象
    id obj = [[NSObject alloc] init];
    
    return obj;
}

如上述代码,将使用alloc方法生成并持有的对象原封不动地返回,就能让调用方也持有该对象。

下面对该方法进行调用:

objectivec 复制代码
//取得非自己生成并持有的对象
id obj1 = [obj0 allocObject];
//自己持有对象

下面我们讨论如果调用非alloc等命名开头的方法,使对象存在但自己不持有,释放该如何实现(这里以调用[NSMutableArray array]方法为例)。

objectivec 复制代码
-(id)object
{
    id obj = [[NSObject alloc] init];
    //自己持有对象
    
    [obj autorelease];//autorelease方法可以使取得的对象存在,但自己不持有对象。当对象超出指定的生存范围时能够自动且正确地释放对象(调用release方法)
    //取得的对象存在,但自己不持有
    
    return obj;
}

autorelease方法

1.基本概念

在 Objective - C 的 MRC 模式下,对象的生命周期由引用计数管理。当对象的引用计数变为 0 时,对象会被销毁。autorelease 方法的作用是将对象添加到当前的自动释放池中,当自动释放池被销毁时,池中的所有对象会收到 release 消息,引用计数减 1,若引用计数降为 0,对象就会被释放。这实现了对象的延迟释放,避免在创建对象的地方立即手动调用 release 方法。

2.具体使用

objectivec 复制代码
[object autorelease];//这里的 object 是要进行自动释放的对象

调用该方法后,对象会被加入到当前的自动释放池中。

3.工作机制

Objective - C 维护着一个自动释放池栈。当创建一个新的自动释放池时,它会被压入栈顶;当销毁自动释放池时,它会从栈中弹出。autorelease 方法会把对象添加到当前位于栈顶的自动释放池中。

对象入池与释放:

入池:调用 autorelease 方法的对象会被加入到自动释放池的对象列表中。

释放:当自动释放池被销毁时,会遍历池中的所有对象,并向每个对象发送 release 消息。

4.代码实例

objectivec 复制代码
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
@end

@implementation MyClass
@end

int main(int argc, const char * argv[]) {
    // 创建自动释放池
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // 创建对象并调用 autorelease 方法
    MyClass *myObject = [[MyClass alloc] init];
    [myObject autorelease];

    // 可以继续使用 myObject
    NSLog(@"Using myObject...");

    // 销毁自动释放池,对象会收到 release 消息
    [pool drain];

    return 0;
}

在上述代码中,myObject 调用 autorelease 方法后被加入到 pool 自动释放池中。当 [pool drain] 执行时,myObject 会收到 release 消息。

无法释放非自己持有的对象

倘若在程序中释放了非自己持有的对象,就会导致程序崩溃。

eg1(在"取得的对象存在,但自己不持有"时释放):

eg2(释放完自己生成并持有的对象后再次进行释放):

EXC_BAD_ACCESS 是一个通用的异常,表示程序试图访问一个无效的内存地址。这意味着程序尝试读取或写入一个未被分配给它的内存区域,或者访问已经被释放的内存。

code=1 一般表示访问了一个未被映射到进程地址空间的内存地址。

address=0xbeaddeac01b0 是具体的无效内存地址,0xbeaddeac 这类值通常是调试信息的占位符,实际指向的内存是无效的。

在手动引用计数(MRC)环境下,如果对一个对象多次调用 release 方法,会使对象的引用计数降为负数,对象被过度释放。后续再访问该对象时,就会出现内存访问错误。

关于"自己"

在 Objective - C 语言的内存管理中,"自己生成并持有对象"里的"自己"指的是代码中的执行主体,也就是调用创建对象方法的对象或者类。

类作为"自己"

当使用类方法创建对象时,类就是"自己"。类可以调用自身的方法来生成一个新的对象实例,并且在合适的情况下持有这个对象。

示例代码:

objc 复制代码
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
+ (instancetype)createInstance;
@end

@implementation MyClass
+ (instancetype)createInstance {
    // 类自己生成一个对象
    return [[self alloc] init];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 调用类方法生成对象
        MyClass *obj = [MyClass createInstance];
        // 此时 obj 被创建并持有
    }
    return 0;
}

在上述代码中,MyClass类通过createInstance类方法生成了一个MyClass的实例对象。这里的MyClass就是"自己",它负责创建并在返回对象后,由调用者(如main函数中的obj变量)持有该对象。

实例对象作为"自己"

在实例方法中,当前实例对象就是"自己"。实例可以调用方法生成新的对象并持有它。

示例代码:

objc 复制代码
#import <Foundation/Foundation.h>

@interface ContainerClass : NSObject
@property (nonatomic, strong) NSArray *array;
- (void)generateAndHoldArray;
@end

@implementation ContainerClass
- (void)generateAndHoldArray {
    // 实例自己生成一个对象并持有
    self.array = [[NSArray alloc] initWithObjects:@"item1", @"item2", nil];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ContainerClass *container = [[ContainerClass alloc] init];
        [container generateAndHoldArray];
        // container 对象持有了新生成的 NSArray 对象
    }
    return 0;
}

ContainerClassgenerateAndHoldArray实例方法中,当前的ContainerClass实例对象(即self)就是"自己"。它通过allocinit方法生成了一个NSArray对象,并将其赋值给自身的array属性,从而持有了这个新生成的对象。

小结

"自己"在不同的上下文中可以是类或者实例对象,它代表了执行对象创建操作的主体,并且该主体会确保新生成的对象在合适的范围内被持有,以便在需要时可以访问和使用这个对象。

总结

通过学习引用计数,了解到OC提供的自动释放池(ARC计数)的重要性,也了解了在MRC环境下如何进行手动计数管理。这里笔者只学习了大致怎么进行手动引用计数,至于具体alloc\retain\release\dealloc是怎么实现的,后续阅读oc源码时应该会学到。

相关推荐
LinXunFeng9 小时前
Flutter - iOS编译加速
flutter·xcode·apple
AskHarries11 小时前
Spring Boot中对接Twilio以实现发送验证码和验证短信码
ide·macos·xcode
北京自在科技14 小时前
【Find My功能科普】防盗黑科技如何改变生活?
科技·ios·生活·findmy
邓小乐18 小时前
xcode集成deepseek插件实现AI编程
xcode
水木姚姚1 天前
图形界面控件编程(iOS)
人工智能·python·macos·ios·xcode
书弋江山2 天前
Flutter 调用原生IOS接口
flutter·ios·cocoa
q567315232 天前
使用ASIWebPageRequest库编写Objective-C下载器程序
android·开发语言·macos·ios·objective-c·iphone
晨枫阳2 天前
不同开发语言之for循环的用法、区别总结
开发语言·python·objective-c·swift·js
星海拾遗2 天前
debug_unpack_ios failed: Exception: Failed to codesign 解决方案(亲测有效)
flutter·ios
水木姚姚2 天前
视频软件编程(iOS)
macos·ios·objective-c·音视频·xcode