《Objective-C高级编程:iOS与OS X多线程和内存管理》 一 自动引用计数

el/2024/6/24 17:06:01

一、自动引用计数

1.1 内存管理的思考方式

对象操作与objective-c 方法的对应
对象操作Object-C  方法
生成并持有对象alloc、new、copy、mutableCopy
持有对象retain方法
释放对象release方法
废弃对象dealloc方法

        

 

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

使用以下名称开头的方法一位着自己生成的对象只有自己持有: alloc  |new | copy |mutableCopy

使用NSObject类的alloc类方法就能自己生成并持有对象。指向生成并持有对象的指针被赋值给变量obj. 使用new类方法也能生成并持有对象,

[NSOject new] == [[NSObject alloc] init]

copy 方法基于NSCooying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本。

与copy方法类似,mutableCopyWithZone:方法生成并持有对象的副本。

两者区别在于copy方法生成不可变更的对象,而mutableCopy方法生成可变更的对象。这类似于NSArray类对象与NSMutableArray类对象的差异。用这些方法生成的对象,虽然是对象的副本,但同alloc、new方法一样,在“自己生成并持有对象”这点上没有改变。

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

除 alloc  |new | copy |mutableCopy 之外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者

eg1:

// 取得非自己生成并持有的对象 取得的对象存在,但自己不持有对象
id obj = [NSMutableArray array];
// NSMutableArray类对象被赋给变量obj,但变量obj自己并不持有该对象,使用retain方法可以持有对象。
// 自己持有对象
[obj retain];
//通过retain 方法,非自己生成的对象跟用alloc、new、cooy、mutableCopy方法生成并持有的对象一样,成为了自己所持有的。

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

自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release方法

// 释放对象,对象不可再被访问
[obj release];

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

  • 根据命名规则,这些用来取得谁都不持有的对象的方法不能以alloc/ new / copy/ mutableCopy
- (id) allObject{id  obj = [NSObject alloc] init];return obj;
}
id obj1 = [obj0 allObject];

如上例所示:原封不动低返回用alloc方法生成并持有的对象,就能让调用方也持有该对象。

调用[NSMutableArray array] 方法使取得的对象存在,但自己不持有对象,如何实现?

- (id) object{id obj = [NSObject alloc ]init];[obj autorelease];// autorelease提供这样的功能,使对象在超出指定的生存范围时你那个自动并正确地释放(调用realse方法return obj;
}

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

对于持有者是自己的对象,不需要该对象时,需将其释放。

除此之外之外所得到的对象(非自己持有的对象)以及 自己持有对象后 释放过的对象,释放时会导致奔溃;

1.2  alloc/ retain/ release/ dealloc 实现 

p13

alloc类方法用struct obj_layout中的retained整数来保存引用计数,并将其写入对象内存头部,该对象内存块全部置为0返回。

执行alloc 后的对象的retainCout是1

由对象寻址找到对象的内存头部,从而访问其中的retained变量。

  • 由O调用bject - C对象中存有引用计数这一整数值。
  • 调用 alloc 或是 retain 方法后,引用计数值加1。
  • 调用release后,引用计数值减1。
  • 引用计数值为0时,调用dealloc方法废弃对象。

苹果的引用计数的实现:将引用计数保存在引用计数表的记录中。(GNUstep是将引用计数保存在对象占用内存块头部的变量中)

1.3 autorelease

autorelease会像C语言的自动变量那也来对待对象实例。当超出其作用域(相当于变量作用域)时,对象实例的release实例方法被调用。另外,同c语言不同的是,编程人员可以设定变量的作用域。

autorelease 的具体使用方法如下:

  1.  生成并持有NSAutoreleasePool对象;
  2. 调用已分配对象的autorelease方法;
  3. 废弃NSAutoreleasePool对象。

1.4 所有权修饰符

ARC有效时,id类型和对象类型同c语言其他类型不同,其类型上必须附加所有权修饰符,所有权修饰符一共有4种。

_strong、_weak、_unsafe_unretained、 _autoreleasing

_strong修饰符是id类型和对象类型默认的所有权修饰符。即:下面两种方式相同。表示对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

ARC有效时:

id obj = [[NSObject alloc] init]; 
id _strong obj = [[NSObject alloc] init];

ARC无效时:

{id obj = [[NSObject alloc] init]; [obj release];
}

为了释生成并持有的对象,增加了调用release方法的代码。改代码进行的动作同先前ARC有效时的动作完全一样。

取得非自己生成并持有的对象,因为变量obj为强引用,所以自己持有对象。当变量obj超出其作用域,强引用失效,所以自动低释放自己所持有的对象,对象的所有者不存在,因此废弃该对象。

id _strong obj = [NSMutable array];

附有_strong修饰符的变量之间可以相互赋值

id  _strong obj0 = [NSObject alloc]init]; // obj0持有对象A的强引用
id  _strong obj1 = [NSObject alloc]init]; // obj1持有对象B的强引用
id  _strong obj2 = nil;  // obj2不持有任何对象
obj0 = obj1; // obj0持有由obj1赋值的对象B的强引用。因为obj0被赋值,所以原先持有的对对象A的强引用失效,对象A的所有者不存,因此废弃对象A。
// 此时,持有对象B的强引用的变量为obj0和object1
obj2 = obj0; // obj2持有由obj0赋值的对象B的强引用。
//此时,持有对象B的强引用的变量为obj0,obj1和obj2
obj1 = nil; //因为nil被赋予了obj1,所以对对象B的强引用失效
//此时,持有对象B的强引用的变量为obj0和obj2
obj0 = nil;//因为nil被赋予了obj0,所以对对象B的强引用失效
//此时,持有对象B的强引用的变量obj2
obj2 = nil;//因为nil被赋予了obj1,所以对对象B的强引用失效
//此时,象B的所有值不存在,因此废弃对象B

_weak

_strong会造成循环引用 

        

@interface Test : NSObject{id _strong obj_;
}
- (void)setObject:(id _strong)obj;
@end
@implementation Test
- (id)init{self = [super init];return self;
}
- (void)setObject:(id _strong)obj{obj_ = obj;
}
@end
{id test0 = [[Test alloc] init]; // test0 持有Test对象A的强引用id test1 = [[Test alloc] init]; // test1 持有Test对象B的强引用[test0 setObject:test1];  //Test对象A的obj_成员变量持有Test对象B的强引用//此时,持有Test对象B的强引用的变量为 Test对象A的obj_和test1[test1 setObject:test0]; //Test对象B的obj_成员变量持有Test对象A的强引用//此时,持有Test对象A的强引用的变量为 Test对象B的obj_和test0
}
因为test0变量超出其作用域,强引用失效,所以自动释放Test对象A
因为test1变量超出其作用域,强引用失效,所以自动释放Test对象B
此时持有Test对象A的强引用的变量为Test对象B的obj_
此时持有Test对象B的强引用的变量为Test对象B的obj_
!!!发送内存泄漏

循环引用容易发送内存泄漏。所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。

此代码的本意是赋予变量test0()的对象A和赋予变量test1的对象B在超出其变量作用域时被释放,即在对象不被任何变量持有的状态下予以废弃。但是,循环引用使得对象不能再次废弃。

循环引用 ->自引用

id test = [Test alloc]init];
[test setObject:test];

_weak修饰符与_stong修饰符相反,提供弱引用,弱引用不能

{id _strong obj0 = [[NSObject alloc]init]; //自己生成并持有对象,因为obj0变量为强引用,所以自己持有对象。 id _weak obj1 = obj0; //obj1变量持有生成对象的弱引用 
}
因为变量超出其作用域,强引用失效,所以自动释放自己持有的对象,因为所有者不在,所以废弃该对象。

因为带 _weak 修饰符的变量(即弱引用)不持有对象,所以在超出其变量作用域时,对象即被释放。如果像下面这样将先前可能发生循环引用的类成员变量改成附有 _weak修饰符的成员变量的话,该现象可避免。

@interface Test : NSObject{id _weak obj_;
}
- (void)setObject:(id _strong)obj;
@end

_weak修饰符还有另一个优点。在持有某对象的弱引用时,该对象被废弃,则此引用将自动失效且处于nil被赋值的状态(空弱引用)。如下代码所示:

id  _weak obj1 = nil;
{id _strong obj0 = [[NSObject alloc] init];//自己生成并持有对象,因为obj0变量为强引用,所以自己持有对象。 obj1 = obj0;//obj1变量持有生成对象的弱引用
}
因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象,因为无所有者,所以废弃该对象。废弃所有者的同时,持有该对象弱引用的onj变量的弱引用失效,nil赋值给obj1.

使用 _weak修饰符可避免循环引用。通过检查附有_weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。

_autoreleasing修饰符

ARC 有效时,autorelease如何?

不能使用autolease方法,也不能使用NSAutoreleasePool类。但其实autolease功能起作用的。

//ARC无效
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [NSObject alloc] init];
[obj autorelease];
[pool drain];
//ARC无效
@autoreleasepool{id _autoreleasing obj = [NSObject alloc] init];
}

指定"@autoreleasepool 块"来替代“NSAutoreasePool类对象生成、持有以及废弃”这一范围。

ARC有效时,要通过将对象赋值给附加了_autoreleasing修饰符变量来替代调用autoreleasing方法。对象赋值给附有_autoreleasing修饰符的变量等价于在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool。

即在ARC有效时,用@autoreleasepool 块"来替代“NSAutoreasePool类,用附有_autoreleasing修饰符的变量替代autorelease方法。

取得非自己生成并持有的对象时,如下所示:虽然可以使用alloc、new、copy、mutableCopy以外的方法来取得对象,但该对象已被注册到了autoreleasepool。

这同在ARC无效时取得调用了autorelease方法得对象是一样的。

这是由于编译器会检查方法名是否以alloc、new、copy、mutableCopy开始,如果不是自动将返回值的对象注册到autoreleasepool. 

 !!! 要遵守内存管理方法命名规则,init方法返回值的对象不注册到autoreleasepool。

@autoreleasepool{id _strong obj = [MSMutable array];//取得非自己生成并持有的对象//因为变量obj为强引用,所以自己持有对象。并且该对象由编译器判断其方法后,自动注册到autoreleasepool
}
// 因为变量obj超出其作用域,强引用失效,所以自动释放自己持有的对象。
// 同时,随着@autoreleasepool 块的结束,注册到autoreleasepool中的所有对象被自动释放。因为对象的所有者不存在,所以废弃对象。

以上是不使用 _autoreleasing修饰符也能使对象注册到autoreleasepool。以下为取得非自己生成并持有对象时被调用方法:

+ (id) array{id obj = [[NSMutableArray alloc] init];return obj;
}

因为没有显示指定所有权修饰符,所以 id obj 同附有 _strong 修饰符的id_strong obj 是完全一样的。

由于return 使得对象变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放。但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool。

以下为使用_weak修饰符的例子。虽然_weak修饰符是为了避免循环引用而使用的,但在访问附有_weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象。

id _weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);
==========================
id _weak obj1 = obj0;
id _autoreleasing tmp = obj1;
NSLog(@"class = %@",[tmp class]);

为什么在访问附有_weak修饰符的变量必须注册到autoreleasepool的对象?

因为_weak修饰符只持有对象的弱引用,而在访问对象的过程中,该对象可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。因此在使用附有_weak修饰符的变量时就必定要使用注册到autoreleasepool中的对象。

id的指针或对象的指针会默认附加上_autoreleasing修饰符,所以以下代码相等:

- (BOOL) performOperationWithError:(NSError *)error;
========================
- (BOOL) performOperationWithError:(NSError *_autoreleasing *)error;

作为alloc /new /copy /mutableCopy 方法返回值取得的对象是自己生成并持有的,其他情况下便是取得非自己生成并持有的对象。

因此,使用附有_autoreleasing修饰符的变量作为对象取得参数,与除alloc /new /copy /mutableCopy外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象。

_unasfe_unretained修饰符

不安全的所有权修饰符,所修饰的变量不属于编译器的内存管理对象。同_weak 一样,自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即释放。

在使用_unasfe_unretained修饰符时,赋值给附有_strong修饰符的变量时,有必要确保被复制的对象确实存在。否则会奔溃。

1.5 规则

dealloc 中只需记述废弃对象时所必须的处理。

1.6 属性

当ARC有效时,以下可作为这种属性声明中使用的属性来用。

1.7 ARC的实现

1.7.1 _strong修饰符

 


http://www.ngui.cc/el/3419337.html

相关文章

《Objective-C高级编程:iOS与OS X多线程和内存管理》 一 Blocks模式GCD

一、Blocks摘要 Blocks:带有自动变量(局部变量)的匿名函数。 匿名函数:不带有名称的函数。 int func(int cout); //声明名称为func的函数 int result func(10); // 调用该函数,必须使用该函数的名称 // 若像下面这…

JXCategoryTitleView的使用

最近写项目,遇到了一个vc下面有多个vc 指示器可以点击切换,也可也左右滑动进行切换,解除了JXCategoryTitleView 感觉很好用; 一般要求主vc遵循JXCategoryViewDelegate、JXCategoryListContainerViewDelegate两个协议 子vc遵循JX…

基本编程能力练习

一、首先下载Java的编译环境并安装 JDK下载地址:https://www.oracle.com/java/technologies/javase-jdk15-downloads.html 选择适合自己系统的版本进行安装 这里我选择的是jdk-15_windows-x64_bin.exe 下载完成后直接运行安装,安装位置选择默认的C盘&am…

基本任务1.1Java语言基础

一、任务要求 完成基本数据类型的使用。完成基本运算符和表达式的使用。完成基本控制语句:判断、分支、循环等语句的使用。完成数组的使用。所有源代码必须加入行一级注释。 二、任务的理解 本次任务是让我们对Java编程中所遇到的基础知识进行学习和掌握&#xf…

基本任务1.1Java语言基础(任务挑战)

任务挑战: 1.编制一个Java application应用程序,把数组中已知的10个数字由小到大排序后输出。 编程前思考: 完成数组排序的方法有多种,其中有调用Arrays类中的sort方法直接对数组进行由小到大的排序,还可以采用冒泡排序…

基本任务1.2Java面向对象程序

一、任务要求 完成一个Java application应用程序,描述一个人类。要求如下:要求此人类必须包含有人的姓名、性别、出生日期等基本属性(变量)。要求此人类描述吃饭的方法(函数)和描述睡觉的方法(…

基本任务1.3Java API

完成一个 java application应用程序,可以接收用户通过键盘输入的文本,并输出此段文本字符的个数。 代码: package task; //包名 import java.util.Scanner; //导入Scanner类 public class JavaAPI { //创建类public static void…

Tensorflow的安装与配置

Tensorflow的安装与配置 经过多次的安装失败,找寻原因,最终安装成功。 1、基于之前安装的anaconda的基础上进行tensorflow的安装,首先通过按windows键和R键弹出运行框,输入CMD回车,进入cmd命令窗口,先查询…

基本任务1.4java异常捕捉机制

Java异常捕捉机制 任务要求: 一、完成一个 java application应用程序,完成ca/b 的计算并输出c的结果,可以为a和b在程序中赋初值、或者接收用户通过键盘输入a和b的数值文本后转换为数字等,在程序要求当 b 为0时c的计算结果正确。…

基本任务4.2WEB服务

任务要求: 一、学习Tomcat服务器的安装和配置,要求把其WEB根路径从默认值改为自定义的路径,要求把其WEB默认服务端口改为80。二、把基本任务4.1基本任务和挑战任务所完成的静态WEB页面放到WEB服务器根路径下,启动WEB服务器&#…