【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(四)

目录

ARC规则

规则

对象型变量不能作为C语言结构体的成员

显式转换id和void*

属性

数组


ARC规则

规则

在ARC有效的情况下编译源代码必须遵守一定的规则:

主要解释一下最后两条

对象型变量不能作为C语言结构体的成员

要把对象型变量加入到结构体成员中时,可强制转换为void*或是附加前面所述的__unsafe_unretained修饰符。

显式转换id和void*

ARC无效时,像以下代码这样将id变量强制转换void*变量并不会出问题。

objectivec 复制代码
id obj = [[NSObejct alloc] init];
void *p = obj;

更进一步,将该void*变量赋值给id变量中,调用其实例方法,运行时也不会有问题

objectivec 复制代码
id o = p;
[o release];

但是在ARC有效时这便会引起编译错误。

id型或对象型变量赋值给void*或者逆向赋值时都需要进行特定的转换。如果只想单纯地赋值,则可以使用"__bridge转换"。

objectivec 复制代码
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;

像这样,通过"__bridge转换",id和void*就能互相转换。

但是转换为void* 的**** bridge转换,其安全性与赋值给****unsafe_unretained修饰符相近,甚至会更低。如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。

__ bridge转换中还有另外两种转换,分别是"__ bridge_retained转换"和"__ bridge**_**transfer转换"

objectivec 复制代码
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;

__ bridge**_**retained转换可使要转换赋值的变量也持有所赋值的对象。下面来看看ARC无效时的源代码是如何编写的

objectivec 复制代码
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];

__ bridge**_**retained转换变为了retain。变量obj和变量p同时持有对象。再来看几个其他的例子。

objectivec 复制代码
void *p = 0;
{
    id obj = [[NSObject alloc] init];
    p = (__bridge_retained void *)obj;
}
NSLog(@"class=%@", [(__bridge id)p class]);

变量作用域结束时,虽然随着持有强引用的变量obj失效,对象随之释放,但由于__bridge_retained转换使变量p看上去处于持有该对象的状态,因此该对象不会被废弃。下面我们比较一下ARC无效时的代码

objectivec 复制代码
void *p = 0;
​
{
    id obj = [[NSObject alloc] init];
    //[obj retainCount] -> 1
    p = [obj retain];
    //[obj retainCount] -> 2
    [obj release];
    //[obj retainCount] ->1
}
//[(id)p retainCount] -> 1
//即 [obj retainCount] -> 1
//对象仍存在
NSLog(@"class=%@", [(__bridge id)p class]);

**__**bridge_transfer转换提供与此相反的动作,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放。

objectivec 复制代码
id obj = (__bridge_transfer id)p;

该源代码在ARC无效时这样表述:

objectivec 复制代码
id obj = (id)p;
[obj retain];
[(id)p release];

同**** bridge_retained转换与retain类似,****bridge_transfer转换与release相似。在给id obj赋值时retain即相当于__strong修饰符的变量。

如果使用以上两种转换,那么不使用id型或对象型变量也可以生成、持有以及释放对象。虽然可以这样做,但是在ARC中不推荐这种方法

objectivec 复制代码
void *p = (__bridge_retained void *)[[NSObject alloc] init];
NSLog(@"class=%@", [(__bridge id)p class]);
(void)(__bridge_transfer id)p;

该源代码与ARC无效时的下列源代码相同

objectivec 复制代码
//ARC无效
id p = [[NSObject alloc] init];
NSLog(@"class=%@", [p class]);
[p release];

这些转换多用于OC对象与CF对象之间的相互变换中。

OC对象和CF对象的区别很小,不同之处仅仅只在于生成对象的框架不同。可以使用免费桥来实现二者之间的转换("Toll---Free Bridge",这种转换不用使用额外的CPU资源)。

以下函数即Toll---Free Bridge转换的函数,可用于OC对象和CF对象之间的相互变换,即Toll---Free Bridge转换。

属性

当ARC有效时,OC类的属性也会发生变化。

以上各种属性赋值给指定的属性中就相当于赋值给附加各属性对应的所有权修饰符的变量中。只有copy属性不是简单的赋值,它赋值的是通过NSCopying接口的copyWithZone:方法复制赋值源所生成的对象。

并且,在声明类成员变量时,如果同属性声明中的属性不一致则会引起编译错误。比如下面这种情况。

objectivec 复制代码
id obj;
@property (nonatomic, weak)id obj;

在声明id型obj成员变量时,定义属性声明为weak,编译器报错。

此时,需要在成员变量的声明中附加__weak修饰符或者使用strong属性来替代weak属性。

数组

将变量作为静态数组使用时,附有**** strong,**** weak,**__**autoreleasing修饰符的数组可以在初始化时初始化为nil。

而对于动态数组,NSMutableArray、NSMutableDicitionary、MSMutableSet等容器会恰当地持有追加的对象并为我们管理这些对象。

像这样使用容器虽然更为合适,但在C语言的动态数组中也可以使用附有__strong修饰符的变量,但是要遵守一些事项:

声明动态数组用指针

objectivec 复制代码
id __strong *array = nil;

id * 类型默认为"id __ autoreleasing***** 类型",所以要显式指定修饰符**** strong。并且,附有****strong只保证id型变量被初始化为nil,并不保证附有__strong修饰符的id指针型变量被初始化为nil。

使用类名时如下记述:

objectivec 复制代码
NSObject * __strong *array = nil;

其次使用calloc函数确保想分配的附有__strong修饰符变量的容量占有的内存块。

objectivec 复制代码
array = (id __strong *)calloc(entries, sizeof(id));

该源代码分配了entries个所需的内存块。由于使用附有__strong修饰符的变量前必须先将其初始化为nil,所以这里使用使分配区域初始化为0的calloc函数来分配内存。不使用calloc函数,在用malloc函数分配内存后可用memset等函数将内存填充为0。

但是,像下面的源代码这样,将nil代入到malloc函数所分配的数组各元素中来初始化是非常危险的。

objectivec 复制代码
array = (id __strong *)malloc(sizeof(id) * entries);
for (NSUInteger i = 0; i < entries; ++i)
    array[i] = nil;

这是因为由malloc函数分配的内存区域没有被初始化为0,因此nil会被赋值给附有__strong修饰符的并被赋值了随机地址的变量中,从而释放一个不存在的对象。在分配内存时推荐使用calloc函数。

像这样,通过calloc函数分配的动态数组就能完全像静态数组一样使用。

objectivec 复制代码
array[0] = [[NSObject alloc]];

但是,在动态数组中操作附有__strong修饰符的变量与静态数组有很大差异,需要自己释放所有的元素。在只是简单地使用free函数废弃了数组用内存块的情况下,数组各元素所赋值的对象不能被再次释放,从而引起内存泄漏。这是因为在静态数组中,编译器能根据变量作用域自动插入释放赋值对象的代码,而在动态数组中,编译器不能确定数组的生存周期,所以无从处理。

使用动态数组时,一定要将nil赋值给所有元素中,使得元素所赋值对象的强引用失效,从而释放那些对象。在此之后,使用free函数废弃内存块。

objectivec 复制代码
for (NSUInteger i = 0; i < entries; ++i) 
    array[i] = nil;
free(array);

同初始化的注意事项相反,即使用memset等函数将内存填充为0也不会释放所赋值的对象。这非常危险,只会引起内存泄漏。对于编译器,必须明确地使用赋值给附有__strong修饰符变量的源代码。所以请注意,必须将nil赋值给所有数组元素。

并且,memcpy和realloc函数也会有危险,因为数组元素所赋值的对象有可能被保留在内存中或是重复被废弃,所以也禁止使用。

相关推荐
1zero1037 分钟前
[C语言笔记]09、指针
c语言·开发语言·笔记
得物技术20 小时前
得物 iOS 启动优化之 Building Closure
ios·性能优化
云上艺旅1 天前
K8S学习之基础七十四:部署在线书店bookinfo
学习·云原生·容器·kubernetes
你觉得2051 天前
哈尔滨工业大学DeepSeek公开课:探索大模型原理、技术与应用从GPT到DeepSeek|附视频与讲义下载方法
大数据·人工智能·python·gpt·学习·机器学习·aigc
A旧城以西1 天前
数据结构(JAVA)单向,双向链表
java·开发语言·数据结构·学习·链表·intellij-idea·idea
无所谓จุ๊บ1 天前
VTK知识学习(50)- 交互与Widget(一)
学习·vtk
FAREWELL000751 天前
C#核心学习(七)面向对象--封装(6)C#中的拓展方法与运算符重载: 让代码更“聪明”的魔法
学习·c#·面向对象·运算符重载·oop·拓展方法
吴梓穆1 天前
UE5学习笔记 FPS游戏制作38 继承标准UI
笔记·学习·ue5
Three~stone1 天前
MySQL学习集--DDL
数据库·sql·学习
齐尹秦1 天前
HTML 音频(Audio)学习笔记
学习