当前位置: 首页 > article >正文

【iOS】—— ARC学习

ARC

文章目录

  • ARC
    • 内存管理的思考方式
    • 自己生成的对象自己持有
    • 非自己生成的对象,自己也能持有
    • 不再需要自己持有的对象时释放
    • 无法释放非自己持有的对象
    • 所有权修饰符
      • 在什么时候会用到weak和strong?
      • __unsafe_unretained
      • __autoreleasing
        • __autoreleasing的应用场景
    • ARC规则
      • 必须遵守内存管理的方法名规则
      • 不要显式调用dealloc
      • 显示的转化“id”和“void*”
        • __bridge总结
      • 属性声明的属性与所有权修饰符的对应关系

ARC(Automatic Reference Counting)是指内存管理中对引用采取自动计数的计数。

在Objective-C中采用Automatic Reference Counting(ARC)机制,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者release代码,这就降低程序崩溃、内存泄露等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清除目标对象,并能立刻释放那些不再被使用的对象。如此一来,引用程序具有可预防性,且能流畅运行,速度也将大幅提升。

内存管理的思考方式

  • 自己生成的对象自己所持有
  • 非自己生成的对象自己也能持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放
对象操作Objective-c
生成并持有对象alloc/new/copy/mutableCopy方法
持有对象retain方法
释放对象release方法
废弃对象dealloc方法

这些有关Objective-C的内存管理的方法,包含在Cocoa框架中用于OS X,iOS应用开发。Cocoa框架中Foundation框架库的NSObject类负担内存管理的职责。
在这里插入图片描述

自己生成的对象自己持有

使用以下名称开头的方法名意味着自己生成的对象只有自己持有

  • alloc
  • new
  • copy
  • mutableCopy
//自己生成并持有对象

id obj1 = [[NSObject alloc] init];
id obj2 = [NSObject new];

//自己持有对象

使用NSObject类的alloc类方法就能自己生成并持有对象。指向生成并持有对象的指针被赋给变量obj。另外,使用new类方法也能生成并持有对象.
copy方法利用基于NSCopying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本。与copy方法类似,mutableCopy方法利用基于NSMutableCopying方法约定,由各类实现的。
muatbleCopyWithZone:方法生成并持有对象的副本。两者的区别在于:copy方法生成不可变的对象,而mutableCopy方法生成可变更的对象。这类似于NSArray类对象与NSMutableArray类对象的差异。用这些方法生成的对象,虽然是对象的副本,但同alloc,new方法一样。

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

//取得非自己生成并持有的对象

    id obj = [NSMutableArray array];

//取得的对象存在,但是自己不持有

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

通过retain方法,非自己生成的对象跟用alloc/new/copy/mutableCopy方法生成并持有的对象一样,成为了自己持有的。

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

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

//自己生成并持有对象
id obj = [[NSObject alloc] init];

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

//释放对象
//指向对象的指针仍然保留在变量obj中,貌似能够访问。
//但是对象一经释放绝对不可访问

如此,用alloc方法由自己生成并持有的对象就通过release方法释放了。自己生成而非自己所持有的对象,若用retain方法变为自己持有,也同样可以用release方法释放

//取得非自己生成并持有的对象
id obj = [NSMutableArray array];
//取得的对象存在,但自己不持有对象
[obj retain];
//自己持有对象
[obj release];
//释放对象
//对象不可再被访问

用alloc/new/copy/mutableCopy方法生成并持有的对象,或者用retain方法持有的对象,一旦不再需要,务必要用release方法进行释放。
如果要用某个方法生成对象,并将其返回给该方法的调用方,那么他的源代码是怎样的?

-(id)allocObject {
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
return obj;

原封不动地返回alloc方法生成并持有的对象,就能让调用方也持有该对象。

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

allocObject名称符合前文的命名规则,因此它与用alloc方法生成并持有对象的情况完全相同,所以使用allocObject方法也就意味着“自己生成并持有对象”
那么,调用[NSMutableArray array]方法使取得的对象存在,但自己不持有该对象,又是如何实现的呢?不能使用以alloc/new/copy/mutableCopy开头的方法名,因此要使用object这个方法名

-(id)object {
id obj = [[NSObject alloc] init];
//自己持有对象
[obj autorelease];
//取得的对象存在,但自己不持有该对象
return obj;
}

在这里插入图片描述
使用NSMutableArray类的array类方法等可以取得谁都不持有的对象。这些方法都是通过autorelease而实现的。这些用来取得谁都不持有的对象的方法名不能以alloc/new/copy/mutableCopy开头。

id obj1 = [obj0 object];
//取得的对象存在,但自己不持有对象

也能通过retain方法将autorelease方法取得的对象变为自己持有

id obj1 = [obj0 object];
//取得的对象存在,但自己不持有对象
[obj1 retain];
//自己持有对象

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

对于用alloc/new/copy/mutableCopy方法生成并持有的对象,或者是用retain方法持有的对象,由于持有者是自己,所以不需要对象时将其释放。而由此以外得到的对象绝不能释放。如果在应用程序中释放了非自己所持有的对象就会造成崩溃。

//自己生成并持有对象
id obj = [[NSobject alloc] init];
//自己持有对象
[obj release];
//对象已释放
[obj release];
//释放之后再次释放非自己持有的对象
//应用程序崩溃
//崩溃情况:再度风气已经废弃了的对象时崩溃
//访问已经废弃的对象时崩溃

或者是“取得的对象存在,但自己不持有对象”时释放

id obj1 = [obj0 object];
//取得的对象存在,但自己不持有该对象
[obj1 release];
//释放了非自己持有的对象
//导致程序崩溃

所有权修饰符

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

对于weak和strong相关的强弱引用问题,从去年暑假到现在一直有学习,但是最近才能搞的十分明白。
我们以例子来看看:

        id a = [[NSObject alloc] init];
        id __weak b = a;
        id c = a;
        a = nil;
        NSLog(@"%@", b);
        NSLog(@"%@", c);
        c = nil;
        NSLog(@"%@", b);
        NSLog(@"%@", c);

我们来看看输出结果:
在这里插入图片描述
从上面可以看出使用__strong 和__weak的区别,因为strong的对象会使retainCount+1,而weak的并不会。

我们打印引用计数看看就知道了:

        id a = [[NSObject alloc] init];
        id __weak b = a;
        id c = a;
        NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)a));
        NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)c));
        a = nil;
        NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)c));
        NSLog(@"%@", b);
        NSLog(@"%@", c);
        c = nil;
        NSLog(@"%@", b);
        NSLog(@"%@", c);

在这里插入图片描述

在c被赋值之后,对象的引用计数为2,当a被滞空后,c的引用计数为1,这时候b依然可以打印出来,当c被滞空后,b就也滞空了。

注意:这里提下weak和MRC时代的assign的区别,两者都可以用于弱引用,但是内存释放后使用weak会将对象置nil,而assign不会,会造成野指针,现在assign一般只用在基础类型。

在什么时候会用到weak和strong?

首先一般情况下都用strong,weak是用来解决循环引用的,如果两对象相互持有,或者相互强引用对方,所以两者永远不能被销毁,必须将其中一方的引用改成__weak的才能打破这种死循环,最常见的就是delegate模式和block情况下使用。

1.当两个不同的对象各有一个强引用指向对方时就造成循环引用,会导致对象无法释放,例如我们常用的delegate, 见图:
在这里插入图片描述

@property (nonatomic, weak) id<Delegate> delegate
//MRC用assign

2.block的使用经常会有这个问题,在前几期博客中讲到了这个问题。
block的使用也会照成循环引用,比如当一个对象持有block,而该block又持有该对象时。

[self block:^{      
    self.number = 1;
}];

解决方法:

__weak typeof(self) weakself = self;
[self block:^{       
    weakself.number = 1;
}];

注意:只有该对象持有block,而block里的代码块又持有该对象时才需要用到weak。

3.NSTimer 也会照成循环引用,所以使用了NSTimer后,释放资源前要先调用invalidate方法

[Timer invalidate];
Timer = nil;

__unsafe_unretained

__unsafe_unretained和__weak很像,唯一区别就是,__unsafe_unretained变量引用的对象再被销毁以后,不会被自动设置为nil,仍然指向对象销毁前的内存地址。所以它的名字叫做unsafe,此时你再尝试通过变量访问这个对象的属性或方法就会crash。

我们还是以之前weak和strong的例子来看看:(把__weak换成__unsafe_unretained)

id a = [[NSObject alloc] init];
        id __unsafe_unretained b = a;
        id c = a;
        a = nil;
        NSLog(@"%@", b);
        NSLog(@"%@", c);
        c = nil;
        NSLog(@"%@", b);
        NSLog(@"%@", c);

程序直接崩溃了:
在这里插入图片描述

__autoreleasing

  • 表示在autoreleasepool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不应该是autorelease型的。
  • __autoreleasing可以说跟__strong有点类似,你可以放心地在autoreleasing块结束此前都可以正常地使用这个对象,而不用担心它被销毁。
  • 这个对象也会被注册到autoreleasepool里,将autoreleasepool结束时候才会被销毁,而在变量的作用域结束后不会像__strong那样马上被release。

__autoreleasing的应用场景

__autoreleasing在我们实际真实的iOS应用开发的时候,基本上是用不到的,我说的用不到只是针对业务程序员来说。实际上编译器是会用到它的,另外在一些方法声明的时候,你也会看到自动地给你加上的__autoreleasing,即时你没有显式地手敲出来。

__autoreleasing示例:

#import "MyObject.h"
@interface MyObject ()

@property (nonatomic, weak)NSObject *weakObj1;
@property (nonatomic, weak)NSObject *weakObj2;

@end

@implementation MyObject
- (void)test1 {
    NSLog(@"--1---%@--%@",self.weakObj1,self.weakObj2);
    [self test2];
    NSLog(@"--3---%@--%@",self.weakObj1,self.weakObj2);
}

- (void)test2 {
    __autoreleasing NSObject *obj1 = [NSObject new];
    NSObject *obj2 = [NSObject new];
    self.weakObj1 = obj1;
    self.weakObj2 = obj2;
    NSLog(@"--2---%@--%@",self.weakObj1,self.weakObj2);
}
@end

我们来看看输出结果:
在这里插入图片描述

__autoreleasing修饰的变量的生命周期不再受当前函数作用域影响,转而被自动缓存池影响,因此用__autoreleasing修饰变量可延长变量的生命周期。

__autoreleasing 怎么做到的?

添加至自动缓存池的变量会在缓存池结束的时候收到一次 release,但是怎么做到摆脱当前函数作用域影响的?

我们来看这个方法:

- (void)test3 {
    __autoreleasing NSObject *obj1 = [[NSObject alloc] init];
    NSObject *obj2 = [[NSObject alloc] init];
}

我们把它编译成汇编看看:

ARC测试`-[MyObject test3]:
    0x100003b20 <+0>:  sub    sp, sp, #0x40
    0x100003b24 <+4>:  stp    x29, x30, [sp, #0x30]
    0x100003b28 <+8>:  add    x29, sp, #0x30
    0x100003b2c <+12>: stur   x0, [x29, #-0x8]
    0x100003b30 <+16>: stur   x1, [x29, #-0x10]
    0x100003b34 <+20>: adrp   x8, 5
    0x100003b38 <+24>: str    x8, [sp, #0x8]
->  0x100003b3c <+28>: ldr    x0, [x8, #0x140]
    0x100003b40 <+32>: bl     0x100003cf0               ; symbol stub for: objc_alloc_init
    0x100003b44 <+36>: bl     0x100003cfc               ; symbol stub for: objc_autorelease
    0x100003b48 <+40>: ldr    x8, [sp, #0x8]
    0x100003b4c <+44>: str    x0, [sp, #0x18]
    0x100003b50 <+48>: ldr    x0, [x8, #0x140]
    0x100003b54 <+52>: bl     0x100003cf0               ; symbol stub for: objc_alloc_init
    0x100003b58 <+56>: mov    x8, x0
    0x100003b5c <+60>: add    x0, sp, #0x10
    0x100003b60 <+64>: str    x8, [sp, #0x10]
    0x100003b64 <+68>: mov    x1, #0x0
    0x100003b68 <+72>: bl     0x100003d68               ; symbol stub for: objc_storeStrong
    0x100003b6c <+76>: ldp    x29, x30, [sp, #0x30]
    0x100003b70 <+80>: add    sp, sp, #0x40
    0x100003b74 <+84>: ret    

在这里插入图片描述

我们再将汇编翻译成伪代码:

- (void)test3 {
    
   // __autoreleasing NSObject *obj1 = [NSObject new]; 等同于:
    id obj1 = objc_msgSend(NSObject, @selector(new));
    objc_autorelease(obj1);
    
   // NSObject *obj2 = [NSObject new]; 等同于:
    id obj2 = objc_msgSend(NSObject, @selector(new));
    defer {
       objc_storeStrong(&obj2,nil)
    }   
}

我们来分析一下:

  • obj_msgSend主要用于消息发送,就是告诉编译器我们要初始化这个对象
  • obj_msgSend(NSObject, @selector(new))就是新建一个对象,而objc_storeStrong是 objc4 库中的方法,具体逻辑如下:
// strong
void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

其中涉及到了block里看到的类似 objc_retain 和 objc_release,其实就是持有和释放对象的过程

  • 检查输入的 obj 地址 和指针指向的地址是否相同。
  • 持有对象,引用计数 + 1 。objc_retain(obj)
  • 指针指向 obj。
  • 原来指向的对象引用计数 - 1。objc_release(prev)

其中 objc_autorelease(obj1) 即是将obj1添加至自动缓存池。
obj1 在被__autoreleasing修饰后被加入至自动缓存池,并且在函数结束时不会再调用objc_storeStrong,因此摆脱函数作用域对它的生命周期的影响。

ARC规则

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 必须遵守内存管理的方法名规则
  • 不要显式调用dealloc
  • 使用@autorelease块代替NSAutoreleasePool
  • 不能使用区域(NSZone)
  • 对象型变量不能作为C语言结构体的成员
  • 显式转换id和void*

必须遵守内存管理的方法名规则

ARC无效的时候,用于对象生成的持有的方法必须遵守以下命名规则
alloc new copy mutableCopy等上述名称方法返回对象必须返回给调用方应当持有的对象,ARC有效和上述一样,但是init需要注意

以init开始的方法规则要比上述严格,该方法必须是实例方法并且必须返回对象,返回的类型为方法的声明类型,超类或子类,该方法返回对象不注册到autoPool里。

- (id) initWithObject:(id) obj;上述方法遵守规则并返回了对象
- (void) initThisObject;没有返回对象不允许使用
- (void) initialize 之前说过,这个方法不属于上述行列,是在对象初始化之前必须调用的,不需要遵守上述规则

不要显式调用dealloc

dealloc无法释放不属于该对象的一些东西,需要我们重写时加上去,例如:

通知的观察者,或KVO的观察者

对象强委托/引用的解除(例如XMPPMannerger的delegateQueue)

做一些其他的注销之类的操作(关闭程序运行期间没有关闭的资源)

ARC无效的时候调用需要[super dealloc]
ARC有效的时候我们需要记述废弃对象时候我们需要的处理

显示的转化“id”和“void*”

ARC无效的时候我们可以转化“id”和“void*”和调用一些实例方法

    id obj = [[NSObject alloc] init];
    void *p = obj;

ARC有效的时候这样是不行的。

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

- (void)OCAndVoidUse__bridgeInARC {
    
    id obj = [[NSObject alloc] init];
    
    void *p = (__bridge void *)obj;
    
    id o = (__bridge id)(p);
}

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

此时P不持有对象 __bridge并不会改变持有情况。

__bridge总结

  • __bridge可以实现Objective-C与C语言变量 和 Objective-C与Core Foundation对象之间的互相转换
  • __bridge不会改变对象的持有状况,既不会retain,也不会release
  • __bridge转换需要慎重分析对象的持有情况,稍不注意就会内存泄漏
  • __bridge_retained用于将OC变量转换为C语言变量 或 将OC对象转换为Core Foundation对象
  • __bridge_retained类似于retain,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后持有该对象
  • __bridge_transfer用于将C语言变量转换为OC变量 或 将Core Foundation对象转换为OC对象
  • __bridge_transfer类似于release,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后随之释放

属性声明的属性与所有权修饰符的对应关系

在这里插入图片描述


http://www.kler.cn/a/2229.html

相关文章:

  • 深入探索离散 Hopfield 神经网络
  • 图论-代码随想录刷题记录[JAVA]
  • 分布式锁实践方案
  • 2411d,右值与移动
  • react 中 useContext Hook 作用
  • SQL,力扣题目1127, 用户购买平台
  • 学习前端day01
  • 【百面成神】Redis基础11问,你能坚持到第几问
  • 瑞萨Renesas RA2L1 开发板测评(2)--LED闪烁
  • web前端框架——Vue的特性
  • Python程序员看见一个好看的手机壁纸网站,开撸!
  • Linux操作系统ARM体系结构处理器机制原理与实现
  • Vue面试题 路由守卫
  • 【Hive】HQL
  • 【2023新星计划 】博客创作指导 活动解读
  • ChatGPT应用场景与工具推荐
  • GPT-4是个编程高手,真服了!
  • MyBatis --- 缓存、逆向工程、分页插件
  • 【K8S系列】深入解析Pod对象(一)
  • MySQL-触发器
  • 继承和派生
  • vue后台管理系统
  • 网络安全工具大合集
  • C语言学习之路--结构体篇
  • [JS] JS小技巧
  • Python3,5行代码,生成自动排序动图,这操作不比Excel香?