【iOS开发】——ARC(自动管理内存)的一些补充

上学期我总结过一些关于ARCiOS开发——ARC的知识点,最近在复习ARC,发现当时学习时有一些知识点没有总结好而且有些知识点没有总结到,所以再写一篇博客补充一些知识。

__bridge

我们在使用__bridge修饰符的时候用的最多的就是__bridge转换,那么主要有哪些转换可以用到__bridge呢?

OC指针与void *互相转换

使用 __bridge_retained

- (void)OCToVoid__bridge_retainedInARC {

    void *p = 0;
    {
        id obj = [[NSObject alloc] init];
        p = (__bridge_retained void *)obj;
//        p = (__bridge void *)obj; 报错,obj出了作用域就会销毁,__bridge不改变持有情况,所以p成为悬垂指针
    }
    
    NSLog(@"class===%@",[(__bridge id)p class]);
}

若将上述代码中p = (__bridge_retained void *)obj;改为p = (__bridge void *)obj; 会报错,因为obj出了作用域就会销毁,__bridge不改变持有情况,所以p成为悬垂指针

ARC下使用 __bridge_retained对应在MRC的过程

- (void)OCToVoid__bridge_retainedInMRC {
    /* MRC下 */
    void *p = 0;
    
    {
        id obj = [[NSObject alloc] init];
        NSLog(@"obj retainCount===%lu",[obj retainCount]);
        /* [obj retainCount] -> 1 */
        
        p = [obj retain];
        NSLog(@"obj retainCount===%lu",[obj retainCount]);
        /* [obj retainCount] -> 2 */
        
        [obj release];
        NSLog(@"obj retainCount===%lu",[obj retainCount]);
        /* [obj retainCount] -> 1 */
    }
    
    NSLog(@"(id)p retainCount===%lu",[(id)p retainCount]);
    /**
     * [(id)p retainCount] -> 1
     * 即
     * [obj retainCount] -> 1
     * 对象仍然存在
     */
    NSLog(@"class===%@",[(__bridge id)p class]);
}

所以我们就很好理解为什么p = (__bridge_retained void *)obj;改为p = (__bridge void *)obj; 会报错,那是因为:

  • __bridge_retained 转换可使要转换赋值的变量也持有所赋值的对象。
  • __bridge_retained 转换变为了 retain
  • 变量obj和变量p同时持有对象。
  • 变量作用域结束时,虽然随着持有强引用的变量obj失效,对象随之释放,但由于 __bridge_retained 转换使变量p看上去处于持有该对象的状态,因此该对象不会被废弃。

void *指针转换为OC指针

使用 __bridge_transfer

- (void)VoidToOC__bridge_transferInARC {
    
    void *p = 0;
    {
        id tempObj = [[NSObject alloc] init];
        p = (__bridge_retained void *)tempObj;
    }
    
    id obj = (__bridge_transfer id)p;
}

ARC下使用 __bridge_transfer对应在MRC的过程

- (void)VoidToOC__bridge_transferInMRC {
    void *p = 0;
    {
        id tempObj = [[NSObject alloc] init];
        p = [tempObj retain];
        [tempObj release];
    }
    
    id obj = (id)p;
    /**
     * 同__bridge_retained转换与retain类似,__bridge_transfer转换与release相似。
     * 在给id obj 赋值时retain即相当于__strong修饰符的变量。
     */
    [obj retain];
    [(id)p release];
}
  • __bridge_transfer转换与 __bridge_retained 相反,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后随之释放
  • __bridge_retained 转换与retain类似,__bridge_transfer 转换与release相似。在给 id obj 赋值时 retain 即相当于 __strong 修饰符的变量。

Objective-C指针与CoreFoundation指针之间的转换

ARC仅管理Objective-C指针(retainreleaseautorelease),不管理CoreFoundation指针。CF指针由人工管理,手动的CFRetainCFRelease来管理。

在ARC中,CF和OC之间的转化桥梁是 __bridge,有3种方式:

  • __bridge: 只做类型转换,不改变对象所有权,是我们最常用的转换符。
  • __bridge_transfer:ARC接管 管理内存
  • __bridge_retained:ARC释放 内存管理

从OC转CF,ARC管理内存:

  • (__bridge CFStringRef)
  • 需要人工CFRetain,否则,Cocoa指针释放后, 传出去的指针则无效。

例如:

- (void)viewDidLoad {  
    [super viewDidLoad];  
      
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef aCFString = (__bridge CFStringRef)aNSString;  
      
    (void)aCFString;  
}

上面只是单纯地执行了类型转换没有进行所有权的转移,也就是说,当aNSString对象被ARC释放的时候,aCFString也不能被使用了。

从CF转OC,需要开发者手动释放,不归ARC管:

  • (__bridge NSString*)
  • 需要人工CFRelease,否则,OC对象的指针释放后,对象引用计数仍为1,不会被销毁。

例如:

- (void)viewDidLoad {  
    [super viewDidLoad];  
      
    CFStringRef aCFString = CFStringCreateWithCString(NULL, "test", kCFStringEncodingASCII);  
    NSString *aNSString = (__bridge NSString *)aCFString;  
      
    (void)aNSString;  
      
    CFRelease(aCFString);  
}  

ARC下内存管理发生改变的转换

CF–>OC:__bridge_transfer:

  • 非Objective-C 指针到 Objective-C 指针并移交持有权给ARC。ARC负责释放对象

例如:

- (void)viewDidLoad {  
    [super viewDidLoad];  
      
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;  
    aNSString = (__bridge_transfer NSString *)aCFString;  
}  

OC–>CF:__bridge_retained:

  • Objective-C 指针到Core Foundation 指针并移交持有权。你要负责调用 CFRelease 或一个相关的函数来释放对象。

例如:

- (void)viewDidLoad  {  
    [super viewDidLoad];  
      
    NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;  
      
    (void)aCFString;  
      
    //这时候,即使开启ARC,也需要手动执行CFRelease  
    CFRelease(aCFString);   
}  

OC–>CF/CF–>OC进行转换时的细节

  • 因为ARC无法管理CF对象的指针,所以,无论是CF转OC还是OC转CF,我们只需关心CF对象的引用需要加1还是减1即可。

CF转OC:CFRef必须减1
这样原来的CF对象就被释放,所以,以后也不用手动释放。

NSString   *c = (__bridge_transfer NSString*)my_cfref; // -1 on the CFRef   

OC转CF:CFRef 必须加1
这样新的CF对象就不会被释放,所以,以后用完必须手动释放。

CFStringRef d = (__bridge_retained CFStringRef)my_id;  // returned CFRef is +1  

//这时候,即使开启ARC,CF对象用完后也需要手动执行CFRelease  
CFRelease(aCFString); 

以前总结过,再总结一下__bridge
在这里插入图片描述

关于ARC的一些具体问题

什么是ARC

所谓ARC就是自动管理内存,因为在以前内存管理是由程序员自己手动管理的想想就很头疼,所以就有了自动的引用计数(Automatic Reference Count 简称 ARC)的概念,引用计数是一个简单而有效的管理对象生命周期的方式。当我们创建一个对象时,它的引用计数就为1,当有一个新的指针指向它时,其引用计数就加1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。引用计数简单有效,帮助我们解决了关于内存管理的问题,所以引用计数也是ARC的基础。

ARC的工作原理

当我们编译源码的时候,编译器会分析源码中每个对象的生命周期,然后基于这些对象的生命周期,来添加相应的引用计数操作代码。所以,ARC 是工作在编译期的一种技术方案,这样的好处是:

  • 编译之后,ARC 与非 ARC 代码是没有什么差别的,所以二者可以在源码中共存。实际上,你可以通过编译参数 -fno-objc-arc 来关闭部分源代码的 ARC 特性。
  • 相对于垃圾回收这类内存管理方案,ARC 不会带来运行时的额外开销,所以对于应用的运行效率不会有影响。相反,由于 ARC 能够深度分析每一个对象的生命周期,它能够做到比人工管理引用计数更加高效。 例如在一个函数中,对一个对象刚开始有一个引用计数 +1的操作,之后又紧接着有一个 -1 的操作,那么编译器就可以把这两个操作都优化掉。

weak修饰的对象被释放时,weak指针自动被置为nil的实现原理

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个哈希表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。

具体步骤:

  1. 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
  2. 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
  3. 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

当weak引用指向的对象被释放时,如何去处理weak指针?

  1. 调用objc_release
  2. 因为对象的引用计数为0,所以执行dealloc
  3. 在dealloc中,调用了_objc_rootDealloc函数
  4. 在_objc_rootDealloc中,调用了object_dispose函数
  5. 调用objc_destructInstance
  6. 最后调用objc_clear_deallocating

简单来说:
a. 从weak表中获取被释放对象的地址为键值的记录
b. 将包含在记录中的所有附有 weak修饰符变量的地址,赋值为 nil
c. 将weak表中该记录删除
d. 从引用计数表中删除废弃对象的地址为键值的记录

为什么ARC仍然需要@autoreleasepool?

在大多数情况下使用ARC(自动引用计数),我们不需要考虑使用Objective-C对象的内存管理。不允许再创建NSAutoreleasePools,但是有一个新的语法:

@autoreleasepool {}

相当于:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];[pool drain];
  • ARC使用autorelease以及release。
  • 它需要一个自动释放池才能这样做。
  • ARC不会为您创建自动释放池。然而:
    • 每个Cocoa应用程序的主线程都有一个自动释放池。

有两种情况你可能想要使用@autoreleasepool:

  • autorelease 机制基于 UI framework。因此写非UI framework的程序时,需要自己管理对象生存周期。
  • autorelease 触发时机发生在下一次runloop的时候。因此如何在一个大的循环里不断创建autorelease对象,那么这些对象在下一次runloop回来之前将没有机会被释放,可能会耗尽内存。这种情况下,可以在循环内部显式使用@autoreleasepool {}将autorelease 对象释放。
  • 自己创建的线程。Cocoa的应用都会维护自己autoreleasepool。因此,代码里spawn的线程,需要显式添加autoreleasepool。注意:如果是使用POSIX API 创建线程,而不是NSThread,那么不能使用Cocoa,因为Cocoa只能在多线程(multithreading)状态下工作。但可以使用NSThread创建一个马上销毁的线程,使得Cocoa进入multithreading状态。

ARC在编译期和运行期做了什么

  1. 在编译期,ARC会把互相抵消的retain、release、autorelease操作约简。
  2. ARC包含有运行期组件,可以在运行期检测到autorelease和retain这一对多余的操作。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。具体为:为了保证向后兼容性,ARC 在运行时检测到类函数中的 autorelease 后紧跟其后 retain,此时不直接调用对象的 autorelease 方法,而是改为调用 objc_autoreleaseReturnValue。
    objc_autoreleaseReturnValue 会检视当前方法返回之后即将要执行的那段代码,若那段代码要在返回对象上执行 retain 操作,则设置全局数据结构中的一个标志位,而不执行 autorelease 操作,与之相似,如果方法返回了一个自动释放的对象,而调用方法的代码要保留此对象,那么此时不直接执行 retain ,而是改为执行 objc_retainAoutoreleasedReturnValue函数。此函数要检测刚才提到的标志位,若已经置位,则不执行 retain 操作,设置并检测标志位,要比调用 autorelease 和retain更快。

总结

目前对ARC的理解与问题的总结主要为这些,肯定还有很多不足,等以后再有理解或者再碰见有关ARC的问题时再进行补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: iOS内存管理版本记录如下: 1. iOS 2.0及更早版本:使用手动管理内存的方式。 2. iOS 3.0:引入了基于引用计数的自动内存管理,使用retain和release函数来增加或减少对象的引用计数。 3. iOS 5.0:引入了ARC自动引用计数)机制,ARC会在编译时自动插入retain和release代码,减少手动管理内存的工作。 4. iOS 7.0:引入了内存诊断工具Memory Usage Report,可以监测App内存使用情况,帮助开发者优化内存管理。 5. iOS 8.0:引入了一些新的API,如NSCache和NSURLSession,使得内存管理更加方便和灵活。 6. iOS 11.0:引入了基于图片大小的UIImage渲染机制,减少了内存占用。 7. iOS 13.0:引入了叫做“Scene”的多任务环境,使得内存管理更加复杂,需要更加小心谨慎地处理内存问题。 总的来说,随着iOS版本的不断更新,内存管理的机制也在不断地完善和优化,使得iOS应用能够更加高效地使用内存,提高用户体验。 ### 回答2: iOS内存管理是由操作系统自动管理的,在不同的版本中有所不同。 在iOS 5之前的版本中,内存管理主要依赖于手动管理引用计数(reference counting)来管理对象的生命周期。开发者需要手动调用retain和release方法来增加或减少对象的引用计数,以确保对象在不再需要时能够被正确释放。这种方式需要开发者非常谨慎地管理对象的引用,以避免内存泄漏或野指针等问题。 从iOS 5开始,iOS引入了自动引用计数(Automatic Reference Counting,ARC)的内存管理机制。ARC可以自动地插入retain、release和autorelease等方法的调用,使得开发者不再需要手动进行内存管理开发者只需要关注对象的创建和使用,而不需要关心具体的内存管理细节。ARC减少了内存管理的工作量,提高了开发效率,并且减少了内存泄漏和野指针等问题的发生。不过,ARC并不是完全的自动内存管理开发者仍然需要遵循一些规则,比如避免循环引用等,以保证内存的正确释放。 随着iOS版本的不断更新,苹果不断改进和优化内存管理机制。每个新版本都带来了更好的性能和更高效的内存管理开发者可以通过关注苹果的官方文档和开发者社区中的更新内容来了解每个版本中的具体变化和改进。 总结来说,iOS内存管理从手动的引用计数到自动引用计数的演变,极大地简化了开发者的工作,并提高了应用的性能和稳定性。随着不断的改进和优化,iOS内存管理会越来越高效和可靠。 ### 回答3: iOS内存管理版本记录是指苹果公司在不同版本的iOS操作系统中对于内存管理方面的改进和更新记录。随着iOS版本的不断迭代,苹果在内存管理方面进行了一系列的优化和改进,以提高系统的稳定性和性能。 首先,在早期的iOS版本中,苹果采用了手动内存管理的方式,即开发人员需要手动创建和释放内存,容易出现内存泄漏和内存溢出等问题。为了解决这些问题,苹果在iOS5版本中引入了自动引用计数(ARC)机制。ARC机制能够通过编译器自动生成内存管理代码,避免了手动管理内存带来的问题。 其次,iOS6版本引入了内存分页机制。这个机制能够将应用程序内存分成不同的页,将不常用的页置于闲置列表中,从而释放出更多的内存空间。这些闲置列表中的页能够在需要时快速恢复到内存中,减少了内存压力。 此外,iOS7版本中进一步提升了内存管理的能力。苹果在这个版本中引入了内存压缩技术,将内存中的数据进行压缩,从而提高了内存利用率。此外,iOS7还引入了资源清理功能,可以自动清理不再使用的资源,释放内存空间。 最后,在iOS13版本中,苹果进一步改进了内存管理策略。该版本中引入了后台内存优化功能,能够自动优化应用在后台运行时的内存占用,减少了后台应用对于系统内存的占用和影响。 综上所述,iOS内存管理版本记录反映了苹果在不同版本的iOS操作系统中对于内存管理方面的改进和优化。这些改进和优化使得iOS系统更加稳定和高效,并且提升了应用程序的性能和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值