博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Objective-C内存管理
阅读量:6845 次
发布时间:2019-06-26

本文共 6949 字,大约阅读时间需要 23 分钟。

释放掉不用的内存,保证还可能被使用的内存不会被回收。这是内存管理要做的的事情,OC是通过引用计数来管理的,MRC和ARC的区分只是:引用计数是由程序员还是编译器和语言来负责管理。

为啥要使用引用计数

在c中堆中的对象是由程序员负责的:

// malloc必须和free成对出现char *str = (char*)malloc(sizeof(char)*10);// do something// 如果忘了free就泄漏了10个字节的内存,如果多次free,那将导致崩溃free(str);// 如果此时又使用了str,这块内存可能已经分配做其他用途了,所以行为是不确定的// do not use str复制代码

看起来好像很简单,但是在do somethind的过程中可能经历里很多的,所以说保证这块内存的正确使用完全依靠程序员,有很大的风险,而且调试指针异常也是很麻烦的。

OC中的引用计数

// NSObject 提供的引用计数的功能- (instancetype)retain OBJC_ARC_UNAVAILABLE;    // rc++- (oneway void)release OBJC_ARC_UNAVAILABLE;    // rc--,若rc==0,则释放该实例对象- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;   // 延迟释放复制代码

苹果给出了使用引用计数的原则:

  1. 你拥有你创建的任何对象,调用命名上为“alloc”, “new”, “copy”, or “mutableCopy”的函数,将获得一个有所有权的实例对象(You own any object you create)
// 这个NSObject的实例对象属于obj,也就是obj拥有NSObject的实例对象NSObject *obj = [[NSObject alloc] init];复制代码
  1. 可以使用retain获取对象的所有权
// 这个NSObject的实例对象属于obj,也就是obj拥有NSObject的实例对象NSObject *obj = [[NSObject alloc] init];// 通过retain方法,这个对象也属于obj1了NSObject *obj1 = [obj retain];复制代码
  1. 当不再需要它时,必须放弃你拥有的对象的所有权
// 这个NSObject的实例对象属于obj,也就是obj拥有NSObject的实例对象NSObject *obj = [[NSObject alloc] init];// 通过retain方法,这个对象也属于obj1了NSObject *obj1 = [obj retain];// 通过release方法,放弃objc1对这个实例对象的所有权[objc1 release];复制代码
  1. 不能放弃没有拥有权对象的所有权
// 根据1中的命名规则,str没有这个NSString实例对象的所有权NSString *str = [NSString stringWithFormat:@"hello NSString"];// 所以如下,释放一个没有所有权实例对象的所有权是错误的[str release];复制代码

关于autorelease(延时释放)

//- (id)myMethod {    // obj 拥有 MyObject实例对象的所有权,引用计数为1    MyObject *obj = [[MyObject alloc] init];    // 如果按照上述苹果的内存管理原则3,你需要在return前release obj,但是如果release了就被回收了    // 这就用到了autorelease 将对象延时释放    return [obj autorelease];}复制代码

autorelease的实现是要配合autoreleasepool来完成的,AutoreleasePoolPage是由一个双向链表实现的栈,每个节点一个虚页的大小。autorelease消息实际是将obj push到栈中,并在pop时进行release。

MRC与ARC

MRC:手动引用计数,需要我们手动控制拥有权,也就是负责发送retain release autorelease消息。 ARC:通过分析编译生成的语法树,编译器帮我们自动插入引用计数相关的代码。

ARC存在的问题和解决方法

考虑如下代码:

// ARC环境下@interface Person : NSObject 
@property (nonatomic, copy)NSString *name;@property (nonatomic)Person *partner;+(instancetype) personWithName: (NSString*)name;@endPerson *xiaohong = [Person personWithName: @"xiaohong"];Person *xiaoming = [Person personWithName: @"xiaoming"];xiaohong.partner = xiaoming;xiaoming.partner = xiaohong;复制代码

在上述代码中,xiaohongxiaoming如果没有其他的引用,那么最终他们的引用计数都为1,所以他们都不能被释放,又因为不能被释放的原因是xiaohong引用xiaoming导致xiaoming不能被释放,xiaoming引用xiaohong导致xiaohong不能被释放,如下图①,所以称为循环引用

循环引用示意图.png

如何解决循环引用呢,首先选择一方放弃对另一方的引用,例如xiaohong.partner = nil;,如上图②所示,此时没有对象引用xiaoming,所以释放,在xiaoming调用dealloc时将会放弃对xiaohong的引用,所以xiaohong也能得以正确释放。

使用weak来解决循环引用

在使用weak关键字:

// ARC环境下@interface Person : NSObject 
@property (nonatomic, copy)NSString *name;@property (nonatomic, weak)Person *partner;+(instancetype) personWithName: (NSString*)name;@endPerson *xiaohong = [Person personWithName: @"xiaohong"];Person *xiaoming = [Person personWithName: @"xiaoming"];xiaohong.partner = xiaoming;xiaoming.partner = xiaohong;复制代码

此时的情况如上图③所示,weak有并不会增加对象的引用计数,所以在xiaohongxiaoming可以顺利的被释放。

其实解决循环引用只需要要打破这个引用环就可以,即如图②所示, 图③所示只是一种充分非必要条件。

Core Foundation和ARC

ARC是针对于NSObject的一套方法,所以在Core Framework是不适用的,OC中的一些对象与Core Frameword又是toll-bridge(就是可以进行简单的相互转换)的,虽然结构可以相互转换,但是还需要考虑所有权的问题,即谁来负责release的工作。

// 即使是两种结构是toll-bridge的,我们也不能简单的类似于(NSString*)的指针强转,因为还有所有权问题(引用计数)// cf与NSObject结构相互转化的关键字__bridge    // NSObject <-> cf 不改变所有权__bridge_retained   // NSObject -> cf   将所有权从ARC交给CF的引用计数   __bridge_transfer   // cf -> NSObject   将所有权从CF的引用计数交给ARC复制代码

在实际使用的过程中,无非两种情况,内存管理最终交由谁处理呢?

1. 最终由ARC负责内存管理

// case 1 这应该是最常用的方式    NSString *str = [[NSString alloc]initWithFormat:@"test"];      CFStringRef cfStr = (__bridge CFStringRef)str; // 不改变所有权,类似于(CFStringRef)str    // case 2     CFStringRef cfName = ABRecordCopyValue(person, kABPersonFirstNameProperty);    NSString *name = (__bridge_transfer NSString*)cfName;   // 将所有权从CF的引用计数交给ARC    // case 3   下面实际是发生了所有权变化,但经过步骤1,2最终又将所有权交给了ARC    NSString *str = [[NSString alloc]initWithFormat:@"test"];      CFStringRef cfStr = (__bridge_retained CFStringRef)str; // 1. 将所有权从ARC交给CF的引用计数    str = (__bridge_transfer NSString*)cfstr;  // 2. 将所有权从CF的引用计数交给ARC复制代码

2. 最终由CF的引用计数负责

NSString *str = [[NSString alloc]initWithFormat:@"test"];      CFStringRef cfStr = (__bridge_retained CFStringRef)str; // 将所有权从ARC交给CF的引用计数    CFRelease(cfStr); // 由CF的内存管理负责回收复制代码

由objc associated object 引发的循环引用

在category增加property时我们很有可能会使用associated object

// 获取与self associal 的objectobjc_getAssociatedObject(self, &key);// 设置与self associal 的objectobjc_setAssociatedObject(self, &key, retainAssocialValueInCategory, OBJC_ASSOCIATION_RETAIN_NONATOMIC);// associal的object与self的四种关系typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.                                             *   The association is not made atomically. */    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.                                             *   The association is not made atomically. */    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.                                            *   The association is made atomically. */    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.                                            *   The association is made atomically. */};复制代码

如果有如下代码:

@interface Person: NSObject @property NSString *name;@property Person *partener;@end@implementation Personstatic char partenerAssocialKey;- (void)setPartener: (Person*)partener {    objc_setAssociatedObject(self, &partenerAssocialKey, partener, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (Person*)partener {    return objc_getAssociatedObject(self, &partenerAssocialKey);}@endPerson *xiaoming = [[Person alloc] init];Person *xiaohong = [[Person alloc] init];xiaoming.name = @"xiaoming";xiaohong.name = @"xiaohong";// 引用循环,由associal object 引起xiaoming.partener = xiaohong;xiaohong.partener = xiaoming;复制代码

解决方案也比较简单

// 加一层包装@interface CD_weakAssociatedObject : NSObject@property (weak) id value;@end@implementation CD_weakAssociatedObject@end// 实现非原子的set associate方法void cd_setWeakAssociatedObject(id _Nonnull object, id _Nullable value, const void* _Nonnull key) {    CD_weakAssociatedObject *obj = objc_getAssociatedObject(object, key);    if (!obj) {        obj = [[CD_weakAssociatedObject alloc] init];        objc_setAssociatedObject(object, key, obj, OBJC_ASSOCIATION_RETAIN);    }    obj.value = value;}// getid _Nonnull cd_getWeakAssociatedObject(id _Nonnull object, const void* _Nonnull key) {    CD_weakAssociatedObject *obj = objc_getAssociatedObject(object, key);    if (obj) {        return obj.value;    }    return nil;}复制代码

通过添加对associated object的一层包装,实现指向associated object的弱引用

转载于:https://juejin.im/post/5c8cbfcef265da2d9b5e602c

你可能感兴趣的文章
margin-top失效的解决方法
查看>>
FireBug与FirePHP
查看>>
使用socket方式连接Nginx优化php-fpm性能
查看>>
bootstrap笔记
查看>>
c#初学-select和Dictionary字典在c#中的用法
查看>>
git配置之i18n
查看>>
adk环境变量配置
查看>>
jquery中跳出each循环
查看>>
xcconfig 文件配置文件 问题
查看>>
Linux ——————用Secure传文件时直接拖了文件用的是AssIC导致linux那边直乱码...
查看>>
LR的响应时间与使用IE所感受时间不一致的讨论
查看>>
MyBatis中的@Mapper注解及配套注解使用详解(上)
查看>>
Javascript 调用C# 代码并传递参数的两种方法
查看>>
String和datetime在SQL中和在C#中相互转换方法总结
查看>>
Netbeans 7.1中的XML Layer
查看>>
android讲义2之计时器组件Chronometer
查看>>
dos下输出余弦函数图形
查看>>
php+js+mysql设计的仿webQQ-<5>IM窗体的实现
查看>>
BSP技术详解1------有图有真相
查看>>
一步一步学lucene——(第一步:概念篇)
查看>>